2012-05-25 35 views
6

想象一下這樣的代碼:爲什麼部分應用的函數延遲Scala中的類實例化?

class Foo { 
    println("in Foo") 

    def foo(a: Int) = a + 1 
} 

現在,如果我們調用:Foo類的

new Foo().foo _ 

實例將生成,如期望的那樣

in Foo 
res0: (Int) => Int = <function1> 

但是,如果我們調用此:

new Foo().foo(_) 

Foo的構造函數將不會被調用:

res1: (Int) => Int = <function1> 

然後如果我們說:

res1(7) 

時富被實例化,即:

in Foo 
res2: Int = 8 

爲什麼埃塔擴張與局部功能應用在類實例化中有所作爲?

+0

這個問題有了答案,但我看不到它了。有人刪除它嗎? –

回答

2

男孩,這是一個微妙的,但據我所知它完全遵循Scala spec。我將從規範2.9版引用。

對於您的第一個例子:

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.7)的一個特例看到埃塔膨脹6.26.5它可以跟隨,得到下列替換爲表達式new Foo().x1 _

{ 
    val x1 = new Foo(); 
    (y1: Int) => x1.(y1); 
} 

這意味着,當正在使用ETA膨脹,所有的子表達式在點所在的C評價發生反轉(如果我已經正確理解短語「最大子表達式」的含義)並且最終表達式是創建匿名函數。

在第二個例子中,這些額外的括號意味着,編譯器會看§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 bugthis bug)。

後記:在這兩種情況下,匿名函數(x1:Int) => toto被擴大到

new scala.Function1[Int, Int] { 
    def apply(x1: Int): Int = toto 
} 
1

因爲它擴展到

(x: Int) => new Foo().foo(x) 

所以,你只當你調用該函數創建的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定義的原因。

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 
} 

curried函數符合傳統方式使用函數式語言處理curried函數:curried函數是應用於某些數據並評估爲部分應用函數的函數。換句話說:根據一些數據創建一個上下文,這個上下文被存儲並可以在以後使用。這正是curriedFunction正在做的事情。因爲Scala允許可變狀態,所以上下文可以改變 - 這可能導致問題中出現的意外行爲。

像Haskell這樣的純功能語言沒有這個問題,因爲它們不允許出現這樣的副作用。在斯卡拉,人們必須自己確保咖喱函數創建的上下文非常純粹。如果情況並非如此,並且需要純粹的咖喱函數的行爲,則必須使用匿名函數,因爲它們不存儲上下文(如果創建上下文昂貴且必須經常執行,上下文可能會有問題)。

相關問題