Note-se que :sprint
se não reduzir a uma expressão para WHNF. Se assim fosse, o seguinte daria em 4
vez de _
:
Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
Em vez disso, :sprint
recebe o nome de uma ligação, atravessa a representação interna do valor da ligação e mostra as "partes já avaliadas" (ou seja, as partes que são construtores), enquanto usa _
como um marcador para thunks não avaliados (ou seja, a função preguiçosa suspensa chamadas). Se o valor for totalmente desvalorizado, nenhuma avaliação será feita, nem mesmo para o WHNF. (E se o valor for completamente avaliado, você obterá isso, não apenas WHNF.)
O que você está observando em seus experimentos é uma combinação de tipos numéricos polimórficos versus monomórficos, diferentes representações internas para strings literais versus listas explícitas de caracteres, etc. Basicamente, você está observando diferenças técnicas em como diferentes expressões literais são compiladas em código de bytes. Portanto, interpretar esses detalhes de implementação como tendo algo a ver com WHNF vai confundi-lo desesperadamente. Geralmente, você deve usar apenas :sprint
como uma ferramenta de depuração, não como uma forma de aprender sobre o WHNF e a semântica da avaliação de Haskell.
Se você realmente deseja entender o que :sprint
está acontecendo, pode ativar alguns sinalizadores no GHCi para ver como as expressões estão realmente sendo tratadas e, portanto, eventualmente compiladas para bytecode:
> :set -ddump-simpl -dsuppress-all -dsuppress-uniques
Depois disso, podemos ver a razão do seu intlist
dá _
:
> 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>)
[])
Você pode ignorar o returnIO
e a :
chamada externa e se concentrar na parte que começa com((\ @ a $dNum -> ...
Aqui $dNum
está o dicionário da Num
restrição. Isso significa que o código gerado ainda não resolveu o tipo real a
no tipo Num a => [[a]]
, portanto, a expressão inteira ainda é representada como uma chamada de função tomando um (dicionário para) um Num
tipo apropriado . Em outras palavras, é uma ideia não avaliada e obtemos:
> :sprint intlist
_
Por outro lado, especifique o tipo como Int
e o código será completamente diferente:
> let intlist = [[1::Int,2],[2,3]]
==================== Simplified expression ====================
returnIO
(: ((: (: (I# 1#) (: (I# 2#) []))
(: (: (I# 2#) (: (I# 3#) [])) []))
`cast` <Co:6>)
[])
e assim é a :sprint
saída:
> :sprint intlist
intlist = [[1,2],[2,3]]
Da mesma forma, strings literais e listas explícitas de caracteres têm representações completamente diferentes:
> 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>)
[])
e as diferenças na :sprint
saída representam artefatos de quais partes da expressão GHCi considera avaliadas ( :
construtores explícitos ) versus não avaliados (os unpackCString#
thunks).