Que signifie la forme normale de la tête faible (WHNF)? Que signifient la forme normale de la tête (HNF) et la forme normale (NF)?
Le monde réel Haskell déclare:
La fonction seq familière évalue une expression à ce que nous appelons la forme normale de tête (en abrégé HNF). Il s'arrête une fois qu'il atteint le constructeur le plus à l'extérieur (la «tête»). Ceci est distinct de la forme normale (NF), dans laquelle une expression est complètement évaluée.
Vous entendrez également les programmeurs Haskell se référer à la forme normale de tête faible (WHNF). Pour les données normales, la forme normale de la tête faible est la même que la forme normale de la tête. La différence ne se pose que pour les fonctions, et est trop abstraite pour nous concerner ici.
J'ai lu quelques ressources et définitions ( Haskell Wiki et Haskell Mail List et Free Dictionary ) mais je ne les comprends pas. Quelqu'un peut-il peut-être donner un exemple ou donner une définition de profane?
Je suppose que ce serait similaire à:
WHNF = thunk : thunk
HNF = 0 : thunk
NF = 0 : 1 : 2 : 3 : []
Comment faire seq
et se ($!)
rapporter à WHNF et HNF?
Mettre à jour
Je suis toujours confus. Je sais que certaines des réponses disent d'ignorer HNF. À la lecture des différentes définitions, il semble qu'il n'y ait pas de différence entre les données régulières dans WHNF et HNF. Cependant, il semble qu'il y ait une différence en ce qui concerne une fonction. S'il n'y avait pas de différence, pourquoi est-il seq
nécessaire foldl'
?
Un autre point de confusion provient du Haskell Wiki, qui déclare que seq
réduit à WHNF, et ne fera rien à l'exemple suivant. Ensuite, ils disent qu'ils doivent utiliser seq
pour forcer l'évaluation. Cela ne l'oblige-t-il pas à HNF?
Code débordant de pile de débutant commun:
myAverage = uncurry (/) . foldl' (\(acc, len) x -> (acc+x, len+1)) (0,0)
Les personnes qui comprennent la forme normale seq et tête faible (whnf) peuvent immédiatement comprendre ce qui ne va pas ici. (acc + x, len + 1) est déjà dans whnf, donc seq, qui réduit une valeur à whnf, n'y fait rien. Ce code créera des thunks comme l'exemple de foldl d'origine, ils seront juste à l'intérieur d'un tuple. La solution consiste simplement à forcer les composants du tuple, par exemple
myAverage = uncurry (/) . foldl' (\(acc, len) x -> acc `seq` len `seq` (acc+x, len+1)) (0,0)
la source
Réponses:
Je vais essayer de donner une explication en termes simples. Comme d'autres l'ont souligné, la forme normale de la tête ne s'applique pas à Haskell, donc je ne l'examinerai pas ici.
Forme normale
Une expression sous forme normale est entièrement évaluée, et aucune sous-expression n'a pu être évaluée plus avant (c'est-à-dire qu'elle ne contient aucun thunks non évalué).
Ces expressions sont toutes sous forme normale:
Ces expressions ne sont pas sous forme normale:
Forme normale de la tête faible
Une expression sous forme normale de tête faible a été évaluée pour le constructeur de données le plus à l'extérieur ou l'abstraction lambda (la tête ). Les sous-expressions peuvent ou non avoir été évaluées . Par conséquent, chaque expression de forme normale est également sous forme normale de tête faible, bien que l'inverse ne soit pas vrai en général.
Pour déterminer si une expression est sous une forme normale de tête faible, il suffit de regarder la partie la plus externe de l'expression. S'il s'agit d'un constructeur de données ou d'un lambda, il est sous forme normale de tête faible. Si c'est une application de fonction, ce n'est pas le cas.
Ces expressions sont sous forme normale de tête faible:
Comme mentionné, toutes les expressions de forme normale répertoriées ci-dessus sont également sous forme normale de tête faible.
Ces expressions ne sont pas en forme normale de tête faible:
Débordements de pile
L'évaluation d'une expression à une forme normale de tête faible peut nécessiter que d'autres expressions soient évaluées en premier au WHNF. Par exemple, pour évaluer
1 + (2 + 3)
au WHNF, nous devons d'abord évaluer2 + 3
. Si l'évaluation d'une seule expression conduit à un trop grand nombre de ces évaluations imbriquées, le résultat est un débordement de pile.Cela se produit lorsque vous créez une grande expression qui ne produit aucun constructeur de données ou lambdas tant qu'une grande partie de celle-ci n'a pas été évaluée. Ceux-ci sont souvent causés par ce type d'utilisation de
foldl
:Remarquez comment il doit aller assez profondément avant qu'il ne puisse obtenir l'expression sous une forme normale de tête faible.
Vous vous demandez peut-être pourquoi Haskell ne réduit pas à l'avance les expressions intérieures? C'est à cause de la paresse de Haskell. Puisqu'on ne peut pas supposer en général que chaque sous-expression sera nécessaire, les expressions sont évaluées de l'extérieur dans.
(GHC a un analyseur de rigueur qui détectera certaines situations où une sous-expression est toujours nécessaire et il peut ensuite l'évaluer à l'avance. Ce n'est qu'une optimisation, cependant, et vous ne devriez pas vous en remettre à vous pour vous sauver des débordements).
Ce type d'expression, en revanche, est totalement sûr:
Pour éviter de construire ces grandes expressions lorsque nous savons que toutes les sous-expressions devront être évaluées, nous voulons forcer les parties internes à être évaluées à l'avance.
seq
seq
est une fonction spéciale qui est utilisée pour forcer les expressions à être évaluées. Sa sémantiqueseq x y
signifie que chaque fois qu'ily
est évalué à une forme normale de tête faible, ilx
est également évalué à une forme normale de tête faible.C'est entre autres endroits utilisés dans la définition de
foldl'
, la variante stricte defoldl
.Chaque itération de
foldl'
force l'accumulateur à WHNF. Il évite donc de construire une grande expression, et il évite donc de déborder de la pile.Mais comme le mentionne l'exemple sur HaskellWiki, cela ne vous sauve pas dans tous les cas, car l'accumulateur n'est évalué qu'en WHNF. Dans l'exemple, l'accumulateur est un tuple, il ne forcera donc que l'évaluation du constructeur de tuple, et non
acc
oulen
.Pour éviter cela, nous devons faire en sorte que l'évaluation du constructeur de tuple force l'évaluation de
acc
etlen
. Nous le faisons en utilisantseq
.la source
\x -> 1 + 1
va de WHNF mais pas de HNF.seq
leurs arguments?:set +s
. Vous pouvez alors voir que celafoldl' f
finit par allouer plus de thunks quefoldl' f'
.La section sur la forme normale de Thunks and Weak Head dans la description de la paresse de Haskell Wikibooks fournit une très bonne description de WHNF avec cette représentation utile:
la source
Les programmes Haskell sont des expressions et ils sont exécutés en effectuant une évaluation .
Pour évaluer une expression, remplacez toutes les applications de fonction par leurs définitions. L'ordre dans lequel vous faites cela n'a pas beaucoup d'importance, mais il est toujours important: commencez par l'application la plus externe et continuez de gauche à droite; c'est ce qu'on appelle l' évaluation paresseuse .
Exemple:
L'évaluation s'arrête lorsqu'il n'y a plus d'applications de fonction à remplacer. Le résultat est sous forme normale (ou sous forme normale réduite , RNF). Quel que soit l'ordre dans lequel vous évaluez une expression, vous vous retrouverez toujours avec la même forme normale (mais seulement si l'évaluation se termine).
Il existe une description légèrement différente pour l'évaluation paresseuse. À savoir, il dit que vous devez tout évaluer uniquement sous forme normale de tête faible . Il y a précisément trois cas pour qu'une expression soit dans WHNF:
constructor expression_1 expression_2 ...
(+) 2
ousqrt
\x -> expression
En d'autres termes, la tête de l'expression (c'est-à-dire l'application de fonction la plus externe) ne peut plus être évaluée, mais l'argument de fonction peut contenir des expressions non évaluées.
Exemples de WHNF:
Remarques
la source
seq
à lafoldl'
force est-il l'évaluation du WHNF au HNF?seq expr1 expr2
évaluera la première expressionexpr1
en WHNF avant d'évaluer la deuxième expressionexpr2
.Une bonne explication avec des exemples est donnée à http://foldoc.org/Weak+Head+Normal+Form La forme normale de tête simplifie même les bits d'une expression à l'intérieur d'une abstraction de fonction, tandis que la forme normale de tête "faible" s'arrête aux abstractions de fonction .
De la source, si vous avez:
qui est sous forme normale de tête faible, mais pas sous forme normale de tête ... car l'application possible est coincée à l'intérieur d'une fonction qui ne peut pas encore être évaluée.
La forme normale de la tête réelle serait difficile à mettre en œuvre efficacement. Il faudrait fouiller à l'intérieur des fonctions. Ainsi, l'avantage de la forme normale de la tête faible est que vous pouvez toujours implémenter des fonctions comme un type opaque, et donc qu'il est plus compatible avec les langages compilés et l'optimisation.
la source
Le WHNF ne veut pas que le corps des lambdas soit évalué, donc
seq
veut que son premier argument soit dans WHNF, doncévalue à
au lieu de, ce qui utiliserait HNF
la source
En gros, supposons que vous avez une sorte de thunk
t
.Maintenant, si nous voulons évaluer
t
WHNF ou NHF, qui sont les mêmes sauf pour les fonctions, nous trouverions que nous obtenons quelque chose commet1 : t2
oùt1
ett2
sont les thunks. Dans ce cas, cet1
serait votre0
(ou plutôt un thunk à0
ne pas donner de déballage supplémentaire)seq
et$!
évaluer WHNF. Notez quela source