在下面的代码示例中,我不明白为什么函数fun可以作为参数传递给方法addAction
。方法fun
的类型为Unit
,而方法addAction
需要类型为() => Unit
的函数。
如果fun
的类型为() => Unit
,那么当我尝试将fun
添加到Unit
时,为什么编译器会抱怨fun
的类型为actions = fun :: actions
行动清单:package myscala
object MyScala {
def fun() { println("fun1 executed.") }
def addAction(a: () => Unit) {
actions = a :: actions
}
var actions: List[() => Unit] = List()
def main(args: Array[String]) {
// the following line would produce a compiler error (found: Unit, required: () => Unit), it's OK
// actions = fun :: actions
actions = (() => fun) :: actions // OK
// I would expect the same compiler error here (found: Unit, required: () => Unit), but it's OK why?
addAction(fun)
actions.foreach(_()) // prints twice "fun1 executed"
}
}
?
{{1}}
答案 0 :(得分:8)
以此作为介绍性示例:
def fun() { println("fun1 executed.") }
val a1 = fun
val a2: () => Unit = fun
两行编译和(由于类型推断)它们看起来相同。但是,a1
的类型为Unit
,而a2
的类型为() => Unit
...这怎么可能?
由于您未明确提供a1
类型,因此编译器会将fun
解释为类型为fun
的方法Unit
,因此类型为a1
与fun
的类型相同。这也意味着该行将打印 fun1执行。
但是,a2
已明确声明() => Unit
的类型。编译器在这里帮助你,并且它理解由于上下文需要类型() => Unit
的函数,并且你提供了一个匹配这种类型的方法,它不应该调用该方法,而是将它视为第一类函数!
您并非注定要明确指定a1
的类型。话说:
val a1 = fun _
您现在了解问题所在吗?
答案 1 :(得分:5)
您需要在第一种情况下编写fun _
以避免调用该方法并执行eta-expansion。
这将有效:
actions = (fun _) :: actions
如果您不这样做,则会评估fun
。
有关详细信息,请参阅Scala Language Reference的第6.7节(方法值)。
至于为什么fun
未在第二种情况下进行评估,这是因为类型推断可以清楚地得出addAction
期望函数的结论。顺便说一下,fun
的类型在技术上是()Unit
,而不是Unit
,即方法类型,而不是值类型。有关详细信息,请参阅reference中的第3.3.1节。
答案 2 :(得分:3)
方法和功能之间存在差异。在您的情况下,actions
是一个功能列表。当编译器知道需要一个函数时(如addAction
的情况),它可以自动将方法fun
转换为函数。现在::
也是一种方法,因此编译器也知道它将函数作为参数。但问题是右关联运算符::
的句法糖。如果你把它称为方法:actions.::(fun)
它将编译(虽然我目前无法测试它)。在编写fun :: actions
时,编译器认为fun
是一个表达式,因此对其进行求值,因为它“返回”Unit
,就会出现编译错误。
修改强>
由于我现在有可能测试我的假设(这是错误的),这里有你的选择:
// Usual syntax
actions.::[() => Unit](fun)
actions.::(fun: () => Unit)
actions.::(fun _)
// Operator syntax
(fun: () => Unit) :: actions
(fun _) :: actions