Forme normale de tête faible de Haskells

9

J'ai trébuché sur des choses irritantes. Je sais que haskell fonctionne avec une forme normale de tête faible (WHNF) et je sais ce que c'est. Taper le code suivant dans ghci (j'utilise la commande: sprint qui réduit l'expression à WHNF à ma connaissance.):

let intlist = [[1,2],[2,3]]
:sprint intlist

intlist = _cela me donne tout son sens.

let stringlist = ["hi","there"]
:sprint stringlist 

donne stringlist = [_,_] Cela me confond déjà. Mais alors:

let charlist = [['h','i'], ['t','h','e','r','e']]
:sprint charlist

donne étonnamment charlist = ["hi","there"]

Pour autant que j'ai compris Haskell, les chaînes ne sont rien d'autre que des listes de caractères, ce qui semble être confirmé en vérifiant les types "hi" :: [Char]et ['h','i'] :: [Char].

Je suis confus, car à ma connaissance, les trois exemples ci-dessus sont plus ou moins les mêmes (une liste de listes) et devraient donc se réduire au même WHNF, à savoir _. Qu'est-ce que je rate?

Merci

duepiert
la source
Cela semble être lié
Bergi
@Bergi, ces questions sont certainement liées, mais aucune ne semble expliquer pourquoi "bla"et ['b','l','a']sortirait différemment.
leftaroundabout
@leftaroundabout Parce que "bla"pourrait être surchargé, mais ['b','l','a']est connu pour être un String/ [Char]?
Bergi
1
@Bergi J'y ai pensé aussi, mais ce n'est pas vraiment plausible car il ['b', 'l', 'a']pourrait également être surchargé , et "bla"n'est également surchargé que s'il -XOverloadedStringsest activé.
leftaroundabout
2
Semble lié à l'analyseur, peut-être spécifique à GHCi? (Je ne sais pas comment vous testez WHNF dans le code compilé par GHC.) Les guillemets eux-mêmes semblent être le déclencheur.
chepner

Réponses:

5

Notez que :sprintcela ne réduit pas une expression à WHNF. Si c'était le cas, alors ce qui suit donnerait 4plutôt que _:

Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _

Prend plutôt :sprintle nom d'une liaison, parcourt la représentation interne de la valeur de la liaison et montre les "parties déjà évaluées" (c'est-à-dire les parties qui sont des constructeurs) tout en l'utilisant _comme espace réservé pour les thunks non évalués (c'est-à-dire la fonction paresseuse suspendue appels). Si la valeur est complètement non évaluée, aucune évaluation ne sera effectuée, pas même pour WHNF. (Et si la valeur est complètement évaluée, vous l'obtiendrez, pas seulement WHNF.)

Ce que vous observez dans vos expériences est une combinaison de types numériques polymorphes et monomorphes, de différentes représentations internes pour les littéraux de chaîne par rapport à des listes explicites de caractères, etc. Donc, interpréter ces détails d'implémentation comme ayant quelque chose à voir avec WHNF va désespérément vous confondre. En règle générale, vous devez :sprintuniquement utiliser un outil de débogage, et non pas un moyen d'en savoir plus sur WHNF et la sémantique de l'évaluation Haskell.

Si vous voulez vraiment comprendre ce qui :sprintse passe, vous pouvez activer quelques indicateurs dans GHCi pour voir comment les expressions sont réellement gérées et, par conséquent, éventuellement compilées en bytecode:

> :set -ddump-simpl -dsuppress-all -dsuppress-uniques

Après cela, nous pouvons voir la raison pour laquelle vous intlistdonne _:

> let intlist = [[1,2],[2,3]]
==================== Simplified expression ====================
returnIO
  (: ((\ @ a $dNum ->
         : (: (fromInteger $dNum 1) (: (fromInteger $dNum 2) []))
           (: (: (fromInteger $dNum 2) (: (fromInteger $dNum 3) [])) []))
      `cast` <Co:10>)
     [])

Vous pouvez ignorer l' appel returnIOexterne :et vous concentrer sur la partie commençant par((\ @ a $dNum -> ...

Voici $dNumle dictionnaire de la Numcontrainte. Cela signifie que le code généré n'a pas encore résolu le type réel adans le type Num a => [[a]], donc l'expression entière est toujours représentée comme un appel de fonction prenant un (dictionnaire pour) un Numtype approprié . En d'autres termes, c'est un thunk non évalué, et nous obtenons:

> :sprint intlist
_

D'un autre côté, spécifiez le type en tant que Int, et le code est complètement différent:

> let intlist = [[1::Int,2],[2,3]]
==================== Simplified expression ====================
returnIO
  (: ((: (: (I# 1#) (: (I# 2#) []))
         (: (: (I# 2#) (: (I# 3#) [])) []))
      `cast` <Co:6>)
     [])

et la :sprintsortie aussi:

> :sprint intlist
intlist = [[1,2],[2,3]]

De même, les chaînes littérales et les listes explicites de caractères ont des représentations complètement différentes:

> let stringlist = ["hi", "there"]
==================== Simplified expression ====================
returnIO
  (: ((: (unpackCString# "hi"#) (: (unpackCString# "there"#) []))
      `cast` <Co:6>)
     [])

> let charlist = [['h','i'], ['t','h','e','r','e']]
==================== Simplified expression ====================
returnIO
  (: ((: (: (C# 'h'#) (: (C# 'i'#) []))
         (: (: (C# 't'#)
               (: (C# 'h'#) (: (C# 'e'#) (: (C# 'r'#) (: (C# 'e'#) [])))))
            []))
      `cast` <Co:6>)
     [])

et les différences dans la :sprintsortie représentent des artefacts dont des parties de l'expression que GHCi considère évaluées ( :constructeurs explicites ) par opposition à non évaluées (les unpackCString#thunks).

KA Buhr
la source