Haskells Cabeça Fraca Forma Normal

9

Tropecei em algumas coisas irritantes. Eu sei que o haskell funciona com a forma normal da cabeça fraca (WHNF) e sei o que é isso. Digitando o seguinte código no ghci (estou usando o comando: sprint, que reduz a expressão para WHNF para meu conhecimento.):

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

intlist = _isso faz totalmente sentido para mim.

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

stringlist = [_,_] Isso já me confunde. Mas então:

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

surpreendentemente dá charlist = ["hi","there"]

Tanto quanto eu entendi Haskell, strings nada mais são do que listas de caracteres, o que parece ser confirmado verificando os tipos "hi" :: [Char]e ['h','i'] :: [Char].

Estou confuso, porque, no meu entendimento, todos os três exemplos acima são mais ou menos os mesmos (uma lista de listas) e, portanto, devem reduzir para o mesmo WHNF, ou seja, _. o que estou perdendo?

obrigado

duepiert
fonte
Isto parece estar relacionado
Bergi 04/02
@ Bergi, essas perguntas certamente estão relacionadas, mas nenhuma parece abordar o porquê "bla"e ['b','l','a']sairia diferente.
leftaroundabout
@leftaroundabout Porque "bla"poderia estar sobrecarregado, mas ['b','l','a']é conhecido por ser um String/ [Char]?
Bergi 04/02
11
@ Bergi Também pensei nisso, mas não é realmente plausível, porque também['b', 'l', 'a'] pode estar sobrecarregado , e da mesma forma "bla"só será sobrecarregado se -XOverloadedStringsestiver ligado.
leftaroundabout
2
Parece relacionado ao analisador, possivelmente específico ao GHCi? (Não sei como você testa o WHNF no código compilado pelo GHC.) As aspas parecem ser o gatilho.
chepner 04/02

Respostas:

5

Note-se que :sprintse não reduzir a uma expressão para WHNF. Se o fizesse, o seguinte daria, em 4vez de _:

Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _

Em vez disso, :sprintpega 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 :sprintcomo 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 :sprintestá 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_:

> 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 returnIOexterna :e concentrar-se na parte que começa com((\ @ a $dNum -> ...

Aqui $dNumestá o dicionário para a Numrestrição. Isso significa que o código gerado ainda não resolveu o tipo real ano tipo Num a => [[a]]; portanto, toda a expressão ainda é representada como uma chamada de função, utilizando um (dicionário para) um Numtipo apropriado . Em outras palavras, é um thunk não avaliado e obtemos:

> :sprint intlist
_

Por outro lado, especifique o tipo como Inte 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 :sprintsaí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 :sprintsaída representam artefatos dos quais partes da expressão que o GHCi considera avaliadas ( :construtores explícitos ) versus não avaliadas (os unpackCString#thunks).

KA Buhr
fonte