2013-03-21 102 views
3

我無法理解curried和uncurried函數。我谷歌的所有網站都試圖爲我提供一個定義,但對我而言並不清楚。不安全的功能

在一個例子中,我發現他們說

max 4 5相同(max 4) 5

但我不明白他們在做什麼。當max需要2個參數時,如何獲得(max 4)功能?我完全失去了。

+1

參見[什麼是譁衆取寵的優勢在哪裏?(http://programmers.stackexchange.com/q/185585/61231)。 – 2013-03-21 05:48:00

+0

我想這個問題,我的答案可能有幫助:http://stackoverflow.com/questions/8148253/how-are-functions-curried/8148957 – Ben 2013-03-21 05:54:03

回答

13

Haskell的訣竅是函數只有一個參數。這看起來非常瘋狂,但它確實有效。

一個Haskell函數:

foo :: Int -> Int -> Int 
foo a b = a + b 

真正含義是:函數,它接受在1個參數,然後返回另一個函數,它帶有一個參數。這叫做咖喱。

所以用這個,我們真的可以寫這個函數的定義是這樣的:

foo :: Int -> (Int -> Int) --In math speak: right associative 

,並意味着完全一樣的東西。

這實際上是超級有用的,因爲我們現在可以編寫簡潔的代碼,如:

foo1 :: Int -> Int 
foo1 = foo 1 

由於在Haskell功能的應用僅僅是空白,大部分的時間你可以假裝咖喱功能uncurried(採取更多比一個參數還要返回結果)。

如果你確實真的需要uncurried函數:使用元組。

uncFoo :: (Int, Int) -> Int 
uncFoo (a, b) = a + b 

編輯

行,所以要了解怎麼回事與部分應用程序考慮bar

bar a b c = [a, b, c] 

的事情是,編譯器將desugar你剛纔輸入到這樣

lambda表達式
bar = \a -> 
     \b -> 
     \c -> 
      [a, b, c] 

這利用了閉包(每個內部函數都可以「記住」以前的參數。

所以當我們說bar 1,編譯器去,並看着bar,看到最外層的λ,並將其應用於給

bar 1 = \b -> 
     \c -> 
      [1, b, c] 

如果說bar 1 2

bar 1 2 = \c -> 
       [1, 2, c] 

如果我是什麼意思時,我說「應用」是模糊的,那麼它可能有助於知道我真的是從lambda演算中得到beta reduction

+0

我明白,它需要一個參數,返回另一個函數,它接受另一種說法,但它是如何計算的?所以在你的情況下,如果我有'foo a'這個函數,它應該怎麼做只有一個參數?我想我需要更詳細的解釋它的工作過程。 – dtgee 2013-03-21 04:52:08

+0

看看lambdas。這就是實際發生的情況。嗯,編輯因爲這太小了 – jozefg 2013-03-21 04:52:55

+0

@ user1831442你在這裏 – jozefg 2013-03-21 05:03:49

4

根據您的背景,您可能會發現本文有啓發性:How to Make a Fast Curry: Push/Enter vs Eval Apply。雖然多參數函數可以理解爲綁定單個參數並返回另一個函數的函數:max = (\a -> (\b -> if a > b then a else b)),但實際實現效率更高。

如果編譯器靜態地知道max需要兩個參數,編譯器將始終通過推入堆棧(或寄存器中)的兩個參數來轉換max 4 5,然後調用max。這與C編譯器如何翻譯max(4, 5)基本相同。另一方面,如果例如max是更高階函數的參數,則編譯器可能不會靜態知道max需要多少個參數。也許在一個例子中,它需要三個,所以max 4 5是一個部分應用程序,或者也許在另一個它只需要一個和max 4生成一個新的功能,應用5。本文討論了處理不確定靜態的情況的兩種常見方法。

0

涉及到你自己的例子......

假設您想要一個函數,給出了最大的4和功能參數。您可以實現這樣的:

max4 :: Integer -> Integer 
max4 x = max 4 x 

max 4做什麼只是返回動態創建功能max4

1

你可能有已在您的回答,只是重申:

如果我們有

add x y = x + y 

那麼我們可以說以下內容:

add = \ x y -> x + y 
add 3 = \ y -> 3 + y 
add 3 5 = 3 + 5 = 8 

你問「怎麼會max 3計算什麼?「,答案是」它不能「。它只是給你另一個函數。這個函數可以在你調用它的時候做一些事情,但是你不會「得到一個答案」,直到所有的參數都被提供。在此之前,你只需要獲取函數。

大多數時候,這僅僅是一個有用的語法捷徑。例如,你可以寫

uppercase :: String -> String 
uppercase = map toUpper 

,而不必說

uppercase xs = map toUpper xs 

注意,如果map有其他方式的爭論,我們就無法做到這一點(你只能咖喱了最後論點,而不是_first),所以重要的是要考慮你定義你的函數的論點的順序。


我說「大部分時間」,因爲這不僅僅是語法糖。在語言中有幾個地方可以使用不同數量的參數來處理函數多態性,因爲它是柯里化的。每個函數都會返回一個答案或另一個函數。如果你認爲它像一個鏈表(它包含下一項數據或列表結束標記),你可以看到這是如何遞歸處理函數的。

那麼究竟發生了什麼我的意思了?嗯,比如說,快速檢查可以測試功能與任何數量的參數(提供有一種方法來自動生成每個參數的測試數據)。這是可能的,因爲功能類型是咖喱。每個函數都會返回另一個函數或結果。如果你想像鏈表一樣,你可以想象QuickCheck遞歸迭代函數,直到沒有更多的參數被留下。

下面的代碼片段可能會或可能無法解釋的想法:

class Arbitrary a where 
    autogenerate :: RandomGenerator -> a 

instance Arbitrary Int 
instance Arbitrary Char 
... 

class Testable t where 
    test t :: RandomGenerator -> Bool 

instance Testable Bool where 
    test rnd b = b 

instance (Arbitrary a, Testable t) => Testable (a -> t) where 
    test rnd f = test $ f (autogenerate rnd) 

如果我們有一個功能foo :: Int -> Int -> Bool,那麼這是Testable。爲什麼?那麼,Bool是可測試的,因此這樣的Int -> Bool,因此這樣是Int -> (Int -> Bool)

相反,元組的每個尺寸是不同的大小,所以你必須寫爲每個和元組的每一個單獨的大小的函數(或實例)。你不能遞歸地處理元組,因爲它們沒有遞歸結構。