haskell - pagina - ¿Cómo funciona seq force?
vim pagina oficial (2)
Fondo
Esta pregunta surge de un desafío que Brent Yorgey planteó en OPLSS: escribe una función f :: (Int -> Int) -> Bool
que distingue f undefined
de f (/x -> undefined)
. Todas nuestras respuestas utilizaron seq
o algo así como patrones de explosión que desaparecen en seq
. Por ejemplo:
f :: (Int -> Int) -> Bool
f g = g `seq` True
*Main> f undefined
*** Exception: Prelude.undefined
*Main> f (/x -> undefined)
True
El comentario de GHC sobre seq
dice que
e1 `seq` e2
solía desugar en
case e1 of { _ -> e2 }
Así que traté de desugaring manualmente. No funcionó:
f'' g = case g of { _ -> True }
*Main> f'' undefined
True
*Main> f'' (/x -> undefined)
True
Pregunta
¿Depende este comportamiento de la secuencia más compleja que se describe al final del comentario y, de ser así, cómo funciona? ¿Podría escribirse tal f
sin estos primitivos?
x `seq` e2 ==> case seq# x RW of (# x, _ #) -> e2 -- Note shadowing!
e1 `seq` e2 ==> case seq# x RW of (# _, _ #) -> e2
Hay una descripción de nivel más alto de la máquina STG en Cómo hacer un curry rápido: push / enter vs eval / apply
La figura 2 contiene la regla CASEANY que funciona para las funciones. En este documento, la proposición "es un valor" significa:
- Es una aplicación constructora saturada.
- es una funcion
- es una aplicación de función parcial (que sigue siendo una función, semánticamente)
Los valores de Unboxed, incluidos los literales, se tratan de forma especial; se puede encontrar más información en los valores de Unboxed como ciudadanos de primera clase.
Todos estos son detalles de implementación y están ocultos dentro del compilador (GHC). La expresión de caso de Haskell no obliga a su escrutador, solo lo hacen los patrones y la secuencia.
seq
no se puede implementar en Haskell. En su lugar, es un "gancho" primitivo en la evaluación a la forma normal de cabeza débil en cualquier tiempo de ejecución en el que se esté ejecutando Haskell. Por ejemplo, en GHC se compila a un case
en GHC Core, que desencadena la evaluación al constructor más externo.
Como no se puede implementar en Haskell puro, se define (en GHC) como primop:
pseudoop "seq"
a -> b -> b
{ Evaluates its first argument to head normal form, and then returns its second
argument as the result. }
Dado que las funciones no tienen una forma normal, se detiene la evaluación una vez que alcanza una.
Mágicamente disponible para el compilador. Lo mismo ocurre con otras primitivas como par
o unsafeCoerce
, el token de RealWorld
, forkOn
, etc. Todas las cosas útiles.