Що означає синтаксис "Просто" у Haskell?


118

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

Ось базовий фрагмент коду від Real World Haskell, який використовується Just. Я розумію, що робить код, але не розумію, у чому полягає мета чи функція Just.

lend amount balance = let reserve    = 100
                      newBalance = balance - amount
                  in if balance < reserve
                     then Nothing
                     else Just newBalance

З того, що я зауважив, це пов’язано з Maybeнабором тексту, але це майже все, що мені вдалося навчитися.

Гарне пояснення того, які Justзасоби були б дуже вдячні.

Відповіді:


211

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

Що може бути, структурно

Визначення виглядає приблизно так:

data Maybe a = Just a
             | Nothing

Ця декларація визначає тип, Maybe aякий параметризується змінною типу a, що означає, що ви можете використовувати його з будь-яким типом замість a.

Побудова та руйнування

Тип має два конструктори, Just aі Nothing. Якщо тип має кілька конструкторів, це означає, що значення типу повинно бути побудоване лише з одним із можливих конструкторів. Для цього типу значення було побудовано через JustабоNothing інших можливостей (без помилок) немає.

Оскільки Nothingне має типу параметрів, коли він використовується як конструктор, він називає постійне значення, яке є членом типу Maybe aдля всіх типів a. Але у Justконструктора є параметр типу, що означає, що при використанні в якості конструктора він діє як функція від типу aдо Maybe a, тобто має типa -> Maybe a

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

Щоб використовувати Maybe aзначення у відповідності шаблонів, потрібно надати шаблон для кожного конструктора, наприклад:

case maybeVal of
    Nothing   -> "There is nothing!"
    Just val  -> "There is a value, and it is " ++ (show val)

У цьому випадку вираз, перший візерунок збігався б, якщо значення було Nothing, а другий збігався б, якщо значення було побудовано з Just. Якщо другий збігається, він також пов'язує ім'я valз параметром, переданим Justконструктору, коли було побудовано значення, для якого ви співпадаєте.

Що, можливо, означає

Можливо, ви вже були знайомі з тим, як це працювало; насправді немає ніяких магічних Maybeзначень, це просто звичайний тип алгебраїчних даних Haskell (ADT). Але він використовується зовсім небагато, оскільки він ефективно "піднімає" або розширює тип, наприклад, Integerз вашого прикладу, в новий контекст, в якому він має додаткове значення ( Nothing), що представляє відсутність цінності! Тоді система типів вимагає перевірити це додаткове значення, перш ніж воно дозволить вам отримати те, Integerщо може бути там. Це запобігає надзвичайній кількості помилок.

Багато мов сьогодні обробляють подібне значення "без значення" через посилання NULL. Тоні Хоаре, відомий комп'ютерний вчений (він винайшов Квіксорта і є лауреатом премії Тьюрінга), належить до цього як до своєї "помилки в мільярд доларів" . Тип Можливо - це не єдиний спосіб виправити це, але він виявився ефективним способом це зробити.

Можливо, як функціонер

Ідея перетворення одного типу в інший, щоб операції зі старим типом також можна було перетворити на роботу над новим типом - це концепція, що стоїть під назвою класу типу Haskell Functor, який Maybe aмає корисний екземпляр.

Functorнадає метод fmap, який називається , який відображає функції, що перебувають у діапазоні значень від базового типу (наприклад Integer) до функцій, які перебувають у межах значень від піднятого типу (наприклад, Maybe Integer). Функція, перетворена fmapна роботу для Maybeзначення, працює так:

case maybeVal of
  Nothing  -> Nothing         -- there is nothing, so just return Nothing
  Just val -> Just (f val)    -- there is a value, so apply the function to it

Тож якщо у вас є Maybe Integerзначення m_xта Int -> Intфункція f, ви можете зробити, fmap f m_xщоб застосувати функцію fбезпосередньо до Maybe Integerбез занепокоєння, чи дійсно вона отримала значення чи ні. Насправді, ви можете застосувати цілий ланцюжок піднятих Integer -> Integerфункцій до Maybe Integerзначень, і вам доведеться переживати лише за явну перевірку, Nothingколи ви закінчите.

Можливо, як Монада

