Scala:高阶函数的参数类型中的多态性

时间:2015-07-19 10:03:58

标签: scala functional-programming

我偶然发现了Scala中的一些让我感到困惑的事情。似乎高阶函数的参数类型不是多态的。以下是我的意思:

class A

class B extends A

def call(f: A => A):A = f(new A)

def A2A(a: A): A = new A
def A2B(a: A): B = new B
def B2A(b: B): A = new A
def B2B(b: B): B = new B

call(A2A)  // Works
call(A2B)  // Works
call(B2A)  // Error!
call(B2B)  // Error!

两个调用的错误消息相同:

<console>:12: error: type mismatch;
found   : B => A
required: A => A
          call(new B, B2A)

<console>:12: error: type mismatch;
found   : B => B
required: A => A
          call(new B, B2B)

A2A和A2B工作,因此它不是导致问题的返回类型,它必须是参数类型。这与类型擦除有关吗?

我不认为我正在尝试做的事情有任何逻辑上的错误(可能有吗?)所以我怀疑这是类型系统的怪癖?

修改

感谢您的回答!事实证明,这毕竟是我错误的逻辑。问题在于call的定义。它期望一个函数以A(或子类型)作为参数。

B2AB2B都采用B(或子类型),因此与call的定义不兼容。

我通过引入A从被调用的ALike扩展的特征并更改了call的定义来解决了这个问题,因此它将ALike的任何参数作为超类型。

trait ALike
class A extends ALike
class B extends A

def call[T <: ALike](t: T, f: T => A): A = f(t)

def A2A(a: A): A = a
def A2B(a: A): B = new B
def B2A(b: B): A = new A
def B2B(b: B): B = b

call(new A, A2A)
call(new A, A2B)
call(new B, B2A)
call(new B, B2B)

2 个答案:

答案 0 :(得分:3)

call(f)期望一个可以接受A(或任何子类型)并返回A(或任何子类型)的函数。原因1和2的作用是你的函数定义可以取A或A的任何子类型,并且在1中,返回A(满足条件为A)或2,返回B(满足作为A的子类型的条件) )。

后两者不工作的原因是你传递了一个只能拿B并返回A或B的功能。要想知道为什么这是一个问题,假设你&#39;已经得到C类扩展A. B2A无法处理C,这意味着,根据定义,调用(f)无法处理输入C,尽管是子类型一个。由于调用的定义,这是一个编译错误。

答案 1 :(得分:3)

Scala Function的参数是逆变的。在A => A的情况下,这意味着您可以创建一个接受超级类型A但不接受像B这样的子类型的函数的函数。

您可以定义一个需要Any的函数:

def Any2A(any: Any) = new A 
def Any2B(any: Any) = new B

call(Any2A) // A = A@b5bddc9
call(Any2B) // A = B@62c1c65f
  

功能1 [-T1,+ R]

Function1的参数是逆变的,结果是协变的,这意味着在A => A类型的函数中:

  • B为结果类型(协变)时,您可以返回AA2B
  • B是参数的类型(AB2A)时,您无法接受B2B,但您可以传递超级类型{{ 1}}喜欢AAnyAny2A)。

关于Any2B的逆变(和协方差)的一些解释可以在Programming in Scala, 1ed

中找到