作者:sysv | 来源:互联网 | 2023-06-03 12:06
考虑协变类型参数 Acase class Foo[+A](a: A): def bar(a: A) = a // error: covariant type A occurs
考虑协变类型参数 A
case class Foo[+A](a: A):
def bar(a: A) = a // error: covariant type A occurs in contravariant position
def zar(f: A => Int) = f(a) // ok
|
This is contravariant position. Why is it ok?
Foo(41).zar(_ + 1) // : Int = 42
为什么zar
当它出现在 中的逆变位置时它被接受为参数A => Int
?
回答
遵循@sarveshseri 的想法,我将提供一个直观的解释,而不是正式的解释。既是因为我不知道正式的细节,也是因为我希望这对读者更有帮助。
首先是一些免责声明:
- 我可能有错别字或一些错误,如果您注意到,请编辑答案。
- 如上所述,我要描述的心智模型是一个近似值,在编译时和运行时实际发生的情况会有所不同。
- 在这个答案中,我将提到可互换的类型和类。这是错误的,我自己已经在其他答案中指出了这一点。在这种情况下,是为了简化这种心理模型;但我建议在点击差异后,将类型和类的区别混合到该模型中:https : //typelevel.org/blog/2017/02/13/more-types-than-classes.html
- 连接到上一点,我将在我的示例中使用具体/简单类型。幸运的是,由于类型擦除和参数化,我将解释的内容适用于类型构造函数和其他复杂类型。
- 我还将交替使用方法和函数,这也是错误的:https : //docs.scala-lang.org/tutorials/FAQ/index.html#whats-the-difference-between-methods-and-functions
现在让我们开始吧。首先让我们假设,如果一个方法接受 aFoo
那么它只能接受 type 的值,Foo
而不能接受其他的值,期间。
然后让我们假设子类型实际上是“转换”值的“隐式”函数。所以,B <: A
只是B => A
并让我们想象一下,这样的“演员”是一个伪装,该值实际上是同一个,但看到不同的(这基本上是里氏原理)。
因此,当您尝试将 a 传递B
给需要 an 的方法时,A
编译器将插入此隐式强制转换。这样在运行时,该方法接收的值看起来像是 type 之一A
而不是 type 之一B
;但值仍然是类型的B
(实际上我们在这里讨论的是类而不是类型,但我希望你能明白)。
那么让我们看看你的协变类的方法bar
和baz
方法会发生什么,Foo
因为Foo[Dog] <: Foo[Animal]
我可以将前者转换为后者,那么鉴于Cat <: Animal
我也可以将前者转换为后者。最后,我可以通过这个Cat
伪装成Animal
到bar
的方法Foo[Dog]
伪装成Foo[Animal]
,但随后在运行时,我们会传递Cat
给需要一个方法Dog
kataplum!当然,除非这种方法总是为这种情况做好准备。
这就是为什么bar
必须像[B >: A](b: B)
. 在这里,我们是说我们可以接受任何B
编译器可以为其生成隐式强制转换函数A => B
(与以前相反,由于Any
此类类型和此类函数始终是可能的)。然后 的实现bar
应该能够为该新类型工作B
并在需要时使用 cast 函数。
这意味着前面的例子不会在运行时爆炸,因为我们可以Cat
直接通过而不通过间接伪装;这工作,因为编译器总是能够推断B
应该是Animal
(的LUBCat
和Dog
),所以它会投的Cat
作为Animal
并将其传递给bar
以及 cast 函数Dog => Animal
请注意,A => B
cast 函数的存在意味着编译器也可以创建该F[A] => F[B]
函数,如果它F
是协变的。
现在让我们看看会发生什么baz
。同样,我们将 a 转换Foo[Dog]
为 a Foo[Animal]
,然后我们将尝试baz
使用一个Animal => Int
应该在运行时工作的函数进行调用,因为我们甚至根本不需要伪装,我们可以直接将这样的函数传递给Foo[Dog]
因为(Animal => Int) <: (Dog => Int)
this 因为函数在他们的投入。
但这将如何运作?很简单,直觉告诉我,如果我能够处理/消费/接收/使用任何,Animal
那么我应该能够处理任何,Dog
因为那些是Animals
,对吧?让我们看看它如何与我们的心智模型一起工作。
我有baz(Dog => Int)
并且我有f(Animal => Int)
编译器可以做的就是创建一个新函数g(Dog => Int) = cast(Dog => Animal) andThen f(Animal => Int)
并使用它g
。
希望这会有所帮助,请随时留下任何问题。