我正在为一个我在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;布尔。这似乎是在一个参数列表中提供参数时它是如何工作的,那么为什么将参数分成两个参数列表呢?
答案 0 :(得分:2)
我自己对Scala比较陌生,而且我没有很多关于类型推理如何工作的详细技术知识。所以最好带上一粒盐。
我认为不同之处在于编译器无法证明两个T
在condition : T => Boolean
和effect : 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) = ...}
}