Tenga en cuenta que :sprintno no reducir a una expresión WHNF. Si lo hiciera, entonces lo siguiente daría en 4lugar de _:
Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
Más bien, :sprinttoma el nombre de un enlace, atraviesa la representación interna del valor del enlace y muestra las partes ya "evaluadas" (es decir, las partes que son constructoras) mientras se usa _como marcador de posición para troncos no evaluados (es decir, la función perezosa suspendida llamadas). Si el valor no está evaluado por completo, no se realizará ninguna evaluación, ni siquiera para WHNF. (Y si el valor se evalúa por completo, obtendrá eso, no solo WHNF).
Lo que está observando en sus experimentos es una combinación de tipos numéricos polimórficos versus monomórficos, diferentes representaciones internas para literales de cadena versus listas explícitas de caracteres, etc. Básicamente, está observando diferencias técnicas en cómo se compilan diferentes expresiones literales para el código de bytes. Por lo tanto, interpretar estos detalles de implementación como algo que tiene que ver con WHNF te va a confundir irremediablemente. En general, debe usarlo solo :sprintcomo una herramienta de depuración, no como una forma de aprender sobre WHNF y la semántica de la evaluación de Haskell.
Si realmente quiere entender lo que :sprintestá haciendo, puede activar algunos indicadores en GHCi para ver cómo se manejan las expresiones y, finalmente, compilarse en bytecode:
> :set -ddump-simpl -dsuppress-all -dsuppress-uniques
Después de esto, podemos ver la razón por la que intlistda _:
> 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>)
[])
Puede ignorar el returnIOy la :llamada externa , y concentrarse en la parte que comienza con((\ @ a $dNum -> ...
Aquí $dNumestá el diccionario para la Numrestricción. Esto significa que el código generado aún no ha resuelto el tipo real aen el tipo Num a => [[a]], por lo que toda la expresión todavía se representa como una llamada de función que toma un (diccionario para) un Numtipo apropiado . En otras palabras, es un thunk no evaluado, y obtenemos:
> :sprint intlist
_
Por otro lado, especifique el tipo como Int, y el código es completamente diferente:
> let intlist = [[1::Int,2],[2,3]]
==================== Simplified expression ====================
returnIO
(: ((: (: (I# 1#) (: (I# 2#) []))
(: (: (I# 2#) (: (I# 3#) [])) []))
`cast` <Co:6>)
[])
y también lo es la :sprintsalida:
> :sprint intlist
intlist = [[1,2],[2,3]]
Del mismo modo, las cadenas literales y las listas explícitas de caracteres tienen representaciones 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>)
[])
y las diferencias en la :sprintsalida representan artefactos de los cuales partes de la expresión GHCi considera evaluados ( :constructores explícitos ) versus no evaluados (los unpackCString#thunks).