Я не впевнений, наскільки ви ще знайомі з концепцією Monadще, але ви принаймні використовували IO aраніше, і підпис типу IO aвиглядає надзвичайно схожим на Maybe a. Хоча IOвін особливий тим, що він не виставляє ваших конструкторів вам і, таким чином, може бути «керований» системою виконання Haskell, він все ще є Functorдодатком до того, що він є Monad. Насправді, є важливий сенс, в якому a Monad- це лише особливий вид Functorз деякими додатковими функціями, але це не місце для цього.

У будь-якому випадку, Monads люблять IOтипи карт до нових типів, які представляють "обчислення, які призводять до значень", і ви можете підняти функції на Monadтипи за допомогою дуже fmapподібної функції, яка називається, liftMщо перетворює звичайну функцію в "обчислення, що призводить до значення, отриманого шляхом оцінки значення функція. "

Ви, мабуть, здогадалися (якщо ви читали це далеко), що Maybeтакож є Monad. Він представляє "обчислення, які не зможуть повернути значення". Як і в fmapприкладі, це дозволяє робити цілу купу обчислень без необхідності чітко перевіряти помилки після кожного кроку. Насправді, так, як побудований Monadекземпляр, обчислення Maybeзначень припиняється, як тільки Nothingзустрічається a , тож це на зразок негайного переривання або безцінного повернення в середині обчислення.

Ви могли б написати, можливо

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

Сподіваємось, ви зараз розумієте Maybeтип та його конструктори, але якщо все ще є щось незрозуміле, дайте мені знати!


19
Яка видатна відповідь! Слід зазначити, що Хаскелл часто використовує Maybeтам, де інші мови використовувались ( nullабо nilз неприємним NullPointerExceptionприхованням у кожному куті). Тепер інші конструкції також починають використовувати цю конструкцію: тип Scala Optionі навіть Java 8 матимуть Optionalтип.
Ландей

3
Це відмінне пояснення. Багато з пояснень, які я читав, наче натякали на думку про те, що Just - це конструктор для типу "Maybe", але жодне ніколи не робило це явним.
reem

@Landei, дякую за пропозицію. Я зробив правки, щоб посилатися на небезпеку нульових посилань.
Леві Пірсон

@Landei Опціонний тип існував з ML в 70-х, швидше за все, Скала взяв його звідти, тому що Scala використовує конвенцію про іменування ML, Варіант з конструкторами деякі і жодні.
каменеметал

@Landei Apple's Swift трохи також використовує додаткові опції
Jamin

37

Більшість нинішніх відповідей - це високотехнічні пояснення того, як Justі друзі працюють; Я подумав, що я можу спробувати свої сили, пояснивши, що це таке.

Багато мов мають таке значення, nullяке можна використовувати замість реального значення, принаймні для деяких типів. Це змусило багатьох людей дуже розлютитися і вважалося поганим кроком. Однак іноді корисно мати значення, nullяке вказує на відсутність речі.

Haskell вирішує цю проблему, роблячи чітке позначення місць, де ви можете мати Nothing(її версія a null). В основному, якщо ваша функція зазвичай повертає тип Foo, вона замість цього повинна повертати тип Maybe Foo. Якщо ви хочете вказати, що немає значення, поверніться Nothing. Якщо ви хочете повернути значення bar, вам слід повернути його Just bar.

Отже, якщо ви цього не можете Nothing, то вам це не потрібно Just. Якщо ви можете мати Nothing, вам це потрібно Just.

У цьому немає нічого магічного Maybe; він побудований за системою типу Haskell Це означає, що ви можете використовувати всі звичні трюки Haskell, що відповідають цьому.


1
Дуже приємна відповідь поруч з іншими відповідями, але я думаю, що все-таки виграє приклад коду :)
PascalVKooten

13

З огляду на тип t, значення Just t- це існуюче значення типу t, де Nothingпредставляється недосягнення значення, або випадок, коли мати значення буде безглуздим.

У вашому прикладі мати від'ємний баланс не має сенсу, і якщо таке трапилося, його замінюють на Nothing.

