Haskells Débil Cabeza Forma normal

9

Me he tropezado con algunas cosas irritantes. Sé que Haskell funciona con la forma normal de la cabeza débil (WHNF) y sé qué es esto. Escribiendo el siguiente código en ghci (estoy usando el comando: sprint que reduce la expresión a WHNF que yo sepa):

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

da intlist = _esto tiene mucho sentido para mí.

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

da stringlist = [_,_] Esto ya me confunde. Pero entonces:

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

sorprendentemente da charlist = ["hi","there"]

Hasta donde entendí Haskell, las cadenas no son más que listas de caracteres, lo que parece confirmarse al verificar los tipos "hi" :: [Char]y ['h','i'] :: [Char].

Estoy confundido porque, según tengo entendido, los tres ejemplos anteriores son más o menos lo mismo (una lista de listas) y, por lo tanto, deberían reducirse al mismo WHNF, es decir, _. ¿Qué me estoy perdiendo?

Gracias

duepiert
fuente
Esto parece estar relacionado
Bergi
@Bergi esas preguntas están ciertamente relacionadas, pero ninguna parece abordar por qué "bla"y ['b','l','a']saldría de otra manera.
Leftaroundabout
@leftaroundabout Porque "bla"podría estar sobrecargado, pero ['b','l','a']se sabe que es un String/ [Char]?
Bergi
1
@Bergi También pensé en eso, pero no es realmente plausible porque también['b', 'l', 'a'] podría estar sobrecargado , y del mismo modo "bla"solo se sobrecarga si -XOverloadedStringsestá encendido.
Leftaroundabout
2
Parece relacionado con el analizador, posiblemente específico para GHCi? (No sé cómo se prueba WHNF en el código compilado por GHC). Las citas en sí mismas parecen ser el desencadenante.
Chepner

Respuestas:

5

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).

KA Buhr
fuente