2013-04-05 46 views
12

在OCaml中,它是合法的,在.mliη膨脹在純函數式語言

val f : 'a -> 'a 
val g : 'a -> 'a 

.ml

let f x = x 
let g = f 
在F#

然而,這個被拒絕:

eta_expand.ml(2,5): error FS0034: Module 'Eta_expand' contains 
    val g : ('a -> 'a)  
but its signature specifies 
    val g : 'a -> 'a  

The arities in the signature and implementation differ. The signature specifies that 'g' is function definition or lambda expression accepting at least 1 argument(s), but the implementation is a computed function value. To declare that a computed function value is a permitted implementation simply parenthesize its type in the signature, e.g. 
    val g: int -> (int -> int) 
instead of 
    val g: int -> int -> int. 

一種解決方法是η擴展g的定義:

let g x = f x 

如果我的代碼是純功能性的(無異常,無副作用等),這應該是相等的(實際上,它可能是更好的針對多態,這取決於語言如何概括類型:OCaml中部分應用程序不會產生多態函數,但它們的η擴展會)。

系統η擴張有什麼缺點嗎?

兩個答案迴避了關於η-擴展的問題:-)而是建議我在我的功能類型周圍添加括號。這是因爲顯然,F#在功能的「真實」定義(如λ表達式和計算定義,如在部分應用程序中)之間的打字級別上區分開來;大概這是因爲λ表達式直接映射到CLR函數,而計算的定義映射到委託對象。 (我不知道這樣的解釋,如果有人非常熟悉F#可能指向參考文件描述此將不勝感激。)

一個解決方案是將括號中.mli系統添加到所有功能類型,但我擔心這可能會導致效率低下。另一種方法是檢測計算的函數,並在.mli中添加相應類型的括號。第三種解決方案是η擴展明顯的情況,並將其他表達爲括號。

我對F#/ CLR內部構件不夠熟悉,無法測量哪些測試會導致顯着的性能或接口處罰。

+2

只要使它成爲'val g:('a - >'a)'。這是F#類型系統的已知功能/錯誤。 – 2013-04-05 09:03:27

+0

再一次,「只是做它」 - 大概如果λ表達式和計算函數不具有可互換類型,這是有原因的。此外,這是自動生成的代碼,因此,這個問題比手動添加一些括號稍微複雜一些... – 2013-04-05 09:26:03

+0

在這種情況下,兩者之間存在差異,一個編譯成一個方法,另一個編譯成靜態'功能'財產。兼容性不是雙向的(即「a→b:<: (a -> b」),而不是「(a→b):<: a -> b」)。 – 2013-04-05 10:41:14

回答

4

有趣的是我fsi給出了一個更實用的錯誤消息:

/test.fs(2,5): error FS0034: Module 'Test' contains 
    val g : ('a -> 'a) but its signature specifies 
    val g : 'a -> 'a The arities in the signature and implementation differ. 
      The signature specifies that 'g' is function definition or lambda expression 
      accepting at least 1 argument(s), but the implementation is a computed 
      function value. To declare that a computed function value is a permitted 
      implementation simply parenthesize its type in the signature, e.g. 
     val g: int -> (int -> int) instead of 
     val g: int -> int -> int. 

如果加上括號獲得g :('a -> 'a)一切都很好

+1

爲了簡潔起見,我已經截斷了錯誤消息的結尾。我不同意「一切都很好」。 :-) – 2013-04-05 09:18:47

8

從理論上講,F#函數類型'a -> 'b -> 'c相同類型'a -> ('b -> 'c)。也就是說,多個參數函數使用F#中的curried形式表示。在大多數情況下,您可以使用預期中的另一個。當調用一個更高階的函數。但是,出於實際的原因,F#編譯器實際上區分了兩種類型 - 其動機是它們在編譯後的.NET代碼中以不同方式表示。這對性能以及與C#的互操作性都有影響,因此有必要做出區分。

函數Foo : int -> int -> int將被編譯爲成員int Foo(int, int) - 編譯器不使用默認的咖喱形式,因爲調用Foo用兩個參數(更常見的情況)時,這是更有效,這是互操作更好。函數Bar : int -> (int -> int)將被編譯爲FSharpFunc<int, int> Bar(int) - 實際上使用了curried形式(因此僅使用單個參數調用它就更加高效,並且將很難從C#中使用)。

這也是爲什麼F#在簽名時並不把它們看作是平等的 - 簽名指定了類型,但是這裏還指定了函數將如何被編譯。實現文件必須提供正確類型的函數,但在這種情況下也是正確的編譯形式。

+0

我明白不同之處。我不知道的是最好的解決方法:我應該(a)η擴展所有計算的函數(b)將所有計算的函數的類型括起來? – 2013-04-05 11:32:57

+0

@monniauxη-擴展所有計算的函數將是我的默認選擇。但這取決於許多因素。如果你想C#的互操作性,那麼肯定(a),如果你經常在計算函數上使用部分應用程序,那麼(b)會給你更好的性能。 – 2013-04-05 12:13:57