Почему функция [-A1,..., + B] не позволяет разрешать какие-либо супертипы в качестве параметров?

Я считаю, что можно определить ковариацию (по крайней мере, для объектов) как "способность использовать значение более узкого (вспомогательного) типа вместо значения некоторого более широкого (супер) типа", и что контравариантность - это прямо противоположное этому.

По-видимому, функции Scala являются экземплярами функции [-A1,..., + B] для контравариантных типов параметров A1 и т.д. и ковариантного типа возврата B. В то время как это удобно для подтипирования функций, t означает, что я могу передавать любые супертипы в качестве параметров?

Пожалуйста, сообщите, где я ошибаюсь.

Ответы

Ответ 1

Ковариация и контравариантность являются качествами класса, а не качествами параметров. (Это качества, зависящие от параметров, но они делают заявления о классе.)

Итак, Function1[-A,+B] означает, что функция, которая принимает суперклассы A, может рассматриваться как подкласс исходной функции.

Посмотрите это на практике:

class A
class B extends A
val printB: B => Unit = { b => println("Blah blah") }
val printA: A => Unit = { a => println("Blah blah blah") }

Теперь предположим, что вам нужна функция, которая умеет печатать B:

def needsB(f: B => Unit, b: B) = f(b)

Вы можете пройти printB. Но вы также можете пройти printA, так как он также знает, как печатать B (и многое другое!), Как если бы A => Unit был подклассом B => Unit. Это именно то, что означает контравариантность. Это не значит, что вы можете передать Option[Double] в printB и получить что-либо, кроме ошибки времени компиляции!

(Ковариация - это другой случай: M[B] <: M[A], если B <: A.)

Ответ 2

Этот вопрос старый, но я думаю, что более ясное объяснение заключается в том, чтобы ссылаться на принцип замещения Лискова: все, что верно в отношении суперкласса, должно быть справедливо для всех его подклассов. Вы должны иметь возможность делать с SubFoo все, что вы можете сделать с помощью Foo и, возможно, больше.

Предположим, что у нас есть Calico <: Cat <: Animal и Husky <: Dog <: Animal. Посмотрите на Function[Cat, Dog]. Какие утверждения верны об этом? Есть два:

(1) Вы можете передать любой Cat (так что любой подкласс Cat)

(2) Вы можете вызвать любой метод Dog для возвращаемого значения

Значит, имеет смысл Function[Calico, Dog] <: Function[Cat, Dog]? Нет, утверждения, которые верны для суперкласса, не соответствуют подклассу, а именно утверждению (1). Вы не можете передать ни одного кота в функцию, которая принимает только кошек Calico.

Но имеет ли смысл Function[Animal, Dog] <: Function[Cat, Dog] смысл? Да, все утверждения о суперклассе верны для подкласса. Я все еще могу пройти в любом Cat - на самом деле я могу сделать еще больше, я могу передать любое Animal - и я могу вызвать все методы Dog на возвращаемое значение.

Итак, A <: B означает Function[B, _] <: Function[A, _]

Теперь, имеет ли смысл Function[Cat, Husky] <: Function[Cat, Dog]? Да, все утверждения о суперклассе верны для подкласса; Я все еще могу пройти в Cat, и я все еще могу вызвать все методы Dog для возвращаемого значения - на самом деле я могу сделать еще больше, я могу вызвать все методы Husky на возвращаемое значение.

Но имеет ли смысл Function[Cat, Animal] <: Function[Cat, Dog] смысл? Нет, утверждения, которые верны для суперкласса, не соответствуют подклассу, а именно утверждению (2). Я не могу вызывать все методы, доступные на Dog для возвращаемого значения, только те, которые доступны для Animal.

Итак, с помощью Function[Animal, Husky] я могу делать все, что я могу сделать с помощью Function[Cat, Dog]: я могу пройти в любом Cat, и я могу вызвать все методы Dog для возвращаемого значения. И я могу сделать еще больше: я могу передать другим животным, и я могу назвать методы, доступные на Husky, которые недоступны на собаке. Это имеет смысл: Function[Animal, Husky] <: Function[Cat, Dog]. Параметр первого типа может быть заменен суперклассом, второй - подклассом.

Ответ 3

Здесь есть две отдельные идеи. Один использует подтипирование, чтобы позволить передавать более конкретные аргументы функции (называемой подпунктом). Другой способ - проверить подтипирование самих функций.

Для проверки типов аргументов функции вам нужно только проверить, являются ли указанные аргументы подтипами объявленных типов аргументов. Результат также должен быть подтипом объявленного типа. Здесь вы фактически проверяете подтипирование.

Контр/со-дисперсия параметров и результата зависит только тогда, когда вы хотите проверить, является ли данный тип функции подтипом другого типа функции. Поэтому, если параметр имеет тип Function[A1, ... ,B], тогда аргумент должен быть типом функции Function[C1, ..., D], где A1 <: C1 ... и D <: B.

Это рассуждение не относится к Scala и относится к другим статически типизированным языкам с подтипированием.

Ответ 4

Ковариант означает преобразование из более широкого (супер) в более узкое (sub). Например, у нас есть два класса: один - это животное (супер), а другое - кошка, тогда ковариант, мы можем превратить животное в кошку.

Контравариант - это просто противоположность коварианту, что означает, что кошка животного.

Инвариант означает, что он не может преобразовать.