2015-12-03 10 views
8

Quindi sono stato impegnato con il libro Real World Haskell e ho eseguito l'esercizio lastButOne. Sono venuto con 2 soluzioni, uno con pattern matchingUn esempio di vita reale quando l'abbinamento di modelli è più preferibile rispetto a un'espressione di caso in Haskell?

lastButOne :: [a] -> a 
lastButOne ([]) = error "Empty List" 
lastButOne (x:[]) = error "Only one element" 
lastButOne (x:[x2]) = x 
lastButOne (x:xs) = lastButOne xs 

E uno utilizzando un caso espressione

lastButOneCase :: [a] -> a 
lastButOneCase x = 
    case x of 
    [] -> error "Empty List" 
    (x:[]) -> error "Only One Element" 
    (x:[x2]) -> x 
    (x:xs) -> lastButOneCase xs 

Quello che volevo sapere è quando sarebbe pattern matching essere preferito su espressioni del caso e viceversa. Questo esempio non è stato abbastanza buono per me perché sembra che mentre entrambe le funzioni funzionano come previsto, non mi ha portato a scegliere un'implementazione rispetto all'altra. Quindi la scelta "sembra" preferenziale a prima vista?

Quindi ci sono buoni casi per mezzo del codice sorgente, sia nella sorgente di haskell o github o altrove, dove si è in grado di vedere quando uno dei due metodi è preferito o no?

+5

Preferirei quello con due righe più corte. – Bergi

+8

Le espressioni del caso non utilizzano lo stesso abbinamento di pattern, solo all'interno di un diverso costrutto sintattico? – 9000

+3

Un'espressione case può essere incorporata in un'altra espressione, non necessariamente la prima di una definizione di funzione. Suppongo che le dichiarazioni di funzioni multiple con pattern siano solo uno zucchero sintattico sulla seconda forma. – 9000

risposta

15

Prima una breve deviazione della terminologia: chiamerei entrambi questi "pattern matching". Non sono sicuro che esista un termine valido per distinguere la corrispondenza del modello, caso per caso e corrispondenza modello, tramite definizione multipla.

La distinzione tecnica tra i due è piuttosto leggera. Puoi verificare tu stesso chiedendo a GHC di scaricare il core che genera per le due funzioni, usando il flag -ddump-simpl. Ho provato questo a pochi diversi livelli di ottimizzazione, e in tutti i casi le uniche differenze nel Core erano i nomi. (A proposito, se qualcuno conosce un buon programma "semantic diff" per Core - che sa come minimo l'equivalenza alfa - sono molto interessato a sentirlo!)

Ci sono alcuni piccoli trucchi per fare attenzione, però. Ci si potrebbe chiedere se il seguente è anche equivalente:

{-# LANGUAGE LambdaCase #-} 
lastButOne = \case 
    [] -> error "Empty List" 
    (x:[]) -> error "Only One Element" 
    (x:[x2]) -> x 
    (x:xs) -> lastButOneCase xs 

In questo caso, la risposta è sì. Ma considerare questo aspetto simile uno:

-- ambiguous type error 
sort = \case 
    [] -> [] 
    x:xs -> insert x (sort xs) 

Tutto ad un tratto questo è un CAF typeclass-polimorfico, e così via vecchie GHCs In tal modo, la restrizione monomorfismo e causare un errore, mentre la versione superficialmente identico a un argomento esplicito non lo fa:

-- this is fine! 
sort [] = [] 
sort (x:xs) = insert x (sort xs) 

l'altra differenza minore (che ho dimenticato - grazie a Thomas DuBuisson per avermelo ricordato) è nella gestione di clausole WHERE. Dal momento che le clausole sono collegate ai siti vincolanti, non possono essere condivise tra più equazioni ma possono essere condivise in più casi.Per esempio:

-- error; the where clause attaches to the second equation, so 
-- empty is not in scope in the first equation 
null [] = empty 
null (x:xs) = nonempty 
    where empty = True 
     nonempty = False 

-- ok; the where clause attaches to the equation, so both empty 
-- and nonempty are in scope for the entire case expression 
null x = case x of 
    [] -> empty 
    x:xs -> nonempty 
    where 
    empty = True 
    nonempty = False 

Si potrebbe pensare che questo significa che si può fare qualcosa con le equazioni che non si può fare con espressioni case, vale a dire, hanno significati diversi per lo stesso nome a due equazioni, come questo:

null [] = answer where answer = True 
null (x:xs) = answer where answer = False 

Tuttavia, dal momento che i modelli di case espressioni sono siti di legame, questo può essere emulato in case espressioni così:

null x = case x of 
    [] -> answer where answer = True 
    x:xs -> answer where answer = False 

Se la clausola where è collegata al modello case o all'equazione dipende dal rientro, ovviamente.

+4

"Non sono sicuro che esista un termine valido per distinguere la corrispondenza del modello, caso per caso e corrispondenza del modello, tramite una definizione multipla." Il documento [Hudak, Hughes, Peyton Jones e Wadler sulla storia di Haskell] (http://haskell.cs.yale.edu/wp-content/uploads/2011/02/history.pdf) chiama questo "stile di dichiarazione" (per le equazioni multiple) rispetto a "stile di espressione" (per l'espressione 'case'). –

+0

Ottima risposta! Io per primo non sapevo che avresti potuto "dove" legare le gambe di un "caso". –

4

Se ricordo correttamente entrambi questi verranno "desugar" nello stesso codice di base in ghc, quindi la scelta è puramente stilistica. Personalmente vorrei andare per il primo. Come qualcuno ha detto, è più breve, e ciò che si chiama "abbinamento di modelli" è destinato a essere usato in questo modo. (In realtà la seconda versione è anche la corrispondenza dei pattern, usando solo una sintassi diversa per essa).

1

È una preferenza stilistica. Alcune persone a volte sostengono che una scelta o l'altra fa sì che alcune modifiche al codice richiedano meno sforzo, ma in genere trovo che tali argomenti, anche se accurati, non rappresentano in realtà un grande miglioramento. Fai come vuoi

Una prospettiva che vale la pena includere in questo è Hudak, Hughes, Peyton Jones e il documento di Wadler "A History of Haskell: Being Lazy With Class". La sezione 4.4 riguarda questo argomento. Il racconto: Haskell supporta entrambi perché i designer non potevano essere d'accordo l'uno sull'altro. Sì, ancora una volta, è una preferenza stilistica.

0

Quando si sta corrispondenti a più di un'espressione, le espressioni case iniziano ad apparire più interessanti.

f pat11 pat21 = ... 
f pat11 pat22 = ... 
f pat11 pat23 = ... 
f pat12 pat24 = ... 
f pat12 pat25 = ... 

può essere più fastidioso da scrivere rispetto

f pat11 y = 
    case y of 
    pat21 -> ... 
    pat22 -> ... 
    pat23 -> ... 
f pat12 y = 
    case y of 
    pat24 -> ... 
    pat25 -> ... 

Più significativamente, ho trovato che quando si usa GADTs, lo "stile dichiarazione di" non sembra propagarsi prove da sinistra a destra il come me lo aspetterei. Potrebbe esserci qualche trucco che non ho risolto, ma finisco per dover annidare le espressioni case per evitare falsi avvisi di pattern incompleti.

Problemi correlati