任何人都可以就Scala编译器如何将名称参数=> T
和Function0
参数() => T
转换为另一个来提供明确的答案吗?我知道它们不一样,但差异非常微妙,因为它们可以在许多情况下互换使用。
示例:如果我定义
def someFunction: Int = 2
def f(x: => Int): Unit = println(x)
然后我可以成功拨打
f(2)
f(someFunction)
() => Int
如何成为=> Int
的可接受替代品?
更一般地说,() => T
是一个普遍接受的名称=> T
参数的替代品吗?
此外,如果我在以下推理中出错,请纠正我:=> T
永远不是() => T
的可接受替代品,因为第一个是值类型(T
),另一种是功能类型。也就是说,如果我有def f(x: () => Int)
,我就永远无法传递Int
或懒惰Int
(因为没有懒惰类型,所以无论如何都没有意义)
答案 0 :(得分:6)
好的,这里有完整的故障。
def value: Int = ???
def method(): Int = ???
def f1(f: () => Int) = ???
def f2(f: => Int) = ???
f1(value) // fails
f1(method) // works
f2(value) // works
f2(method) // works with a warning "empty-paren method accessed as parameterless"
F1(值)
这个失败是因为f1
期待一个单位=> Int函数,但是给出了Int值。
F1(方法)
这个有效,因为f1
期待一个函数,并且给出了一个方法。这里有区别:方法不是Scala中的值;它不能单独存在,而是它在(类,特征,对象等)中定义的上下文的属性。功能是一个价值;它可以保存在一个集合中,作为另一个函数中的参数,从函数等返回。当编译器期望一个函数并给出一个方法时,它执行 eta扩展。给定一个例如f: (a: Int, b: Int) => Int
,eta扩展是在保留签名的同时创建另一层的过程,因此它变为(a: Int, b: Int) => f(a, b)
。这种技术很有用,因为我们可以将方法转化为函数。给定一些方法def f(a: Int): Int = ???
,我们可以执行eta-expansion来创建Int =>类型的函数。 Int out of it:(a: Int) => f(a)
。如果您有兴趣,我前段时间就写了一篇blog post。
F2(值)
毫无意外地工作,但请注意每次在函数体中使用时都会访问传递的值。
F2(方法)
工作,但警告我们正在调用一个用空括号定义的方法,不使用括号。好的做法是使用没有括号的方法(例如f
),当它们只是表示一个值时,但是每次访问时都会重新计算一个值,例如:numberOfUpvotes
。 f()
,并且当执行某种副作用时使用带有空括号的方法(例如createSnapshot()
),因此该方法不是幂等的,例如def f(): Int = ???
val a = f // no function context; a is a string
val b: () => Int = f // b is a function Unit => Int
val c = f2 _ // c is a function Unit => Int
(同样,这不应出现在纯函数代码中)。
建议:不要以什么为替代,以此来阻碍你的思想。不要使用替代品。如果某些东西需要一个功能,请提供一个功能。如果需要值,请提供值。如果定义了没有parens的方法,则在没有parens的情况下调用它。如果它有parens,请用parens调用它。
如果你需要从一个方法转到另一个函数并且编译器期望一个函数,那么eta-expansion将自动发生。如果它不期望某个功能,您需要手动完成。
{{1}}
最后一种情况是部分应用的功能。我觉得我现在走得太宽了所以我会在这里停下来。我希望这会有所帮助。