28

In Haskell, potrei implementare if come questo:Idris valutazione eager

if' True x y = x 
if' False x y = y 
spin 0 =() 
spin n = spin (n - 1) 

Questo si comporta come mi aspetto:

haskell> if' True (spin 1000000)() -- takes a moment 
haskell> if' False (spin 1000000)() -- immediate 

In Racket, ho potuto implementare un if2 imperfetto in questo modo:

(define (if2 cond x y) (if cond x y)) 
(define (spin n) (if (= n 0) (void) (spin (- n 1)))) 

Questo si comporta come mi aspetto:

racket> (if2 #t (spin 100000000) (void)) -- takes a moment 
racket> (if2 #f (spin 100000000) (void)) -- takes a moment 

In Idris, potrei implementare if come questo:

if' : Bool -> a -> a -> a 
if' True x y = x 
if' False x y = y 
spin : Nat ->() 
spin Z =() 
spin (S n) = spin n 

Questo surpris comportamento Mi es:

idris> if' True (spin 1000)() -- takes a moment 
idris> if' False (spin 1000)() -- immediate 

mi aspettavo Irdis a comportarsi come Racket, dove entrambi gli argomenti sono valutati. Ma non è questo il caso!

Come decide Idris quando valutare le cose?

risposta

40

Diciamo che Idris ha una valutazione rigorosa, ma questa è per la sua semantica di runtime.

Essendo un linguaggio completamente tipizzato, Idris ha due fasi in cui valuta le cose, la compilazione e il tempo di esecuzione. In fase di compilazione valuterà solo le cose che sa essere totali (cioè terminando e coprendo tutti i possibili input) al fine di mantenere il controllo del tipo decidibile. Il valutatore di compilazione è parte del kernel di Idris ed è implementato in Haskell usando una rappresentazione di valori HOAS (sintassi astratta di ordine superiore). Poiché tutto è noto per avere una forma normale qui, la strategia di valutazione non conta in realtà perché in entrambi i casi otterrà la stessa risposta, e in pratica farà tutto ciò che il sistema run-time di Haskell sceglie di fare.

Il REPL, per comodità, utilizza la nozione di valutazione in fase di compilazione. Quindi, il tuo 'spin 1000' non viene mai realmente valutato. Se realizzi un eseguibile con lo stesso codice, mi aspetterei di vedere un comportamento molto diverso.

Oltre ad essere più facile da implementare (perché abbiamo a disposizione il programma di valutazione), questo può essere molto utile per mostrare come valutano i termini nel correttore di tipi. Così si può vedere la differenza tra:

Idris> \n, m => (S n) + m 
\n => \m => S (plus n m) : Nat -> Nat -> Nat 

Idris> \n, m => n + (S m) 
\n => \m => plus n (S m) : Nat -> Nat -> Nat 

Questo sarebbe più difficile (se non impossibile) se usassimo la valutazione di run-time al REPL, che non riduca in lambda.

+0

Grazie! Ho dovuto cambiare 'spin: Nat ->()' per 'spin: Nat -> Bool' (immagino che Idris abbia realizzato'() 'ha solo un abitante e ottimizzato la chiamata a' spin'), ma dopo che l'eseguibile si è preso un momento per correre indipendentemente da quale ramo del "se" è andato giù. – Snowball

+0

Sì, avrebbe cancellato il(). In realtà nel master corrente esegue un'analisi di cancellazione molto più profonda quindi probabilmente dovresti fare qualcosa come stampare il risultato di spin n per essere sicuro che sia stato valutato ... –

+1

Come mai, se è noto che 'spin 1000' è terminando e senza effetti collaterali, questa espressione non viene valutata in fase di compilazione e sostituita dal suo risultato nel codice generato, in modo che entrambe le versioni della riga 'if'' restituiscano immediatamente i loro risultati in fase di esecuzione? (Il commento di palle di neve sopra indica che non è così.) – Lii