Si noti che :sprint
non non riduce l'espressione di WHNF. Se lo facesse, allora darebbe 4
piuttosto che _
:
Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
Piuttosto, :sprint
prende 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 :sprint
come strumento di debug, non come modo per conoscere WHNF e la semantica della valutazione di Haskell.
Se vuoi davvero capire cosa :sprint
sta 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
dà _
:
> 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 returnIO
esterna :
e e concentrarsi sulla parte che inizia con((\ @ a $dNum -> ...
Ecco $dNum
il dizionario per il Num
vincolo. Ciò significa che il codice generato non ha ancora risolto il tipo effettivo a
nel tipo Num a => [[a]]
, quindi l'intera espressione è ancora rappresentata come una chiamata di funzione che prende un (dizionario per) un Num
tipo appropriato . In altre parole, è un thunk non valutato e otteniamo:
> :sprint intlist
_
D'altra parte, specificare il tipo come Int
e 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' :sprint
output:
> :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 :sprint
nell'output rappresentano artefatti di cui parti dell'espressione che GHCi considera valutate ( :
costruttori espliciti ) rispetto a non valutate (i unpackCString#
thunk).