Концепция Scala Variance, почему она не компилируется

Я новичок в Scala, поэтому, пожалуйста, не голосуйте против.

class MyClass extends AnyRef
class MySubClass extends MyClass

val af0: (Seq[_]) => Boolean = (s) ⇒ { s eq null }

val f4: (MySubClass) => Boolean = (s) => { s eq null }

val af2: (List[_]) => Boolean = af0 //(Line 1)
val f7: MyClass => Boolean = f4 //(Line 2)

Почему строка (1) компилируется, а строка (2) нет? Для меня они оба одинаковы, поскольку Sequence является подтипом List. Как заставить это работать? как в случае линии 1?

https://docs.scala-lang.org/tutorials/FAQ/collections.html [Список иерархии объектов]


person AZ_    schedule 05.07.2018    source источник
comment
Возможный дубликат Scala Co и Contravariance   -  person Suma    schedule 05.07.2018


Ответы (2)


То, что вы видите, называется контравариантностью. Из-за этого параметры функции должны быть контравариантными:

class HisSubClass extends MyClass

val his = new HisSubClass 
f7(his) // his is accepted as MyClass

Теперь f4 будет вызываться с чем-то, что не является MySubClass, что будет ошибкой.

Случай Seq / List работает, потому что все наоборот. List является подклассом Seq.

val af2: (List[_]) => Boolean = af0

как

val aff0: (MyClass) => Boolean = (s) ⇒ { s eq null }
val aff2: (MySubClass) => Boolean = aff0

Обещание (контракт)

Что мне очень помогло понять разницу между параметром и возвращаемым значением, так это то, что я думал об объявлениях типов как об обещаниях (или контрактах). Возвращаемое значение является ковариантным, поскольку вы пообещали, что возвращаемое значение будет иметь тип MyClass, и, предоставив MySubClass в подклассе, вы по-прежнему выполняете свое обещание. Обещание, что вы примете параметр типа MyClass, а затем попытка объявить член подкласса, принимающий только MySubClass, означает попытку сузить обещание, чего вы не можете (подкласс должен полностью реализовать родительский класс).

В вашем примере в f4 вы обещали, что функция будет передана MySubClass в качестве параметра. Когда вы пытаетесь присвоить это f7, вы пытаетесь нарушить это обещание, поскольку вы можете передать любое MyClass в f4, вызвав его через f7.

person Suma    schedule 05.07.2018

Поскольку вы пытаетесь присвоить значению типа Function1[MyClass, Boolean] значение типа Function1[MyClass, Boolean], но первый параметр типа Function1 является контравариантным, см. Документация по API:

trait Function1[-T1, +R] extends AnyRef

Но это позволяет вам сделать это:

val f7: MyClass => Boolean = s => s eq null 
val f44: (MySubClass) => Boolean = f7

Вы можете найти объяснение отклонений здесь.

person Duelist    schedule 05.07.2018
comment
тогда почему строка (2) согласно вопросу компилируется? - person AZ_; 05.07.2018
comment
Потому что Seq является надтипом List, а не подтипом. Если вы поменяете местами Seq и List в своем примере, то это будет эквивалентно тому, что вы делаете с MyClass, и эта строка также не будет компилироваться. - person Astrid; 05.07.2018