2010-05-06 16 views
10

Ho appena iniziato Haskell, ma da tutti i tutorial online che ho trovato non riesco a trovare se c'è un modo accettato di fare una dichiarazione di controllo condizionale. Ho visto se-else, guardie e pattern matching, ma sembrano tutti realizzare la stessa cosa. C'è un modo generalmente accettato/più veloce/più efficiente rispetto al resto?Dichiarazioni di controllo in Haskell?

+2

Non dimenticare 'case-of' :) – kennytm

+1

' case' è pattern-matching. – Chuck

+4

Tutte queste tecniche vengono compilate in "caso". 'caso' è il meccanismo di controllo fondamentale - dispacciamento basato su un modello. Guardie, schemi ecc. Sono semplicemente zucchero. –

risposta

10

C'è un modo generalmente accettato/più veloce/più efficiente rispetto al resto?

Le protezioni sono zucchero sintattico (piuttosto complesso) per la corrispondenza del modello if-then-else successivo. If-then-else è zucchero sintattico per case su Bool. Quindi queste cose sono per lo più ugualmente efficienti.

Ma ecco un'osservazione: spesso è facile da fare inefficiente con un'espressione booleana che cosa è efficiente con un pattern match. Un esempio preferito di inizio Haskell programmatori è scrivere

length xs == 0 

che costa proporzionale alla lunghezza di xs, dove

case xs of { [] -> True; _:_ -> False } 

costa tempo costante.

Un modo più preciso per osservare cosa sta succedendo è che (assenza di estensioni di fantasia come i modelli di visualizzazione), il costo peggiore di un pattern match è proporzionale al numero di costruttori che appaiono sul lato sinistro — non è possibile scrivere una corrispondenza di modello che è sia costosa che piccola. Al contrario, la dimensione di un'espressione booleana ti dice niente su quanto costa valutarlo. In questo senso, e solo in questo senso, la corrispondenza dei modelli è più economica di se-then-else o delle guardie.

Una buona euristica per i principianti è di utilizzare la corrispondenza dei modelli ovunque sia possibile. Man mano che acquisisci maggiore esperienza, puoi perfezionare il tuo approccio.

+0

(Ovviamente, dovresti usare 'null xs' per controllare se' xs' è vuoto.) – kennytm

+1

@Kenny Se avessi un dollaro per ogni mio studente che ha scritto 'length xs == 0' invece di' null xs' , Avrei abbastanza da tenermi nel caffè per l'anno :-) –

4

Beh, non so se pensare in termini di "dichiarazioni di controllo" sia il modo migliore per farlo in Haskell. Detto questo, per lo più tutto si riduce al pattern matching alla fine; condizionali booleani come if ... then ... else possono essere definiti in termini di pattern matching sui costruttori per Bool, per esempio.

La forma più "primitiva" è probabilmente l'istruzione case - la corrispondenza del modello per le definizioni di funzione è solo zucchero sintattico su una singola definizione di funzione contenente una grande espressione case.

In termini di cosa dovresti usare, vai con ciò che ha più senso dal punto di vista concettuale. La corrispondenza del modello è più appropriata quando è necessario separare un tipo di dati algebrico; I blocchi if sono appropriati per quando è necessario un semplice risultato sì/no per alcuni predicati. Le guardie vengono generalmente utilizzate quando è necessaria una combinazione di decostruzioni di tipo di dati e predicati booleani.

Il punto più importante da tenere a mente è che la corrispondenza del modello è l'unico modo per separare un tipo di dati algebrico. I predicati booleani possono essere facilmente replaced with higher-order functions, ma l'estrazione di valori all'interno di un costruttore di dati richiede la corrispondenza del modello.

4

Nessuna delle tre opzioni fa esattamente la stessa cosa o può essere utilizzata in tutte le situazioni.

Le corrispondenze di modello controllano quale costruttore è stato utilizzato per creare un determinato valore e si collegano le variabili. Né se né le guardie lo fanno. Potresti usarli solo al posto delle corrispondenze di pattern se combaci con un costruttore null (o un numero letterale) di un tipo che implementa l'Eq.

Esempio:

foo (Just x) = x+1 -- Can not do this without a pattern match (except by using 
        -- functions like fromJust that themselves use pattern matches) 
foo Nothing = 0 -- You could do this using a pattern guards like 
       -- foo x | x==Nothing = 0, but that is less readable and less 
       -- concise than using a plain pattern match 

guardie del modello consentono di verificare la presenza di altre cose che l'uguaglianza. Per esempio. puoi verificare se un dato numero è maggiore di zero. Ovviamente puoi fare la stessa cosa con if, ma le protezioni ti permettono di passare al modello successivo quando una guardia fallisce, il che può portare a una minore ripetizione rispetto all'utilizzo di if. Esempio:

maybeSqrt (Just x) | x >= 0 = sqrt x 
maybeSqrt _ = Nothing 

Utilizzando se questo sarebbe simile a questa (si noti la ripetizione di niente):

maybeSqrt (Just x) = if x >= 0 then sqrt x 
        else Nothing 
maybeSqrt _ = Nothing 

Infine, se può essere utilizzato senza motivo partite. Se in realtà non si utilizza la corrispondenza del modello su un dato valore introducendo un case x of ... solo così è possibile utilizzare le protezioni di pattern ha poco senso ed è meno leggibile e conciso rispetto all'utilizzo di if.

2

Scelgo in base a ciò che rende il codice più bello e più facile da leggere. Come sottolinea @ Don, molte di queste diverse forme sono compilate allo case. Sembrano diversi a causa dello zucchero sintattico disponibile. Questo zucchero non è per il compilatore, è per gli umani. Quindi decidi in base a ciò che pensi che gli altri umani vorrebbero leggere e che cosa ti sembra leggibile.

Problemi correlati