Що означає знак оклику в декларації Haskell?


261

Я натрапив на таке визначення, коли я намагаюся навчитися Haskell використовуючи реальний проект для його управління. Я не розумію, що означає знак оклику перед кожним аргументом, і мої книги, схоже, не згадують про це.

data MidiMessage = MidiMessage !Int !MidiMessage

13
Я підозрюю, що це може бути дуже поширеним питанням; Я чітко пам’ятаю, як собі цікавився про те саме, що колись назад.
cjs

Відповіді:


313

Це декларація суворості. По суті, це означає, що при створенні значення структури даних її слід оцінювати у тому, що називається "слабкою головою нормальної форми". Давайте подивимось на приклад, щоб ми могли побачити, що це означає:

data Foo = Foo Int Int !Int !(Maybe Int)

f = Foo (2+2) (3+3) (4+4) (Just (5+5))

fВищенаведена функція , коли оцінюється, повертає "сповіщення": тобто код, який потрібно виконати, щоб з'ясувати його значення. На той момент Foo ще не існує, а лише код.

Але в якийсь момент хтось може спробувати заглянути всередину нього, ймовірно, через відповідність шаблону:

case f of
     Foo 0 _ _ _ -> "first arg is zero"
     _           -> "first arge is something else"

Це дозволить виконати достатньо коду, щоб зробити те, що йому потрібно, і не більше. Таким чином, він створить Foo з чотирма параметрами (тому що ви не можете заглянути всередину без його існуючих). По-перше, оскільки ми її тестуємо, нам потрібно оцінити весь шлях4 , де ми розуміємо, що він не відповідає.

Друге не потрібно оцінювати, оскільки ми його не тестуємо. Таким чином, замість того , 6зберігається в цьому осередку пам'яті, ми будемо просто зберігати код для можливої подальшої оцінки, (3+3). Це перетвориться на 6, лише якщо хтось на це подивиться.

Третій параметр, однак, має !перед собою, тому суворо оцінюється: (4+4)виконується і8 зберігається в цьому місці пам'яті.

Четвертий параметр також суворо оцінюється. Але ось дещо стає складним: ми оцінюємо не повністю, а лише слабку нормальну форму голови. Це означає , що ми з'ясовуємо , чи є це Nothingабо Justщо - то, і зберігати, але ми не йти далі. Це означає, що ми зберігаємо не, Just 10а насправді Just (5+5), залишаючи гроно всередині не оціненим. Це важливо знати, хоча я думаю, що всі наслідки цього виходять за рамки цього питання.

Ви можете коментувати аргументи функцій так само, якщо ввімкнути BangPatternsрозширення мови:

f x !y = x*y

f (1+1) (2+2)поверне шматок (1+1)*4.


16
Це дуже корисно. Я не можу не задатися питанням, чи ускладнює термінологія Haskell щось складніше для того, щоб люди розуміли такі терміни, як "слабка нормальна форма голови", "сувора" тощо. Якщо я вас правильно зрозумів, це звучить як! Оператор просто означає зберігати оцінене значення виразу, а не зберігати анонімний блок, щоб потім його оцінити. Це розумна інтерпретація чи є в ній щось більше?
Девід

71
@David Питання полягає в тому, як далеко оцінити значення. Нормальна форма слабкої головки означає: оцінюйте її до тих пір, поки ви не дістанетесь до самого зовнішнього конструктора. Нормальна форма означає оцінку всього значення, поки не залишиться жодних неоцінених компонентів. Оскільки Haskell дозволяє здійснювати всілякі рівні глибини оцінювання, він має багату термінологію для опису цього. Ви не схильні знаходити ці відмінності в мовах, які підтримують лише семантику за викликом за значенням.
Дон Стюарт

8
@David: Я написав тут більш поглиблене пояснення: Haskell: Що таке нормальна форма слабкої голови? . Хоча я не згадую шаблони чубків або строгості анотацій, вони рівноцінні використанню seq.
гаммар

1
Напевне, я зрозумів: чи лінь стосується лише невідомого під час компіляції аргументів? Тобто, якщо у вихідного коду дійсно є вислів (2 + 2) , чи все-таки він буде оптимізований до 4 замість коду для додавання відомих чисел?
Привіт-Ангел

1
Привіт-Ангел, це справді залежить від того, наскільки компілятор хоче оптимізувати. Хоча ми використовуємо чубку, щоб сказати, що ми хотіли б примусити певні речі до слабкої нормальної форми голови, у нас немає (і не потрібно і не хочемо) способу сказати: "Будь ласка, переконайтеся, що ви ще не оцінювали цю грубість крок далі ». З семантичної точки зору, з урахуванням грубого "n = 2 + 2", ви навіть не можете сказати, чи певна посилання на n зараз оцінюється чи була раніше оцінена.
cjs

92

Простий спосіб побачити різницю між суворими та не строгими аргументами конструктора - це те, як вони поводяться, коли їх не визначають. Дано

data Foo = Foo Int !Int

first (Foo x _) = x
second (Foo _ y) = y

Оскільки не суворий аргумент не оцінюється second, передача даних undefinedне викликає проблем:

> second (Foo undefined 1)
1

Але суворого аргументу не може бути undefined, навіть якщо ми не використовуємо значення:

> first (Foo 1 undefined)
*** Exception: Prelude.undefined

2
Це краще, ніж відповідь Керта Сампсона, оскільки він фактично пояснює ефекти, які спостерігаються користувачем, які !символ має, а не заглиблюючись у внутрішні деталі реалізації.
Девід Грейсон

26

Я вважаю, що це анотація суворості.

Haskell - це чиста і ледача функціональна мова, але іноді накладні витрати на лінь можуть бути занадто великими або марними. Отже, щоб вирішити це, ви можете попросити компілятора повністю оцінити аргументи функції, а не розбирати обертів.

На цій сторінці є додаткова інформація: Продуктивність / Строгість .


Гм, приклад на тій сторінці, на яку ви посилаєтесь, здається, говорить про використання! при виклику функції. Але це здається інакшим, ніж розміщення його в декларації про тип. Що я пропускаю?
Девід,

Створення примірника типу - це також вираз. Ви можете розглядати конструктори типів як функції, що повертають нові екземпляри зазначеного типу, враховуючи надані аргументи.
Кріс Вест

4
Фактично, для всіх намірів і цілей конструктори типів - це функції; Ви можете частково їх застосувати, передати їх іншим функціям (наприклад, map Just [1,2,3]отримати [Просто 1, Просто 2, Просто 3]) тощо. Мені корисно думати про здатність узгоджувати відповідність з ними, а також про абсолютно неспоріднений об'єкт.
cjs
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.