Haskells Weak Head Нормальная форма

9

Я наткнулся на некоторые раздражающие вещи. Я знаю, что haskell работает со слабой головой нормальной формы (WHNF), и я знаю, что это такое. Введите следующий код в ghci (я использую команду: sprint, которая, насколько мне известно, сокращает выражение до WHNF):

let intlist = [[1,2],[2,3]]
:sprint intlist

дает intlist = _это имеет смысл для меня.

let stringlist = ["hi","there"]
:sprint stringlist 

дает stringlist = [_,_] Это уже меня смущает. Но потом:

let charlist = [['h','i'], ['t','h','e','r','e']]
:sprint charlist

удивительно дает charlist = ["hi","there"]

Насколько я понял Haskell, строки - это не что иное, как списки символов, что, кажется, подтверждается проверкой типов "hi" :: [Char]и ['h','i'] :: [Char].

Я в замешательстве, потому что в моем понимании все три приведенных выше примера более или менее одинаковы (список списков) и поэтому должны сводиться к одному и тому же WHNF, а именно _. Что мне не хватает?

Спасибо

duepiert
источник
Это, кажется, связано
Берги
@ Берги эти вопросы, безусловно, связаны, но ни один из них, похоже, не решает, почему "bla"и ['b','l','a']выйдет иначе.
оставил около
@leftaroundabout Потому что "bla"может быть перегружен, но ['b','l','a']известно, что это String/ [Char]?
Берги
1
@ Bergi Я тоже об этом думал, но это не совсем правдоподобно, потому что также['b', 'l', 'a'] может быть перегружено , а также "bla"перегружено, только если -XOverloadedStringsвключено.
оставил около
2
Кажется связанным с парсером, возможно специфичным для GHCi? (Я не знаю, как вы тестируете на WHNF в GHC-скомпилированном коде.) Сами кавычки, кажется, являются триггером.
Чепнер

Ответы:

5

Обратите внимание, что :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).

К. А. Бур
источник