Zauważ, że :sprintnie nie zmniejszyć wyrażenie do WHNF. Gdyby tak było, to 4zamiast tego podano _:
Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
Zamiast tego :sprintbierze nazwę powiązania, przegląda wewnętrzną reprezentację wartości powiązania i pokazuje już „ocenione części” (tj. Części, które są konstruktorami), jednocześnie wykorzystując _jako symbol zastępczy dla nieocenionych zespołów (tj. Zawieszona leniwa funkcja połączenia). Jeśli wartość jest całkowicie nieoceniona, nie zostanie przeprowadzona ocena, nawet dla WHNF. (A jeśli wartość zostanie całkowicie oszacowana, dostaniesz to, nie tylko WHNF.)
To, co obserwujesz w swoich eksperymentach, to kombinacja polimorficznych i monomorficznych typów liczbowych, różnych wewnętrznych reprezentacji literałów łańcuchowych w porównaniu z wyraźnymi listami znaków itp. Zasadniczo obserwujesz techniczne różnice w sposobie kompilacji różnych wyrażeń literalnych do kodu bajtowego. Tak więc interpretacja tych szczegółów implementacji jako mających coś wspólnego z WHNF spowoduje beznadziejne zamieszanie. Zasadniczo powinieneś używać wyłącznie :sprintjako narzędzie do debugowania, a nie jako sposób na poznanie WHNF i semantyki oceny Haskell.
Jeśli naprawdę chcesz zrozumieć, co :sprintsię dzieje, możesz włączyć kilka flag w GHCi, aby zobaczyć, w jaki sposób obsługiwane są wyrażenia, i ostatecznie skompilować je do kodu bajtowego:
> :set -ddump-simpl -dsuppress-all -dsuppress-uniques
Następnie możemy zobaczyć powód, dla którego intlistpodajesz _:
> 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>)
[])
Możesz zignorować wywołanie returnIOzewnętrzne i zewnętrzne :i skoncentrować się na części zaczynającej się od((\ @ a $dNum -> ...
Oto $dNumsłownik Numograniczenia. Oznacza to, że wygenerowany kod nie rozwiązał jeszcze rzeczywistego typu aw tym typie Num a => [[a]], więc całe wyrażenie jest nadal reprezentowane jako wywołanie funkcji przyjmujące (słownik dla) odpowiedniego Numtypu. Innymi słowy, jest to nieoceniony kawał, a otrzymujemy:
> :sprint intlist
_
Z drugiej strony określ typ jako Int, a kod jest zupełnie inny:
> let intlist = [[1::Int,2],[2,3]]
==================== Simplified expression ====================
returnIO
(: ((: (: (I# 1#) (: (I# 2#) []))
(: (: (I# 2#) (: (I# 3#) [])) []))
`cast` <Co:6>)
[])
podobnie jak :sprintwynik:
> :sprint intlist
intlist = [[1,2],[2,3]]
Podobnie, dosłowne ciągi znaków i wyraźne listy znaków mają zupełnie inne reprezentacje:
> 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>)
[])
a różnice w danych :sprintwyjściowych reprezentują artefakty, które części wyrażenia, które GHCi uważa za ocenione ( :konstruktory jawne ) w porównaniu do nieocenionych (thunks unpackCString#).