想象一下这段代码:
class Foo {
println("in Foo")
def foo(a: Int) = a + 1
}
现在,如果我们调用:
new Foo().foo _
将按预期创建类Foo的实例:
in Foo
res0: (Int) => Int = <function1>
但是,如果我们调用它:
new Foo().foo(_)
Foo的构造函数不会被调用:
res1: (Int) => Int = <function1>
如果我们再说:
res1(7)
当Foo被实例化时:
in Foo
res2: Int = 8
为什么Eta扩展与部分函数应用程序在类实例化方面有所不同?
答案 0 :(得分:2)
我不完全确定,但我认为存在差异的原因是Scala不是纯函数式编程语言 - 它允许副作用:
scala> class Adder { var i = 0; def foo(a:Int)={i+=1;println(i);a+1} }
defined class Adder
scala> val curriedFunction = new Adder().foo _
curriedFunction: (Int) => Int = <function1>
scala> val anonymousFunction = new Adder().foo(_)
anonymousFunction: (Int) => Int = <function1>
scala> curriedFunction(5)
1
res11: Int = 6
scala> curriedFunction(5)
2
res12: Int = 6
scala> anonymousFunction(5)
1
res13: Int = 6
scala> anonymousFunction(5)
1
res14: Int = 6
匿名函数被视为:
val anonymousFunction = x => new Adder().foo(x)
而curry函数被视为:
val curriedFunction = {
val context = new Adder()
(a:Int) => context foo a
}
curried函数符合传统方式curried函数在函数式语言中处理:curried函数是一个函数,它应用于某些数据并评估这个部分应用的函数。换句话说:基于某些数据创建了一个上下文,该上下文被存储并可以在以后使用。这正是curriedFunction
正在做的事情。因为Scala允许可变状态,所以可以改变上下文 - 这可能导致意外行为,如问题所示。
像Haskell这样的纯函数式语言没有这个问题,因为它们不允许这样的副作用。在Scala中,必须自己确保由curried函数创建的上下文非常纯粹。如果不是这种情况并且需要纯粹curry函数的行为,则必须使用匿名函数,因为它们不存储上下文(如果上下文的创建是昂贵的并且必须经常进行,则可能会有问题)。
答案 1 :(得分:2)
男孩,这是一个微妙的,但据我所知,它完全跟随Scala spec。我将引用规范的2.9版本。
对于你的第一个例子:正如你正确地说的那样,你通过一个特殊的方法值(§6.7)看到eta扩展:
The expression e _ is well-formed if e is of method type or if e is a call-by-name parameter. If e is a method with parameters, e _ represents e converted to a function type by eta expansion.
eta扩展算法在§6.26.5中给出,您可以按照以下内容替换表达式new Foo().x1 _
:
{
val x1 = new Foo();
(y1: Int) => x1.(y1);
}
这意味着当使用eta扩展时,所有子表达式都在转换发生的位置进行评估(如果我已正确理解短语“maximal sub-expression”的含义)和最终表达式是创建一个匿名函数。
在你的第二个例子中,那些额外的括号表示编译器将查看§6.23(特别是“匿名函数的占位符语法”)并直接创建匿名函数。
An expression (of syntactic category Expr) may contain embedded underscore symbols _ at places where identifiers are legal. Such an expression represents an anonymous function where subsequent occurrences of underscores denote successive parameters.
在这种情况下,并按照该部分中的算法,您的表达式最终成为:
(x1: Int) => new Foo().foo(x1)
差异是微妙的,正如@Antoras所解释的那样,只有在存在副作用代码的情况下才会显示。
请注意,对于涉及按名称调用代码块的情况,正在进行错误修正(例如,请参阅this question,this bug和this bug)。
Postscript :在这两种情况下,匿名函数(x1:Int) => toto
都会扩展为
new scala.Function1[Int, Int] {
def apply(x1: Int): Int = toto
}
答案 2 :(得分:1)
因为它扩展到
(x: Int) => new Foo().foo(x)
因此,您只在调用该函数时创建Foo
的实例。
第一个实例化Foo的原因是因为它扩展到了
private[this] val c: (Int) => Int = {
<synthetic> val eta$0$1: Foo = new Foo();
((a: Int) => eta$0$1.foo(a))
};
<stable> <accessor> def c: (Int) => Int = Foo.this.c;
一旦定义了c,Foo
就会被实例化。