2011-10-05 31 views
4

我正在爲我在Scala中構建的實驗庫開發DSL,並且遇到了一些令人煩惱的Scala類型推理特性,因爲它涉及到lambda表達式參數似乎沒有在書Programming In Scala複雜的Scala類型推理w/Lambda表達式

在我的庫中,我有一個特徵Effect [-T],用於表示可以應用於T類型對象的臨時修飾符。我有一個對象myEffects,它有一個名爲+ =接受類型爲Effect [PlayerCharacter]的參數。最後,我有一個泛型方法,當[T]用於通過接受條件表達式和另一個效果作爲參數來構造條件效果時。簽名是如下:

def when[T](condition : T => Boolean) (effect : Effect[T]) : Effect[T] 

當我所說的「時」的方法與上述簽名,傳遞它的結果到+ =方法,它是無法推斷的參數lambda表達式的類型。

myEffects += when(_.hpLow()) (applyModifierEffect) //<-- Compiler error 

如果我將「when」的參數組合到單個參數列表中,Scala能夠推斷出lambda表達式的類型就好了。

def when[T](condition : T => Boolean, effect : Effect[T]) : Effect[T] 

/* Snip */ 

myEffects += when(_.hpLow(), applyModifierEffect) //This works fine! 

它也適用,如果我完全刪除第二個參數。然而,出於美學的原因,我真的希望將參數作爲單獨的參數列表傳遞給「when」方法。

我從Programming in Scala的第16.10節中瞭解到,編譯器首先查看方法類型是否已知,如果是,則使用它來推斷它的參數的預期類型。在這種情況下,最外面的方法調用是+ =,它接受類型爲Effect [PlayerCharacter]的參數。由於[T]爲Effect [T]的返回類型以及要傳遞結果的方法需要Effect [PlayerCharacter]類型的參數,因此可以推斷出T是PlayerCharacter,因此lambda的類型表達式作爲第一個參數傳遞給「when」是PlayerCharacter => Boolean。這似乎是它在一個參數列表中提供參數時的工作方式,那麼爲什麼將參數分成兩個參數列表會將其分開呢?

+1

http://pchiusano.blogspot.com/2011/05/making-most-of-scalas-extremely-limited.html – retronym

回答

2

我對Scala本人比較陌生,對於類型推斷是如何工作的,我沒有很多詳細的技術知識。所以最好拿一點鹽來。

我認爲不同之處在於編譯器無法證明condition : T => Booleaneffect : Effect[T]兩個T在兩參數列表版本中是相同的。

我相信當你有多個參數列表(因爲Scala認爲這是定義一個方法,它返回一個函數使用下一個參數列表),編譯器一次處理一個參數列表,而不是全部在一起參數列表版本。

因此,在這種情況下:

def when[T](condition : T => Boolean, effect : Effect[T]) : Effect[T] 

/* Snip */ 

myEffects += when(_.hpLow(), applyModifierEffect) //This works fine! 

類型的applyModifierEffectmyEffects +=所需要的參數類型可以幫助限制的_.hpLow()參數類型;所有的T必須是PlayerCharacter。但在以下情況:

myEffects += when(_.hpLow()) (applyModifierEffect) 

編譯器具有獨立弄清楚when(_.hpLow())類型,以便它可以檢查它是否是有效的將其應用到applyModifierEffect。並且_.hpLow()本身並沒有提供足夠的信息讓編譯器推斷出這是when[PlayerCharacter](_.hpLow()),所以它不知道返回類型是類型爲Effect[PlayerCharacter] => Effect[PlayerCharacter]的函數,所以它不知道它適用於應用在那方面起作用。我的猜測是,類型推斷只是沒有連接點,並且發現只有一種類型可以避免類型錯誤。

而作爲該工程的其他情況:

def when[T](condition : T => Boolean) : Effect[T] 

/* Snip */ 

myEffects += when(_.hpLow()) //This works too! 

這裏的when及其參數類型的返回類型更直接連接,無需經過參數類型和產生的額外函數的返回類型會通過咖啡。由於myEffects +=需要Effect[PlayerCharacter],所以T必須是PlayerCharacter,它有一個hpLow方法,並完成編譯器。

希望有人更有知識可以糾正我的細節,或指出如果我吠叫完全錯誤的樹!

+0

你可能是對的。我真的不認爲具有多個參數列表的函數是真正被curry的,因爲當您調用它時必須使用下劃線代替任何缺少的參數列表,而如果您有一個明確curried的函數,比如「def curried(x:Int)=(y:Int)=> x + y「,則不必使用下劃線。但是,也許我對斯卡拉如何對待這些類型的功能的心理模型需要一些修改。 – Nimrand

+0

@Nimrand啊,我不知道你必須增加下劃線才能做到這一點。我知道,通過多個參數列表,編譯器將完全基於提供給第一個列表的參數修復類型參數(有時這是有幫助的,並避免需要顯式提示以避免含糊不清,有時會導致類型錯誤,可以通過手動解決類型註釋)。即使他們沒有得到與顯式使用函數相同的處理方式,多重參數列表也可以通過類型推理器以不同方式處理。 – Ben

2

我有點困惑,因爲在我看來,你所說的工作版本都不應該,而且實際上我不能讓它們中的任何一個工作。

類型推斷從左到右從一個參數列表(不參數)到下一個參數列表。典型的例子是在集合方法foldLeft(說SEQ [A])

def foldLeft[B] (z: B)(op: (B, A) => B): B 

的Z型意願使得乙知的,因此運算可以被寫入,而無需指定B(也不A,其是從一開始就知道,類型參數這個的)。 如果程序被寫成

def foldLeft[B](z: B, op: (B,A) => B): B 

def foldLeft[B](op: (B,A) => B)(z: B): B 

它不會工作,一個人必須要確保運算類型是明確的,或致電foldLeft時通過B明確地。

在你的情況,我覺得最愉快的閱讀相當於是使whenEffect,方法(或使它看起來像一個帶有隱式轉換),那麼你會寫

Effects += applyModifierEffect when (_.hpLow()) 

由於您提到效應是逆變的,when簽名不允許用於Effect的方法(因爲T => Boolean,函數在其第一個類型參數中是逆變的,並且該條件作爲參數出現,所以在逆變位置,兩個逆變量會生成一個協變),但它仍然可以用隱式

來完成
object Effect { 
    implicit def withWhen[T](e: Effect[T]) 
    = new {def when(condition: T => Boolean) = ...} 
} 
+0

如果'foldLeft'被寫爲'def foldLeft [B](z:B,op:(B,A)=> B):B',它不會工作,因爲爲'z'傳遞一個值會綁定'B'到它的類型?或者參數列表中的所有東西的類型必須獨立於同一列表中的其他東西纔可以推出? – Ben

+0

第二個。只有從以前的參數列表中獲得的信息纔可用。 –

+0

如果使用其參數推斷的類型參數,則僅使用第一個參數列表是正確的。我相信它能夠推斷出「when」的類型參數的原因是它的結果被傳遞給期望類型爲Effect [PlayerCharacter]的參數的方法,所以「when」的預期類型爲Effect [ PlayerCharacter],因此T的類型必須是PlayerCharacter(或者更寬的類型)。如果我只是自己打電話「什麼時候」,我沒有給出任何例子。 – Nimrand