Зауважте, що :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
_
З іншого боку, вкажіть тип як 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#
громи).