Обратите внимание, что :sprint
это не уменьшает выражение до WHNF. Если бы это было так, то 4
вместо этого было бы следующее _
:
Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
Скорее, он :sprint
берет имя привязки, пересекает внутреннее представление значения привязки и показывает уже «вычисленные части» (т.е. части, которые являются конструкторами) при использовании _
в качестве заполнителя для неоцененных блоков (т. Е. Приостановленной ленивой функции). звонки). Если значение полностью не оценено, оценка не будет выполнена, даже для WHNF. (И если значение будет полностью оценено, вы получите это, а не только WHNF.)
В ваших экспериментах вы наблюдаете сочетание полиморфных и мономорфных числовых типов, различных внутренних представлений для строковых литералов и явных списков символов и т. Д. В основном вы наблюдаете технические различия в том, как различные литеральные выражения компилируются в байт-код. Таким образом, интерпретация этих деталей реализации как имеющих отношение к WHNF может безнадежно запутать вас. Как правило, вы должны использовать только :sprint
как инструмент отладки, а не как способ узнать о WHNF и семантике оценки Haskell.
Если вы действительно хотите понять, что :sprint
происходит, вы можете включить несколько флагов в GHCi, чтобы увидеть, как на самом деле обрабатываются выражения и, таким образом, в конечном итоге компилируется в байт-код:
> :set -ddump-simpl -dsuppress-all -dsuppress-uniques
После этого мы можем увидеть причину, по которой вы 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>)
[])
Вы можете игнорировать returnIO
внешний и внешний :
вызов и сосредоточиться на той части, которая начинается с((\ @ a $dNum -> ...
Вот $dNum
словарь для Num
ограничения. Это означает, что сгенерированный код еще не разрешил фактический тип a
в типе Num a => [[a]]
, поэтому все выражение по-прежнему представляется как вызов функции, принимающей (словарь для) соответствующий Num
тип. Другими словами, это неоцененный отрывок, и мы получаем:
> :sprint intlist
_
С другой стороны, укажите тип as Int
, и код будет совершенно другим:
> let intlist = [[1::Int,2],[2,3]]
==================== Simplified expression ====================
returnIO
(: ((: (: (I# 1#) (: (I# 2#) []))
(: (: (I# 2#) (: (I# 3#) [])) []))
`cast` <Co:6>)
[])
и так :sprint
вывод:
> :sprint intlist
intlist = [[1,2],[2,3]]
Аналогично, буквенные строки и явные списки символов имеют совершенно разные представления:
> 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>)
[])
и различия в :sprint
выходных данных представляют собой артефакты, которые части выражения GHCi считает оцененными (явные :
конструкторы) по сравнению с неоцененными ( unpackCString#
thunks).