Tenga en cuenta que :sprint
no no reducir a una expresión WHNF. Si lo hiciera, entonces lo siguiente daría en 4
lugar de _
:
Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
Más bien, :sprint
toma 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 :sprint
como 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 :sprint
está 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 intlist
da _
:
> 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 returnIO
y la :
llamada externa , y concentrarse en la parte que comienza con((\ @ a $dNum -> ...
Aquí $dNum
está el diccionario para la Num
restricción. Esto significa que el código generado aún no ha resuelto el tipo real a
en 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 Num
tipo 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 :sprint
salida:
> :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 :sprint
salida representan artefactos de los cuales partes de la expresión GHCi considera evaluados ( :
constructores explícitos ) versus no evaluados (los unpackCString#
thunks).