Zauważ, że :sprint
nie nie zmniejszyć wyrażenie do WHNF. Gdyby tak było, to 4
zamiast tego podano _
:
Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
Zamiast tego :sprint
bierze 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 :sprint
jako narzędzie do debugowania, a nie jako sposób na poznanie WHNF i semantyki oceny Haskell.
Jeśli naprawdę chcesz zrozumieć, co :sprint
się 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 intlist
podajesz _
:
> 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 returnIO
zewnętrzne i zewnętrzne :
i skoncentrować się na części zaczynającej się od((\ @ a $dNum -> ...
Oto $dNum
słownik Num
ograniczenia. Oznacza to, że wygenerowany kod nie rozwiązał jeszcze rzeczywistego typu a
w tym typie Num a => [[a]]
, więc całe wyrażenie jest nadal reprezentowane jako wywołanie funkcji przyjmujące (słownik dla) odpowiedniego Num
typu. 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 :sprint
wynik:
> :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 :sprint
wyjś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#
).