具有Lambda表达式的复杂Scala类型推断

时间:2011-10-05 01:27:22

标签: scala type-inference lambda

我正在为一个我在Scala中构建的实验库工作的DSL,我遇到了Scala类型推断的一些棘手的特性,因为它与lambda表达式参数有关,似乎没有在这本书Programming In Scala

在我的库中,我有一个特性Effect [-T],用于表示可以应用于T类对象的临时修饰符。我有一个对象myEffects,它有一个名为+ =的方法接受Effect [PlayerCharacter]类型的参数。最后,我有一个泛型方法,当[T]时,用于通过接受条件表达式和另一个效果作为参数来构造条件效果。签名如下:

def when[T](condition : T => Boolean) (effect : Effect[T]) : Effect[T]

当我使用上述签名调用“when”方法时,将其结果传递给+ =方法,它无法推断lambda表达式的参数类型。

myEffects += when(_.hpLow()) (applyModifierEffect) //<-- Compiler error

如果我将“when”的参数合并到一个参数列表中,Scala就可以很好地推断出lambda表达式的类型。

def when[T](condition : T => Boolean, effect : Effect[T]) : Effect[T]

/* Snip */

myEffects += when(_.hpLow(), applyModifierEffect) //This works fine!

如果我完全删除第二个参数,它也可以。

def when[T](condition : T => Boolean) : Effect[T]

/* Snip */

myEffects += when(_.hpLow()) //This works too!

但是,出于审美原因,我真的希望将参数作为单独的参数列表传递给“when”方法。

我对Programming in Scala第16.10节的理解是,编译器首先查看方法类型是否已知,如果是,则使用它来推断它的参数的预期类型。在这种情况下,最外层的方法调用是+ =,它接受Effect [PlayerCharacter]类型的参数。由于when [T]的返回类型是Effect [T],并且传递结果的方法需要Effect [PlayerCharacter]类型的参数,它可以推断T是PlayerCharacter,因此lambda的类型表达式作为第一个参数传递给“when”是PlayerCharacter =&gt;布尔。这似乎是在一个参数列表中提供参数时它是如何工作的,那么为什么将参数分成两个参数列表呢?

2 个答案:

答案 0 :(得分:2)

我自己对Scala比较陌生,而且我没有很多关于类型推理如何工作的详细技术知识。所以最好带上一粒盐。

我认为不同之处在于编译器无法证明两个Tcondition : T => Booleaneffect : Effect[T]中是相同的,在双参数列表版本中

我相信当你有多个参数列表时(因为Scala将其视为定义一个返回消耗下一个参数列表的函数的方法),编译器一次处理一个参数列表,而不是像单个参数一样处理参数列表列表版本。

所以在这种情况下:

def when[T](condition : T => Boolean, effect : Effect[T]) : Effect[T]

/* Snip */

myEffects += when(_.hpLow(), applyModifierEffect) //This works fine!

applyModifierEffect的类型和myEffects +=所需的参数类型可以帮助约束_.hpLow()的参数类型;所有T必须为PlayerCharacter。但是在下面:

myEffects += when(_.hpLow()) (applyModifierEffect)

编译器必须独立地确定when(_.hpLow())的类型,以便它可以检查将它应用于applyModifierEffect是否有效。并且_.hpLow()本身没有为编译器提供足够的信息来推断它是when[PlayerCharacter](_.hpLow()),因此它不知道返回类型是Effect[PlayerCharacter] => Effect[PlayerCharacter]类型的函数,因此它不知道在该上下文中应用该函数是有效的。我的猜测是类型推断只是没有连接点,并发现只有一种类型可以避免类型错误。

至于另一种有效的案例:

def when[T](condition : T => Boolean) : Effect[T]

/* Snip */

myEffects += when(_.hpLow()) //This works too!

这里when的返回类型及其参数类型更直接连接,而不通过参数类型和currying创建的额外函数的返回类型。由于myEffects +=需要Effect[PlayerCharacter],因此T必须为PlayerCharacter,其中hpLow方法,并且编译器已完成。

希望有更多知识渊博的人可以纠正我的细节,或者指出我是否完全咆哮错误的树!

答案 1 :(得分:2)

我有点困惑,因为在我看来,你所说的任何版本都不应该,但实际上我不能让它们中的任何一个工作。

类型推断从一个参数列表(非参数)到下一个参数列表从左到右工作。典型的例子是集合中的方法foldLeft(比如Seq [A])

def foldLeft[B] (z: B)(op: (B, A) => B): B

z的类型将使B成为已知,因此可以在不指定B的情况下编写op(也不是从start,类型参数中知道的A)。 如果例程写成

def foldLeft[B](z: B, op: (B,A) => B): B

def foldLeft[B](op: (B,A) => B)(z: B): B

它不起作用,必须确保操作类型清晰,或者在调用B时传递foldLeft明确。

在你的情况下,我认为最令人愉快的读取方法是使when成为Effect的方法,(或者让它看起来像是一个带有隐式转换的方法)然后你会写< / p>

Effects += applyModifierEffect when (_.hpLow())

正如您提到效果是逆变的,when方法不允许Effect签名(因为T => Boolean,函数在其第一个类型参数中是逆变的,并且条件作为一个参数出现,所以在逆变位置,两个逆变量产生一个协变量,但它仍然可以用一个隐含的

object Effect {
  implicit def withWhen[T](e: Effect[T]) 
    = new {def when(condition: T => Boolean) = ...}
}