6

想象一下这段代码:

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

4

3 回答 3

2

男孩,这是一个微妙的,但据我所知,它完全遵循Scala 规范。我将引用规范的 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 扩展时,所有子表达式都在转换发生的点进行评估(如果我正确理解了短语“最大子表达式”的含义)并且最终表达式是创建一个匿名函数。

在您的第二个示例中,那些额外的括号意味着编译器将查看 §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 很好解释的那样,实际上只有在存在副作用代码的情况下才会显示出来。

请注意,对于涉及按名称调用代码块的案例(例如,请参阅此问题此错误此错误),正在进行错误修复。

后记:在这两种情况下,匿名函数(x1:Int) => toto都被扩展为

new scala.Function1[Int, Int] {
  def apply(x1: Int): Int = toto
}
于 2012-05-28T22:14:08.283 回答
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)

而咖喱函数被视为:

val curriedFunction = {
  val context = new Adder()
  (a:Int) => context foo a
}

柯里化函数符合用函数式语言处理柯里化函数的传统方式:柯里化函数是应用于某些数据并评估该部分应用函数的函数。换句话说:根据一些数据创建一个上下文,该上下文被存储并可以在以后使用。这正是curriedFunction正在做的事情。因为 Scala 允许可变状态,所以可以更改上下文 - 如问题中所见,这一事实可能导致意外行为。

像 Haskell 这样的纯函数式语言没有这个问题,因为它们不允许这样的副作用。在 Scala 中,必须自己确保由 curried 函数创建的上下文是真正纯的。如果不是这种情况,并且需要纯 curried 函数的行为,则必须使用匿名函数,因为它们不存储上下文(如果上下文的创建代价高昂并且必须经常进行,这可能会出现问题)。

于 2012-05-26T22:51:21.980 回答
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;

并且Foo一旦定义 c 就会在此处实例化。

于 2012-05-26T22:50:27.707 回答