2009-05-07 9 views
31

Ho un codice Haskell che fa funziona correttamente su una lista infinita, ma non capisco perché può farlo con successo. (Ho modificato il mio codice originale - che non ha gestito liste infinite - per incorporare qualcosa da qualche altro codice online, e improvvisamente vedo che funziona ma non so perché).Perché questo codice Haskell funziona correttamente con elenchi infiniti?

myAny :: (a -> Bool) -> [a] -> Bool 
myAny p list = foldr step False list 
    where 
     step item acc = p item || acc 

mia comprensione di foldr è che sarà un ciclo tra ogni elemento della lista (e forse che la comprensione è incompleta). Se è così, non dovrebbe importare come è formulata la funzione "step" ... il codice dovrebbe essere incapace di gestire loop infiniti.

Tuttavia, le seguenti opere:

*Main Data.List> myAny even [1..] 
True 

Si prega di aiutarmi a capire: perché ??

risposta

46

Facciamo una piccola traccia nella nostra testa di come Haskell valuterà la tua espressione. Sostituendo è uguale per uguale su ogni riga, l'espressione restituisce abbastanza rapidamente True:

myAny even [1..] 
foldr step False [1..] 
step 1 (foldr step False [2..]) 
even 1 || (foldr step False [2..]) 
False || (foldr step False [2..]) 
foldr step False [2..] 
step 2 (foldr step False [3..]) 
even 2 || (foldr step False [3..]) 
True || (foldr step false [3..]) 
True 

Questo funziona perché acc viene passato come thunk non valutata (valutazione pigra), ma anche perché la funzione || è severo nel suo prima Argomento.

Quindi questo termina:

True || and (repeat True) 

ma questo non significa:

and (repeat True) || True 

Guardate la definizione di || per vedere il motivo per cui questo è il caso:

True || _ = True 
False || x = x 
+4

Inoltre è possibile verificare che il codice non calcoli più di 2 elementi con: 'myAny p list = foldr (\ ia -> trace (mostra i) (pi || a))' - che mostrerà solo '1 2 True' – viraptor

+0

Wow, questa è stata una risposta di apertura MOLTO occhio. Prima di tutto, non ho iniziato con la definizione di foldr davanti a me. Ho pensato che il suo codice avrebbe utilizzato funzionalità avanzate che ancora non conosco, quindi l'ho visto solo come ultima risorsa. La tua risposta mi ha spinto a dare un'occhiata, e questo chiarisce molto. foldr stesso usa la ricorsione strutturale "normale". Adoro il modo in cui l'hai rotto. Grazie. –

+0

BTW, è il || completamente severo nel suo primo argomento o semplicemente "dare la preferenza" al suo primo argomento? Ad esempio, cosa succede se Arg 2 era già stato valutato, ma Arg 1 era ancora solo un thunk? E dire che Arg 2 era falso. Has Haskell avrebbe cortocircuito nella direzione opposta? Grazie. –

1

Il punto chiave qui è che Haskell è un linguaggio non rigoroso. "Non severi" significa che consente funzioni non rigorose, il che significa che i parametri di funzione potrebbero non essere valutati completamente prima che possano essere utilizzati. Ciò ovviamente consente una valutazione lazy, che è "una tecnica per ritardare un calcolo fino a quando non è richiesto il risultato".

Start dal this Wiki article

+0

OK, lo sapevo della valutazione pigra. Ma ho bisogno di aiuto per collegare i punti da questo al perché il codice sopra funziona. Nel frattempo, controllerò l'articolo wiki. Grazie. –

1

non so Haskell, ma ho il sospetto che nel tuo caso, funziona a causa della valutazione pigra. Perché ti permette di lavorare con la lista infinitamente lunga, quando accedici, calcolerà il risultato quando ne hai bisogno.

Vedi http://en.wikipedia.org/wiki/Lazy_evaluation

+0

Questo è un buon articolo, grazie per il link. –

18

mia comprensione di foldr è che ciclo volontà attraverso ogni elemento della lista (e forse che la comprensione è incompleta).

foldr (a differenza foldl) non c'è bisogno di scorrere ogni elemento della lista. È istruttivo osservare come è definito foldr.

foldr f z []  = z 
foldr f z (x:xs) = f x (foldr f z xs) 

Quando una chiamata a foldr viene valutata, costringe la valutazione di una chiamata alla funzione f. Notare come la chiamata ricorsiva a foldr è incorporata in un argomento della funzione f. Quella chiamata ricorsiva non viene valutata se f non valuta il suo secondo argomento.

+0

Buon punto. Inizialmente non sapevo che la definizione di foldr sarebbe stata comprensibile al mio livello di conoscenza Haskell. Osservo come fai notare che la chiamata ricorsiva è stata intenzionalmente inserita in un argomento di funzione, al fine di renderlo un thunk. È qualcosa che gli sviluppatori di Haskell si trovano a fare molto? Ci sono casi in cui non hai bisogno di una funzione, ma ne crei uno solo per poter passare un argomento e sapere che sarà un thunk? –

+2

Charlie, dal momento che è Haskell che stiamo usando, * nulla * viene valutato a meno che qualcosa lo costringa a. –

Problemi correlati