haskell lazy-evaluation evaluation expression-evaluation idris

haskell - Idris entusiasta evaluación



lazy-evaluation evaluation (1)

En Haskell , podría implementar if así:

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

Esto se comporta como espero :

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

En Racket , podría implementar un fallo if así:

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

Esto se comporta como espero :

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

En Idris , podría implementar if así:

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

Este comportamiento me sorprende :

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

Esperaba que Irdis se comportara como Racket, donde se evalúan ambos argumentos. ¡Pero ese no es el caso!

¿Cómo decide Idris cuándo evaluar las cosas?


Decimos que Idris tiene una evaluación estricta, pero esto es por su semántica en tiempo de ejecución.

Al ser un lenguaje totalmente dependiente, Idris tiene dos fases en las que evalúa las cosas, el tiempo de compilación y el tiempo de ejecución. En tiempo de compilación, solo evaluará las cosas que sabe que son totales (es decir, que terminan y cubren todas las entradas posibles) a fin de que la comprobación de tipos sea decidible. El evaluador en tiempo de compilación es parte del kernel Idris, y se implementa en Haskell utilizando una representación de valores de estilo HOAS (sintaxis abstracta de orden superior). Como se sabe que todo tiene una forma normal aquí, la estrategia de evaluación en realidad no importa porque de cualquier manera obtendrá la misma respuesta, y en la práctica hará lo que el sistema de tiempo de ejecución Haskell decida hacer.

El REPL, por conveniencia, utiliza la noción de evaluación en tiempo de compilación. Entonces, tu ''spin 1000'' nunca se está evaluando. Si haces un ejecutable con el mismo código, esperaría ver un comportamiento muy diferente.

Además de ser más fácil de implementar (porque tenemos el evaluador disponible), esto puede ser muy útil para mostrar cómo se evalúan los términos en el verificador de tipos. Entonces puedes ver la diferencia entre:

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

Esto sería más difícil (aunque no imposible) si usáramos la evaluación en tiempo de ejecución en REPL, que no se reduce en lambdas.