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が純粋に関数型プログラミング言語ではないためだと思います。副作用が発生する可能性があります。

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では、カリー化された関数によって作成されたコンテキストが本当に純粋であることを自分で確認する必要があります。これが当てはまらず、純粋にカレーされた関数の動作が要求される場合は、無名関数を使用する必要があります。無名関数はコンテキストを格納しないためです(コンテキストの作成に費用がかかり、頻繁に実行する必要がある場合は問題になる可能性があります)。

于 2012-05-26T22:51:21.980 に答える
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展開が使用されている場合、変換が行われる時点ですべてのサブ式が評価され(「最大サブ式」というフレーズの意味を正しく理解している場合)、最終的な式が作成であることを意味します。匿名関数の。

2番目の例では、これらの余分な括弧は、コンパイラーが§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 に答える
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 に答える