2011-10-05 30 views
4

我正在为我在Scala中构建的实验库开发DSL,并且遇到了一些令人烦恼的Scala类型推理特性,因为它涉及到lambda表达式参数似乎没有在书Programming In Scala复杂的Scala类型推理w/Lambda表达式

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

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

当我所说的“时”的方法与上述签名,传递它的结果到+ =方法,它是无法推断的参数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! 

它也适用,如果我完全删除第二个参数。然而,出于美学的原因,我真的希望将参数作为单独的参数列表传递给“when”方法。

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

+1

http://pchiusano.blogspot.com/2011/05/making-most-of-scalas-extremely-limited.html – retronym

回答

2

我对Scala本人比较陌生,对于类型推断是如何工作的,我没有很多详细的技术知识。所以最好拿一点盐来。

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

我相信当你有多个参数列表(因为Scala认为这是定义一个方法,它返回一个函数使用下一个参数列表),编译器一次处理一个参数列表,而不是全部在一起参数列表版本。

因此,在这种情况下:

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

/* Snip */ 

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

类型的applyModifierEffectmyEffects +=所需要的参数类型可以帮助限制的_.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及其参数类型的返回类型更直接连接,无需经过参数类型和产生的额外函数的返回类型会通过咖啡。由于myEffects +=需要Effect[PlayerCharacter],所以T必须是PlayerCharacter,它有一个hpLow方法,并完成编译器。

希望有人更有知识可以纠正我的细节,或指出如果我吠叫完全错误的树!

+0

你可能是对的。我真的不认为具有多个参数列表的函数是真正被curry的,因为当您调用它时必须使用下划线代替任何缺少的参数列表,而如果您有一个明确curried的函数,比如“def curried(x:Int)=(y:Int)=> x + y“,则不必使用下划线。但是,也许我对斯卡拉如何对待这些类型的功能的心理模型需要一些修改。 – Nimrand

+0

@Nimrand啊,我不知道你必须增加下划线才能做到这一点。我知道,通过多个参数列表,编译器将完全基于提供给第一个列表的参数修复类型参数(有时这是有帮助的,并避免需要显式提示以避免含糊不清,有时会导致类型错误,可以通过手动解决类型注释)。即使他们没有得到与显式使用函数相同的处理方式,多重参数列表也可以通过类型推理器以不同方式处理。 – Ben

2

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

类型推断从左到右从一个参数列表(不参数)到下一个参数列表。典型的例子是在集合方法foldLeft(说SEQ [A])

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

的Z型意愿使得乙知的,因此运算可以被写入,而无需指定B(也不A,其是从一开始就知道,类型参数这个的)。 如果程序被写成

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

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

它不会工作,一个人必须要确保运算类型是明确的,或致电foldLeft时通过B明确地。

在你的情况,我觉得最愉快的阅读相当于是使whenEffect,方法(或使它看起来像一个带有隐式转换),那么你会写

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

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

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

如果'foldLeft'被写为'def foldLeft [B](z:B,op:(B,A)=> B):B',它不会工作,因为为'z'传递一个值会绑定'B'到它的类型?或者参数列表中的所有东西的类型必须独立于同一列表中的其他东西才可以推出? – Ben

+0

第二个。只有从以前的参数列表中获得的信息才可用。 –

+0

如果使用其参数推断的类型参数,则仅使用第一个参数列表是正确的。我相信它能够推断出“when”的类型参数的原因是它的结果被传递给期望类型为Effect [PlayerCharacter]的参数的方法,所以“when”的预期类型为Effect [ PlayerCharacter],因此T的类型必须是PlayerCharacter(或者更宽的类型)。如果我只是自己打电话“什么时候”,我没有给出任何例子。 – Nimrand