[英]Does Haskell “understand” curried function definitions?
在 Haskell 中,函數總是采用一個參數。 多個參數通過Currying實現。 既然如此,我可以在下面看到如何將具有兩個參數的函數定義為“func1”。 它是一個返回函數(閉包)的函數,該函數將外部函數的單個參數添加到返回函數的單個參數中。
然而,盡管這就是柯里化函數的工作方式,但這不是用於定義雙參數函數的常規 Haskell 語法。 相反,我們被教導定義像“func2”這樣的函數。
我想知道 Haskell 如何理解 func2 應該與 func1 的行為方式相同。 func2 的定義並沒有告訴我它是一個返回函數的函數。 相反,它實際上看起來像一個雙參數函數,我們被告知不存在的東西!
這里有什么訣竅? Haskell 是不是剛出生就知道我們可以用這種教科書的方式定義多參數函數,並且它們無論如何都以我們期望的方式工作? 也就是說,這是一個似乎沒有明確記錄的語法約定(Haskell 知道你的意思並且會為你提供丟失的函數返回),或者是否有其他一些魔法在起作用或我遺漏了什么?
func1 :: Int -> (Int -> Int)
func1 x = (\y -> x + y)
func2 :: Int -> Int -> Int
func2 x y = x + y
main = do
print (func1 7 9)
print (func2 7 9)
在語言本身中,編寫形式為fxyz = _
的函數定義相當於f = \\xyz -> _
,相當於f = \\x -> \\y -> \\z -> _
。 這沒有理論上的原因。 只是那些嵌套的 lambda 抽象是一個可怕的眼睛/手指疼痛,每個人都認為犧牲一點迂腐來為它制作一些語法糖是可以的。 這就是表面上的全部內容,現在可能就是您需要知道的全部內容。
然而,在語言的實現中,事情變得更加棘手。 在最常見的 GHC 實現中, fxy = _
和f = \\x -> \\y -> _
之間實際上存在差異。 當 GHC 編譯 Haskell 時,它會為聲明分配arity 。 f
的前一個定義的元數為2
,后者的元數為0
。 從GHC.Base
獲取(.)
(.) f g = \x -> f (g x)
(.)
具有 arity 2
,即使它的類型 ( (b -> c) -> (a -> b) -> a -> c
) 表示它最多可以應用三次。 這會影響優化:GHC 只會內聯一個已飽和的函數,或者應用的參數至少與其元數一樣多。 在調用(maximum .)
, (.)
不會內聯,因為它只有一個參數(它是不飽和的)。 在呼叫(maximum . f)
將內聯到\\x -> maximum (fx)
和在(maximum . f) 1
中, (.)
將直列第一到的λ抽象(產生(\\x -> maximum (fx)) 1
),這將 beta 減少到maximum (f 1)
。 如果(.)
被執行
(.) f g x = f (g x)
(.)
將具有 arity 3
,這意味着它不會經常內聯(特別是f . g
情況,這是高階函數的一個非常常見的參數),可能會降低性能,這正是它的評論所說的:
確保它只在左側有兩個參數,以便在應用於兩個函數時內聯,即使沒有最終參數
最終答案:根據語言的語義,這兩種形式應該是等效的,但是在 GHC 中,這兩種形式在優化方面具有不同的特征,即使它們總是給出相同的結果。
在談論類型簽名時,沒有“多參數函數”這樣的東西。 所有函數都是單參數,周期。 Haskell 不需要以某種方式將多參數函數“翻譯”為單參數函數,因為前者根本不存在。
所有函數類型簽名看起來都像a -> b
,其中a
是參數類型, b
是返回類型。 有時b
可能恰好包含更多箭頭->
,在這種情況下,我們人類(但不是編譯器)可能會說該函數有多個參數。
在談論實現的語法時,即fxy = z
- 這只是語法糖,它在編譯期間被脫糖(即機械轉換)為f = \\x -> \\y -> z
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.