Note-se que :sprint
se não reduzir a uma expressão para WHNF. Se o fizesse, o seguinte daria, em 4
vez de _
:
Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
Em vez disso, :sprint
pega o nome de uma ligação, percorre a representação interna do valor da ligação e mostra as já "partes avaliadas" (ou seja, as partes que são construtoras) enquanto usa _
como espaço reservado para thunks não avaliados (ou seja, a função lenta suspensa) chamadas). Se o valor for completamente não avaliado, nenhuma avaliação será feita, nem mesmo para o WHNF. (E se o valor for completamente avaliado, você obterá isso, não apenas o 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 literais de string versus listas explícitas de caracteres etc. Basicamente, você está observando diferenças técnicas na forma como diferentes expressões literais são compiladas para código de bytes. Portanto, interpretar esses detalhes de implementação como algo que tem a ver com o WHNF vai confundir irremediavelmente você. Geralmente, você deve usar apenas :sprint
como uma ferramenta de depuração, não como uma maneira de aprender sobre o WHNF e a semântica da avaliação Haskell.
Se você realmente quer entender o que :sprint
está fazendo, pode ativar alguns sinalizadores no GHCi para ver como as expressões estão realmente sendo tratadas e, portanto, eventualmente compiladas no bytecode:
> :set -ddump-simpl -dsuppress-all -dsuppress-uniques
Depois disso, podemos ver o motivo pelo qual você 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 a chamada returnIO
externa :
e concentrar-se na parte que começa com((\ @ a $dNum -> ...
Aqui $dNum
está o dicionário para a Num
restrição. Isso significa que o código gerado ainda não resolveu o tipo real a
no tipo Num a => [[a]]
; portanto, toda a expressão ainda é representada como uma chamada de função, utilizando um (dicionário para) um Num
tipo apropriado . Em outras palavras, é um thunk não avaliado e obtemos:
> :sprint intlist
_
Por outro lado, especifique o tipo como Int
e o código é 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, cadeias 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 dos quais partes da expressão que o GHCi considera avaliadas ( :
construtores explícitos ) versus não avaliadas (os unpackCString#
thunks).