为什么部分应用函数推迟Scala中的类实例化?

时间:2012-05-25 22:20:59

标签: scala

想象一下这段代码:

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扩展与部分函数应用程序在类实例化方面有所不同?

3 个答案:

答案 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 questionthis bugthis 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就会被实例化。