В якості іншого прикладу, це може бути використано в поділі, визначити функцію розподілу , яка приймає aі b, і повертається , Just a/bякщо НЕ bдорівнює нулю, а в Nothingіншому випадку. Його часто використовують так, як зручну альтернативу виняткам або як ваш попередній приклад для заміни значень, які не мають сенсу.


1
Тож скажімо, у наведеному вище коді я видалив Just, чому це не спрацює? Чи маєте ви мати Просто будь-коли, коли хочете мати Ніщо? Що відбувається з виразом, коли функція всередині цього виразу нічого не повертає?
reem

7
Якщо ви видалили Just, ваш код не буде встановлено перевірку. Причиною цього Justє підтримка належних типів. Існує тип (насправді монада, але простіше думати як просто тип) Maybe t, який складається з елементів форми Just tта Nothing. Оскільки Nothingмає тип Maybe t, вираз, який може оцінювати те Nothingчи інше значення типу t, неправильно набрано. Якщо функція повертається Nothingв деяких випадках, будь-який вираз, що використовує цю функцію, повинен мати певний спосіб перевірки на це ( isJustабо випадок справи), щоб обробити всі можливі випадки.
qaphla

2
Тож справедливий просто є таким, щоб бути послідовним усередині типу "Можливо", тому що звичайний т не є у "може". Зараз все набагато зрозуміліше. Дякую!
reem

3
@qaphla: Ваш коментар щодо того, що це "монада, насправді, [...]" вводить в оману. Maybe t це просто тип. Той факт, що є Monadекземпляр для Maybe, не перетворює його на щось, що не є типом.
Сара

2

Загальна функція a-> b може знайти значення типу b для кожного можливого значення типу a.

У Haskell не всі функції є загальними. У цьому конкретному випадку функція lendне є повною - вона не визначена для випадку, коли баланс менший за резерв (хоча, на мій смак, було б більше сенсу не допускати, щоб новий Баланс був меншим, ніж резерв - як ви можете позичити 101 у залишок 100).

Інші конструкції, які стосуються неповних функцій:

  • викинути винятки при перевірці вхідного значення не відповідає діапазону
  • повернути спеціальне значення (примітивний тип): улюблений вибір - це від'ємне значення для цілих функцій, які призначені для повернення натуральних чисел (наприклад, String.indexOf - коли підрядка не знайдена, повернутий індекс зазвичай розрахований на негативний)
  • повернути спеціальне значення (покажчик): NULL або щось таке
  • мовчки повертатися, нічого не роблячи: наприклад, lend можна було б написати, щоб повернути старий баланс, якщо умова для кредитування не виконується
  • повернути спеціальне значення: Нічого (або Залишив обгортання об'єкта опису помилки)

Це необхідні обмеження дизайну в мовах, які не можуть примусити сукупність функцій (наприклад, Agda може, але це призводить до інших ускладнень, як-от стати тюрінг-неповними).

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

Проблема з мовчазним відхиленням несправності також очевидна - ви обмежуєте те, що абонент може зробити з функцією. Наприклад, якщо lendповернутий старий баланс, абонент не може знати, чи змінився баланс. Це може бути або не бути проблемою, залежно від наміченої мети.

Рішення Хаскелла змушує абонента часткової функції мати справу з типом типу Maybe aабо Either error aчерез тип повернення функції.

Таким чином, lendяк визначено, це функція, яка не завжди обчислює новий баланс - за деяких обставин новий баланс не визначається. Ми повідомляємо про цю обставину абоненту, повертаючи спеціальне значення Nothing, або загортаючи новий баланс у Just. Зухвалий тепер має свободу вибору: або обробляти відмову позичити особливим чином, або ігнорувати і використовувати старий баланс - наприклад, maybe oldBalance id $ lend amount oldBalance.


-1

Функція if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)повинна мати один і той же тип ifTrueі ifFalse.

Отже, коли ми пишемо then Nothing, ми повинні використовувати Maybe atype inelse f

if balance < reserve
       then (Nothing :: Maybe nb)         -- same type
       else (Just newBalance :: Maybe nb) -- same type

1
Я впевнений, що ти мав на увазі сказати щось по-справжньому глибоке
1313

1
Haskell має умовивід типу. Немає потреби вказувати тип Nothingта Just newBalanceчітко.
праворуч

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