2014-03-31 50 views
6

我是Haskell的初學者。哈斯克爾函數定義約定

在函數定義中使用按我校材料的約定實際上是如下

函數名arguments_separated_by_spaces = code_to_do

例如:

f a b c = a * b +c 

作爲學生學習數學的我習慣於使用功能如下:

function_name(arguments_separated_by_commas)= code_to_do

例如:

f(a,b,c) = a * b + c 

它在Haskell工作。

我的疑問是它是否適用於所有情況?

我的意思是我可以在Haskell函數定義中使用傳統的數學約定嗎?

如果錯了,在哪些特定情況下會議出錯?

感謝提前:)

回答

13

假設你想定義一個函數來計算右三角形的hypoteneuse的平方。以下定義中的任何一個都是有效的

hyp1 a b = a * a + b * b 

hyp2(a,b) = a * a + b * b 

但是,它們不是相同的功能!您可以通過在GHCI

看着自己的類型
>> :type hyp1 
hyp1 :: Num a => a -> a -> a 

>> :type hyp2 
hyp2 :: Num a => (a, a) -> a 

hyp2第一(現在忽略Num a =>一部分)的類型告訴你的函數取一對(a, a)並返回另一個a(例如,它可能需要告訴一對整數並返回另一個整數或一對實數並返回另一個實數)。你這樣使用它

>> hyp2 (3,4) 
25 

請注意,括號在這裏不是可選的!他們確保論證是正確的類型,一對a s。如果你不包含它們,你會得到一個錯誤(現在看起來可能會讓你感到困惑,但請放心,當你瞭解了類型類時它會有意義的)。

現在看hyp1讀取類型a -> a -> a的一種方法是需要兩件東西a並返回a類型的其他東西。您可以使用它像這樣

>> hyp1 3 4 
25 

現在你會得到一個錯誤,如果你包括括號!

所以首先要注意的是,你使用函數的方式必須與你定義它的方式相匹配。如果用parens定義函數,則每次調用它時都必須使用parens。如果在定義函數時不使用parens,則在調用它時不能使用它們。

因此,似乎沒有理由選擇其中一個 - 這只是一個品味問題。但實際上,我認爲是一個優先於另一個的好理由,並且您應該更喜歡沒有括號的樣式。有三個很好的理由:

  1. 它看起來更乾淨,讓您的代碼更容易閱讀,如果你沒有parens凌亂的頁面。

  2. 如果你在任何地方都使用parens,那麼你會受到性能影響,因爲每次使用該函數時都需要構造和解構一對(儘管編譯器可能會優化它 - 我不確定)。

  3. 你想要得到的好處討好,又名部分應用功能 *。

最後一點是微妙的。回想一下,我說過一種理解a -> a -> a類型函數的方法是它需要兩件事a類型,並返回另一個a。但是還有另一種方法來讀取該類型,即a -> (a -> a)。這意味着完全一樣的東西,因爲->運算符在Haskell中是右關聯的。解釋是該函數只需要一個a,並返回a -> a類型的函數。這可以讓你只需要提供的第一個參數的功能,並應用第二個參數後,例如

>> let f = hyp1 3 
>> f 4 
25 

這是在各種各樣的場合實用價值。例如,map功能可讓您將一些功能列表中的每個元素 -

>> :type map 
map :: (a -> b) -> [a] -> [b] 

假設你有增加了一個爆炸任何String功能(++ "!")。但是你有Strings的列表,你希望它們都以爆炸聲結束。沒問題!你只是部分應用map功能

>> let bang = map (++ "!") 

現在bang是類型的函數**

>> :type bang 
bang :: [String] -> [String] 

,你可以用它像這樣

>> bang ["Ready", "Set", "Go"] 
["Ready!", "Set!", "Go!"] 

非常有用的!

我希望我已經說服你,在你的學校的教育材料中使用的慣例有一些相當堅實的理由被使用。作爲擁有數學背景的人,我可以看到使用更多「傳統」語法的吸引力,但我希望隨着您在編程之旅中的進步,您將能夠看到改變最初的某些東西的優勢對你不熟悉。


*對於書呆子的說明 - 我知道咖喱和部分應用並不完全一樣。

**實際上,GHCI會告訴您類型爲bang :: [[Char]] -> [[Char]],但由於String[Char]的同義詞,所以這些意思是相同的。

+0

在你的第一個筆記上:'不完全相同的東西'暗示着某種關係,而實際上沒有關係。 Currying只允許輕鬆部分應用,但我認爲原則上它們是非常不相關的。 – Xeo

+2

@Xeo他們有些相關。 curried/tupled表單形成一個[adjunction](http://en.wikipedia.org/wiki/Adjoint_functors),其中F是'( - x B)',G是' -^B'。 – jozefg

+0

@Xeo我想這是一個味道點。對於一個函數'(a,b,c) - > d',currying它給出'a - > b - > c - > d'而部分應用第一個參數給出a - >(b,c) - > D'。對於兩個參數的函數,currying和部分應用是相同的事情。在Haskell中,幾乎沒有區別,因爲默認情況下是currying(這是我希望避免用我的腳註做出的評論!) –

2
f(a,b,c) = a * b + c 

關鍵的區別理解的是,上述功能需要一個三人間和給出結果。你實際上在做什麼是三重模式匹配。該類型的上述功能是這樣的:

(a, a, a) -> a 

如果你寫的功能是這樣的:

f a b c = a * b + c 

你得到的功能自動咖喱。 你可以寫這樣的東西,如let b = f 3 2,它會檢查,但同樣的事情不會用你的初始版本。此外,像柯里化這樣的東西可以幫助很多,同時使用(.)來編寫各種功能,除非您嘗試編寫三元組,否則前者的風格無法實現。

2
  1. 數學符號不一致。如果所有的功能使用(,)給定的參數,你就必須寫(+)((*)(a,b),c)通過a*bc運作+ - 當然,a*b被傳遞ab運作*制定。

  2. 可以以tupled形式寫入所有內容,但定義構圖要困難得多。鑑於現在您可以指定一個類型a->b來涵蓋任何元組的功能(因此,您可以將組合定義爲類型(b->c)->(a->b)->(a->c)的函數),但使用元組來定義任意元組的功能要複雜得多(現在a->b只能表示一個函數的一個參數;你不能再用多個參數的函數組成一個具有許多參數的函數)。所以,技術上可能的,但它需要一個語言功能,使其簡單方便。