¿Qué significa la forma normal de la cabeza débil (WHNF)? ¿Qué significa Head Normal form (HNF) y Normal Form (NF)?
El mundo real Haskell afirma:
La función seq familiar evalúa una expresión a lo que llamamos forma normal de la cabeza (abreviado HNF). Se detiene una vez que alcanza el constructor más externo (la "cabeza"). Esto es distinto de la forma normal (NF), en la que una expresión se evalúa por completo.
También escuchará a los programadores de Haskell referirse a la forma normal de la cabeza débil (WHNF). Para datos normales, la forma normal de la cabeza débil es igual a la forma normal de la cabeza. La diferencia solo surge para las funciones, y es demasiado abstrusa para preocuparnos aquí.
He leído algunos recursos y definiciones ( Haskell Wiki y Haskell Mail List and Free Dictionary ) pero no lo entiendo. ¿Puede alguien dar un ejemplo o proporcionar una definición laica?
Supongo que sería similar a:
WHNF = thunk : thunk
HNF = 0 : thunk
NF = 0 : 1 : 2 : 3 : []
¿Cómo seq
y cómo ($!)
relacionarse con WHNF y HNF?
Actualizar
Todavía estoy confundido Sé que algunas de las respuestas dicen ignorar HNF. Al leer las diversas definiciones, parece que no hay diferencia entre los datos regulares en WHNF y HNF. Sin embargo, parece que hay una diferencia cuando se trata de una función. Si no hubo diferencia, ¿por qué es seq
necesario foldl'
?
Otro punto de confusión es el de Haskell Wiki, que establece que se seq
reduce a WHNF y no hará nada con el siguiente ejemplo. Luego dicen que tienen que usar seq
para forzar la evaluación. ¿No es eso forzarlo a HNF?
Código de desbordamiento de pila de novato común:
myAverage = uncurry (/) . foldl' (\(acc, len) x -> (acc+x, len+1)) (0,0)
Las personas que entienden seq y la forma normal de la cabeza débil (whnf) pueden entender de inmediato lo que sale mal aquí. (acc + x, len + 1) ya está en whnf, por lo que seq, que reduce un valor a whnf, no hace nada al respecto. Este código creará thunks al igual que el ejemplo original de foldl, solo estarán dentro de una tupla. La solución es forzar los componentes de la tupla, por ejemplo
myAverage = uncurry (/) . foldl' (\(acc, len) x -> acc `seq` len `seq` (acc+x, len+1)) (0,0)
fuente
Respuestas:
Trataré de dar una explicación en términos simples. Como otros han señalado, la forma normal de la cabeza no se aplica a Haskell, por lo que no lo consideraré aquí.
Forma normal
Una expresión en forma normal se evalúa completamente, y no se puede evaluar más la subexpresión (es decir, no contiene thunks no evaluados).
Estas expresiones están todas en forma normal:
Estas expresiones no están en forma normal:
Cabeza débil de forma normal
Se ha evaluado una expresión en forma normal de cabeza débil para el constructor de datos más externo o la abstracción lambda (la cabeza ). Las subexpresiones pueden o no haber sido evaluadas . Por lo tanto, cada expresión de forma normal también está en forma normal de cabeza débil, aunque lo contrario no se cumple en general.
Para determinar si una expresión está en forma normal de cabeza débil, solo tenemos que mirar la parte más externa de la expresión. Si es un constructor de datos o una lambda, está en forma normal de cabeza débil. Si es una aplicación de función, no lo es.
Estas expresiones están en forma normal de cabeza débil:
Como se mencionó, todas las expresiones de forma normal enumeradas anteriormente también están en forma normal de cabeza débil.
Estas expresiones no están en forma normal de cabeza débil:
La pila se desborda
La evaluación de una expresión en forma normal de cabeza débil puede requerir que otras expresiones se evalúen primero a WHNF. Por ejemplo, para evaluar
1 + (2 + 3)
a WHNF, primero tenemos que evaluar2 + 3
. Si evaluar una sola expresión lleva a demasiadas de estas evaluaciones anidadas, el resultado es un desbordamiento de la pila.Esto sucede cuando construye una expresión grande que no produce ningún constructor de datos o lambdas hasta que se haya evaluado una gran parte de ella. Estos a menudo son causados por este tipo de uso de
foldl
:Observe cómo tiene que ir bastante profundo antes de que pueda obtener la expresión en forma normal de cabeza débil.
Te preguntarás, ¿por qué Haskell no reduce las expresiones internas antes de tiempo? Eso se debe a la pereza de Haskell. Como no se puede suponer en general que se necesitará cada subexpresión, las expresiones se evalúan desde afuera hacia adentro.
(GHC tiene un analizador de rigurosidad que detectará algunas situaciones en las que siempre se necesita una subexpresión y luego puede evaluarla con anticipación. Sin embargo, esto es solo una optimización y no debe confiar en ella para evitar desbordamientos).
Este tipo de expresión, por otro lado, es completamente seguro:
Para evitar construir estas expresiones grandes cuando sabemos que todas las subexpresiones tendrán que ser evaluadas, queremos forzar a las partes internas a ser evaluadas con anticipación.
seq
seq
es una función especial que se usa para forzar que se evalúen expresiones. Su semánticaseq x y
significa que siempre quey
se evalúa en forma normal de cabeza débil,x
también se evalúa en forma normal de cabeza débil.Se encuentra entre otros lugares utilizados en la definición de
foldl'
, la variante estricta defoldl
.Cada iteración de
foldl'
fuerza el acumulador a WHNF. Por lo tanto, evita construir una expresión grande y, por lo tanto, evita desbordar la pila.Pero como menciona el ejemplo en HaskellWiki, esto no lo salva en todos los casos, ya que el acumulador solo se evalúa a WHNF. En el ejemplo, el acumulador es una tupla, por lo que solo forzará la evaluación del constructor de tuplas, y no
acc
olen
.Para evitar esto, debemos hacerlo de manera que la evaluación del constructor de tuplas fuerce la evaluación de
acc
ylen
. Hacemos esto usandoseq
.fuente
\x -> 1 + 1
es WHNF pero no HNF.seq
sus argumentos?:set +s
. Luego puede ver quefoldl' f
termina asignando más thunks quefoldl' f'
.La sección sobre Thunks and Weak Head Normal Form en la descripción de holgazanería de Haskell Wikibooks proporciona una muy buena descripción de WHNF junto con esta útil descripción:
fuente
Los programas Haskell son expresiones y se ejecutan mediante la evaluación .
Para evaluar una expresión, reemplace todas las aplicaciones de función por sus definiciones. El orden en el que hace esto no importa mucho, pero sigue siendo importante: comience con la aplicación más externa y continúe de izquierda a derecha; Esto se llama evaluación perezosa .
Ejemplo:
La evaluación se detiene cuando no quedan más aplicaciones de función para reemplazar. El resultado está en forma normal (o forma normal reducida , RNF). No importa en qué orden evalúe una expresión, siempre terminará con la misma forma normal (pero solo si la evaluación finaliza).
Hay una descripción ligeramente diferente para la evaluación perezosa. A saber, dice que debe evaluar todo a la forma normal de la cabeza débil solamente. Hay exactamente tres casos para que una expresión esté en WHNF:
constructor expression_1 expression_2 ...
(+) 2
osqrt
\x -> expression
En otras palabras, el encabezado de la expresión (es decir, la aplicación de la función más externa) no puede evaluarse más, pero el argumento de la función puede contener expresiones no evaluadas.
Ejemplos de WHNF:
Notas
fuente
seq
en lafoldl'
fuerza de la evaluación de WHNF a HNF?seq expr1 expr2
evaluará la primera expresiónexpr1
para WHNF antes de evaluar la segunda expresiónexpr2
.Se proporciona una buena explicación con ejemplos en http://foldoc.org/Weak+Head+Normal+Form Head. La forma normal de la cabeza simplifica incluso los bits de una expresión dentro de una abstracción de función, mientras que la forma normal de la cabeza "débil" se detiene en las abstracciones de la función. .
De la fuente, si tiene:
eso está en forma normal de cabeza débil, pero no en forma normal de cabeza ... porque la posible aplicación está atrapada dentro de una función que aún no se puede evaluar.
La forma normal de la cabeza real sería difícil de implementar de manera eficiente. Requeriría hurgar dentro de las funciones. Por lo tanto, la ventaja de la forma normal de la cabeza débil es que aún puede implementar funciones como un tipo opaco y, por lo tanto, es más compatible con los lenguajes compilados y la optimización.
fuente
El WHNF no quiere que se evalúe el cuerpo de lambdas, por lo que
seq
quiere que su primer argumento esté en WHNF, entoncesevalúa a
en lugar de, qué estaría usando HNF
fuente
Básicamente, suponga que tiene algún tipo de golpe seco,
t
.Ahora, si queremos evaluar
t
a WHNF o NHF, que son lo mismo, excepto para las funciones, encontraremos que obtenemos algo comot1 : t2
dondet1
yt2
son thunks. En este caso,t1
sería su0
(o más bien, un golpe para0
no dar unboxing adicional)seq
y$!
evaluar WHNF. Tenga en cuenta quefuente