Forma normale testa debole di Haskells


9

Mi sono imbattuto in alcune cose irritanti. So che haskell funziona con la forma normale della testa debole (WHNF) e so di cosa si tratta. Digitando il seguente codice in ghci (sto usando il comando: sprint che riduce l'espressione a WHNF a mia conoscenza.):

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

intlist = _questo ha perfettamente senso per me.

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

stringlist = [_,_] Questo mi confonde già. Ma allora:

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

dà sorprendentemente charlist = ["hi","there"]

Per quanto ho capito Haskell, le stringhe non sono altro che elenchi di caratteri, che sembra essere confermato controllando i tipi "hi" :: [Char]e ['h','i'] :: [Char].

Sono confuso, perché nella mia comprensione tutti e tre gli esempi sopra sono più o meno gli stessi (un elenco di elenchi) e quindi dovrebbero ridursi allo stesso WHNF, vale a dire _. Cosa mi sto perdendo?

Grazie


Questo sembra essere correlato
Bergi il

@Bergi quelle domande sono sicuramente correlate, ma nessuna delle due sembra affrontare il perché "bla"e ['b','l','a']verrebbe fuori in modo diverso.
lasciato il

@leftaroundabout Perché "bla"potrebbe essere sovraccarico, ma ['b','l','a']è noto per essere un String/ [Char]?
Bergi,

1
@Bergi Ci ho pensato anche io, ma non è davvero plausibile perché ['b', 'l', 'a']potrebbe anche essere sovraccarico , e allo stesso modo "bla"è sovraccarico solo se -XOverloadedStringsè acceso.
lasciato il

2
Sembra correlato al parser, forse specifico per GHCi? (Non so come testare WHNF nel codice compilato GHC.) Le citazioni stesse sembrano essere il fattore scatenante.
Chepner

Risposte:


5

Si noti che :sprintnon non riduce l'espressione di WHNF. Se lo facesse, allora darebbe 4piuttosto che _:

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

Piuttosto, :sprintprende il nome di una rilegatura, attraversa la rappresentazione interna del valore della rilegatura e mostra le "parti valutate" (ovvero le parti che sono costruttori) già durante l'utilizzo_ come segnaposto per thunk non valutati (ovvero la funzione pigra sospesa chiamate). Se il valore è completamente sottovalutato, non verrà effettuata alcuna valutazione, nemmeno a WHNF. (E se il valore è completamente valutato, lo otterrai, non solo WHNF.)

Quello che stai osservando nei tuoi esperimenti è una combinazione di tipi numerici polimorfici contro monomorfi, diverse rappresentazioni interne per letterali stringa rispetto a elenchi espliciti di caratteri, ecc. Fondamentalmente, stai osservando differenze tecniche nel modo in cui diverse espressioni letterali vengono compilate in codice byte. Quindi, interpretare questi dettagli di implementazione come qualcosa che ha a che fare con WHNF ti confonderà irrimediabilmente. In generale, è necessario utilizzare solo :sprintcome strumento di debug, non come modo per conoscere WHNF e la semantica della valutazione di Haskell.

Se vuoi davvero capire cosa :sprintsta facendo, puoi attivare alcune flag in GHCi per vedere come vengono effettivamente gestite le espressioni e, quindi, compilate in bytecode:

> :set -ddump-simpl -dsuppress-all -dsuppress-uniques

Dopo questo, possiamo vedere il motivo per il vostro 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>)
     [])

È possibile ignorare la chiamata returnIOesterna :e e concentrarsi sulla parte che inizia con((\ @ a $dNum -> ...

Ecco $dNumil dizionario per il Numvincolo. Ciò significa che il codice generato non ha ancora risolto il tipo effettivo anel tipo Num a => [[a]], quindi l'intera espressione è ancora rappresentata come una chiamata di funzione che prende un (dizionario per) un Numtipo appropriato . In altre parole, è un thunk non valutato e otteniamo:

> :sprint intlist
_

D'altra parte, specificare il tipo come Inte il codice è completamente diverso:

> let intlist = [[1::Int,2],[2,3]]
==================== Simplified expression ====================
returnIO
  (: ((: (: (I# 1#) (: (I# 2#) []))
         (: (: (I# 2#) (: (I# 3#) [])) []))
      `cast` <Co:6>)
     [])

e così è l' :sprintoutput:

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

Allo stesso modo, stringhe letterali ed elenchi espliciti di caratteri hanno rappresentazioni completamente diverse:

> 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 le differenze :sprintnell'output rappresentano artefatti di cui parti dell'espressione che GHCi considera valutate ( :costruttori espliciti ) rispetto a non valutate (i unpackCString#thunk).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.