2012-10-09 18 views
7

我這輩子第三次想學習哈斯克爾,這次是通過Learn you a Haskell...
當筆者說明了警衛,他顯示了這個例子:哪個感官警衛比命令更好 - 如果? (哈斯克爾新手)

bmiTell :: (RealFloat a) => a -> String 
bmiTell bmi 
| bmi <= 18.5 = "You're underweight, you emo, you!" 
| bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!" 
| bmi <= 30.0 = "You're fat! Lose some weight, fatty!" 
| otherwise = "You're a whale, congratulations!" 

,並說

這很容易讓人想起在命令式語言一個大的if else樹,僅此是遠遠更好,更可讀。雖然大樹如果通常被人忽視,有時候問題會以這樣一種離散的方式來定義,以至於無法繞過它們。衛兵是一個非常好的選擇。

我可以看到警衛更具可讀性,但我不明白爲什麼這句法是「更好」
更靈活?它更強大?警衛的最大優勢是什麼?

我的大問題,它可能是句子

雖然很大,如果其他的樹木,通常令人難以接受的,有時一個問題是,你不能身邊

得到這樣的分立方式定義

任何人都可以舉一個例子嗎?

+0

除了更可讀的警衛不添加任何東西。 –

+0

@KarolisJuodelė:他們增加了一些東西:模式守衛,這不能用簡單的'if''然後''else'來完成 - 也不能用''''''''但是隻有兩者的組合。 – leftaroundabout

回答

12

唐給出了使用警衛的主要動機,但除此之外,他們也很好地與模式匹配相結合。如果一個模式中的所有守衛都失敗了,它會一直跳到下一個模式,因此您可以同時檢查模式和條件,而不會有大量重複的遺漏案例。這裏有一個(非常做作)例如:

expandRange x (Just lo, Just hi) | hi < lo = (Just x, Just x) 
expandRange x (Just lo, hi) | x < lo = (Just x, hi) 
expandRange x (lo, Just hi) | x > hi = (lo, Just x) 
expandRange _ range = range 

如果我們認爲Nothing爲無界的,這需要一個元素進行比較,要麼「擴展」負範圍僅元素,移動的下/上界包含該元素,或者如果該元素已包含,則保持該範圍不變。

現在,考慮如何在不使用警衛的情況下編寫上述代碼!你會多少次複製一個概念上相同的分支,因爲這些模式不同?是的,我意識到這個小例子可以重寫,以避免這個問題,但這並不總是可能的(或可取的)。

在我看來,這種風格的定義是使用警衛表達的最重要的東西,雖然仍然有可能,但如果將其寫成混合(無防護)模式案例和if表達式。

+0

社區似乎有一個共識,那就是正確的答案。所以我學到了兩件事:在大多數情況下,守衛只是更具可讀性,但是當它們與模式匹配相結合時,它們的真正威力就顯現出來了。 Haskell對我來說就像是一塊知識:你把它放在一起,或者你只是不明白。 –

+2

@PabloGrisafi:另外,「更具可讀性」並不容易理解!同時簡潔,易讀和具有表現力的是高級語言的價值 - 三者都是絕對必要的。 –

+1

你說得對,可讀性非常重要。我想說的是,除了你所展示的模式匹配組合外,守衛和命令式語言中的if/else幾乎是相同的概念。哈斯克爾對於我的命令性思維非常不同而且很奇怪,但是後衛畢竟不是那種外國人。 –

7

衛隊是語法輕:

  • 許多不同的情況
  • 嵌套的情況下

比較:

describeLetter c 
    | c >= 'a' && c <= 'z' = "Lower case" 
    | c >= 'A' && c <= 'Z' = "Upper case" 
    | otherwise   = "Not an ASCII letter" 

有:

describeLetter c = 
    if c >= 'a' && c <= 'z' 
     then "Lower case" 
     else if c >= 'A' && c <= 'Z' 
      then "Upper case" 
      else "Not an ASCII letter" 

規則部分語法更清晰,維護更容易。

此外,它們與視圖模式組合得很好,以產生令人愉快的語法。

f x | Just t <- bar x = Right (f t) 
     | otherwise  = Left "some error case" 

例如。

+0

雖然增加縮進是沒有必要的。你可以垂直對齊它。 –

+0

最後一個例子中'Right(f t)'中的'f'是否正確?看起來你會有一個無限遞歸類型。 – amindfv

2

如果在這個地方附近有很多問題,可以將其視爲一個決策圖。一種在LYAH例如一塊判定圖你舉會去:

    . 
       /\ 
      / \ 
       /bmi? \ 
       \ /
       \ /
      /\/\ 
      // \ \ 
     / | | \ 
     / | | \ 
     / | |  \ 
     / | |  \ 
< 18.5 /18.5-25| | 25-30 \ > 30 
    /  | |  \ 
    ...  ... ...  ... 

後衛的一大優點是它們讓語法結構反映了決策圖的結構。如果你所有的是if-then-else,那麼你將不得不通過一系列嵌套的if來實現上面的決策圖,也就是說,要編碼一個具有兩個分支選擇級聯的多分支選擇。嵌套如果模糊了算法的高級思想。

現在,我認爲LYAH的作者在你引用的句子中得到的結論是,有時你不能用守衛做得比用嵌套if-then-else更好。但是,只有當選擇是相互依賴的時候,也就是說,當您的決策圖包含許多菱形框(選項),每個菱形框只有兩個分支,並且不能以其他方式重寫時。請注意,在bmiTell示例中,每個分支都相互獨立,因爲BMI只能屬於4個類別,兩者都不會與其他任何重疊。

0

近衛看起來更明顯,不太詳細⁄有關於他們的「噪音」較少。

比較:

bmiTell bmi 
| bmi <= 18.5 = "................................." 
| bmi <= 25.0 = "..........................................." 
| bmi <= 30.0 = "...................................." 
| otherwise = "................................" 

bmiTell bmi = 
if  bmi <= 18.5 then "................................." 
else if bmi <= 25.0 then "..........................................." 
else if bmi <= 30.0 then "...................................." 
else      "................................" 

(加,什麼C.A. McCann said有關落空的情況下)。 :)

+1

但注意:這不是Haskell的慣用。 –

+0

@DonStewart在這種情況下是主觀的,我想。有時你需要在函數中間使用這樣的構造,並且你不想定義一個單獨的函數,所以你可以用守衛來寫下它。 –

+0

這種情況下的另一種選擇是新的[多路if語法](http://www.haskell.org/ghc/docs/7.6.1/html/users_guide/syntax-extns.html#multi-way-if )加入GHC 7.6.1。 – hammar