2012-10-09 8 views
7

Per la terza volta nella mia vita sto cercando di imparare Haskell, questa volta tramite Learn you a Haskell....
Quando l'autore spiega guardie, egli mostra questo esempio:In che senso le guardie sono meglio che imperative se? (nuovo ad 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!" 

e dice

Questo ricorda molto un grande, se altro albero in linguaggi imperativi, solo che questa è di gran lunga migliore e più leggibile . Mentre grandi se gli altri alberi sono solitamente disapprovati, a volte un problema è definito in modo così discreto che non è possibile aggirarli. Le guardie sono un'alternativa molto carina per questo.

posso vedere le guardie sono più leggibili, ma io non capisco perché che la sintassi è "di gran lunga migliore"
E 'più flessibile? È più potente? Qual è il grande vantaggio delle guardie?

Il mio grande problema è probabilmente la frase

Mentre grande se gli alberi sono di solito altro disapprovate, a volte un problema è definito in modo così discreta che non si può andare in giro loro

Qualcuno può dare un esempio?

+0

Oltre a essere guardie più leggibili, non aggiungere nulla. –

+0

@ KarolisJuodelė: aggiungono qualcosa: le guardie del modello, che non possono essere eseguite con 'if'' then' 'else' - né con' case' 'of', ma solo con una combinazione di entrambi. – leftaroundabout

risposta

12

Don dà le motivazioni principali per l'utilizzo di guardie, ma in aggiunta a ciò si combinano anche con il pattern matching. Se tutte le protezioni su un modello falliscono, si passa al modello successivo, in modo da poter controllare simultaneamente i modelli e le condizioni senza dover ricorrere a numerosi casi di ricaduta. Ecco un (molto artificiale) Esempio:

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 

Se pensiamo Nothing come illimitata, questo richiede un elemento di confrontare eo "espande" campo negativo al solo elemento, muove un limite inferiore/superiore includi l'elemento o lascia l'intervallo invariato se l'elemento è già incluso.

Ora, pensa a come scriverei sopra senza usare le guardie! Quante volte finirebbe per duplicare un ramo che è concettualmente lo stesso perché i modelli sono diversi? E sì, mi rendo conto che questo piccolo esempio potrebbe essere riscritto per evitare del tutto il problema, ma ciò non è sempre possibile (o auspicabile).

Questo stile di definizione è, a mio avviso, la cosa più significativa che puoi esprimere usando guardie che, pur essendo ancora possibili, sarebbero terribilmente più prolisse e molto più difficili da leggere se fossero state scritte come una miscela di (incustodito) casi modello e espressioni if.

+0

Sembra esserci un consenso nella comunità riguardo al fatto che questa sia la risposta giusta. Così ho imparato due cose: nella maggior parte dei casi le guardie sono più leggibili se lo sono, ma il loro vero potere si rivela quando si combina con l'abbinamento di modelli. Haskell per me è come un blocco di conoscenza: si ottiene tutto insieme o semplicemente non capisco. –

+2

@PabloGrisafi: Inoltre, essere "appena più leggibile" non è nulla da prendere alla leggera! Essere simultaneamente succinti, leggibili ed espressivi è ciò che dà il valore ai linguaggi di alto livello - e tutti e tre sono assolutamente necessari. –

+1

Hai ragione, essere leggibile è molto importante. Quello che stavo cercando di dire è che, oltre alla combinazione di combinazioni di pattern che hai mostrato, le guardie sono praticamente lo stesso concetto di se/else in lingue imperative. Haskell è davvero diverso e strano per la mia mentalità imperativa, ma le guardie non sono poi così straniere. –

7

guardie sono sintatticamente più leggero per:

  • molti casi distinti
  • casi annidati

Confronta:

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

con:

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

La parte della regola è più sintatticamente più chiara e più facile da mantenere.

Inoltre, si combinano bene con i modelli di visualizzazione per ottenere una sintassi piacevole.

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

per esempio.

+0

tuttavia non è necessario aumentare il rientro. Potresti allineare tutto verticalmente. –

+0

Il 'f' in' Destro (f t) 'nell'ultimo esempio è corretto? Sembra che tu avresti un tipo infinitamente ricorsivo. – amindfv

2

Qualsiasi problema con un sacco di se è intorno al luogo potrebbe essere visualizzato come un diagramma decisionale. Un pezzo del diagramma decisionale per l'esempio Lyah lei cita sarebbe andato:

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

Il grande vantaggio di guardie è che hanno lasciato la struttura della sintassi riflettono la struttura del diagramma decisione. Se tutto ciò che avevi era if-then-else, allora dovresti implementare il diagramma decisionale sopra con una serie di if annidati, cioè per codificare una scelta multi-ramo con una cascata di scelte a due rami. Nidificato se oscura l'idea di alto livello del tuo algoritmo.

Ora, quello che penso che l'autore di LYAH stia ottenendo nella frase che hai citato, è che a volte non puoi fare meglio con le guardie di quanto tu possa fare con nested if-then-else.Ma questo è vero solo quando le scelte sono interdipendenti, cioè quando il tuo diagramma decisionale contiene molte scatole di diamanti (scelte), ciascuna con solo due rami, e non può essere riscritta in nessun altro modo. Si noti che nell'esempio bmiTell, ciascun ramo è indipendente dall'altro, in quanto il BMI può rientrare solo nelle 4 categorie, nessuna delle quali si sovrappone a nessuna.

0

Le protezioni sono visivamente più evidenti e meno verbose. ⁄ hanno meno "rumore" su di esse.

Confronta:

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      "................................" 

(più, cosa C.A. McCann said circa i casi di caduta-through). :)

+1

Ma nota: questo non è un idioma di Haskell. –

+0

@DonStewart che è soggettivo in questo caso, penso.A volte hai bisogno di un tale costrutto nel mezzo di una funzione e non vuoi definire una funzione separata solo così puoi scriverla con le guardie. –

+0

Un'altra opzione in questi casi è la nuova [multi-way if syntax] (http://www.haskell.org/ghc/docs/7.6.1/html/users_guide/syntax-extns.html#multi-way-if) aggiunto in GHC 7.6.1. – hammar

Problemi correlati