Нормальна форма Haskells слабкої голови


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, а саме _. Що я пропускаю?

Дякую


Це , здається, пов'язане
Берги

@Bergi ці питання, безумовно, пов'язані, але жодне з них, схоже, не вирішує питання, чому б "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
_

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.