在函数参数A=>B的逆变位置接受协变类型A

考虑协变类型参数 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 的想法,我将提供一个直观的解释,而不是正式的解释。既是因为我不知道正式的细节,也是因为我希望这对读者更有帮助。

首先是一些免责声明:

  1. 我可能有错别字或一些错误,如果您注意到,请编辑答案。
  2. 如上所述,我要描述的心智模型是一个近似值,在编译时和运行时实际发生的情况会有所不同。
  3. 在这个答案中,我将提到可互换的类型和类。这是错误的,我自己已经在其他答案中指出了这一点。在这种情况下,是为了简化这种心理模型;但我建议在点击差异后,将类型和类的区别混合到该模型中:https : //typelevel.org/blog/2017/02/13/more-types-than-classes.html
  4. 连接到上一点,我将在我的示例中使用具体/简单类型。幸运的是,由于类型擦除和参数化,我将解释的内容适用于类型构造函数和其他复杂类型。
  5. 我还将交替使用方法和函数,这也是错误的: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 (实际上我们在这里讨论的是而不是类型,但我希望你能明白)

那么让我们看看你的协变类的方法barbaz方法会发生什么,Foo
因为Foo[Dog] <: Foo[Animal]我可以将前者转换为后者,那么鉴于Cat <: Animal我也可以将前者转换为后者。最后,我可以通过这个Cat伪装成Animalbar的方法Foo[Dog]伪装成Foo[Animal],但随后在运行时,我们会传递Cat给需要一个方法Dogkataplum!当然,除非这种方法总是为这种情况做好准备。

这就是为什么bar必须像[B >: A](b: B). 在这里,我们是说我们可以接受任何B编译器可以为其生成隐式强制转换函数A => B (与以前相反,由于Any此类类型和此类函数始终是可能的)。然后 的实现bar应该能够为该新类型工作B并在需要时使用 cast 函数。
这意味着前面的例子不会在运行时爆炸,因为我们可以Cat直接通过而不通过间接伪装;这工作,因为编译器总是能够推断B应该是Animal (的LUBCatDog),所以它会投的Cat作为Animal并将其传递给bar以及 cast 函数Dog => Animal

请注意,A => Bcast 函数的存在意味着编译器也可以创建该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

希望这会有所帮助,请随时留下任何问题。


以上是在函数参数A=&gt;B的逆变位置接受协变类型A的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>