Найпростіший спосіб повідомляти про помилки в Haskell


22

Я працюю над вивченням Haskell, і я зіткнувся з трьома різними способами боротьби з помилками в написаних функціях:

  1. Я можу просто написати error "Some error message.", що кидає виняток.
  2. Я можу повернути свою функцію Maybe SomeType, де я можу або не зможу повернути те, що хотів би повернути.
  3. У мене може бути повернення функції Either String SomeType, де я можу повернути або повідомлення про помилку, або те, що мене просили повернути в першу чергу.

Моє запитання: Який метод поводження з помилками я повинен використовувати і чому? Можливо, я повинен використовувати різні методи, залежно від контексту?

Моє сучасне розуміння:

  • З винятками в чисто функціональному коді "важко" боротися з винятками, і в Haskell хочеться зберегти речі максимально функціональними.
  • Повернення Maybe SomeType- це належна річ, яку потрібно зробити, якщо функція буде або вийти з ладу або досягти успіху (тобто, не існує різних способів її відмови ).
  • Повернення Either String SomeType- це правильно зробити, якщо функція може вийти з ладу будь-яким з різних способів.

Відповіді:


32

Добре, перше правило поводження з помилками в Haskell: Ніколи не використовуйтеerror .

Це просто страшно в усіх відношеннях. Він існує суто як акт історії, і те, що Прелюдія використовує його, є жахливим. Не використовуйте його.

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

Тепер питання стає Maybeпроти Either. Maybeдобре підходить для чогось подібного head, що може або не може повернути значення, але є лише одна можлива причина, щоб не вдатися. Nothingговорить щось на кшталт "зламалося, і ти вже знаєш чому". Дехто би сказав, що це вказує на часткову функцію.

Найбільш надійною формою обробки помилок є Either+ ADT про помилку.

Наприклад, в одному зі своїх хобі-компіляторів у мене є щось подібне

data CompilerError = ParserError ParserError
                   | TCError TCError
                   ...
                   | ImpossibleError String

data ParserError = ParserError (Int, Int) String
data TCError = CouldntUnify Ty Ty
             | MissingDefinition Name
             | InfiniteType Ty
             ...

type ErrorM m = ExceptT CompilerError m -- from MTL

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

Кожен з цих типів помилок намагається зберегти якомога довшу інформацію для гарного друку чи іншого аналізу. Що ще важливіше, не маючи рядка, я можу перевірити, що запуск нетипової програми через перевірку типу насправді створює помилку об’єднання! Після того, як щось є String, воно назавжди зникне, і будь-яка інформація, що міститься, непрозора для компілятора / тестів, тому просто теж Either Stringне чудово.

Нарешті я пакую цей тип у ExceptTновий трансформатор монади від MTL. Це по суті EitherTі постачається з приємною партією функцій для кидання та лову помилок чистим, приємним способом.

Нарешті, варто згадати, що Haskell має механізми для підтримки винятків, як інші мови, за винятком випадків, коли відбувається виняток IO. Я знаю, що деякі люди люблять використовувати їх для IOважких програм, де все потенційно може вийти з ладу, але настільки рідко, що вони не люблять думати про це. Чи використовуєте ви ці нечисті винятки чи просто ExceptT Error IOсправді - справа смаку. Особисто я вибираю, ExceptTтому що мені подобається нагадувати про шанс невдачі.


Як підсумок,

  • Maybe - Я можу провалитися одним очевидним способом
  • Either CustomType - Я можу зазнати невдачі, і я розповім тобі, що сталося
  • IO+ винятки - я часом не вдається. Перевірте мої документи, щоб побачити, що я кидаю, коли це роблю
  • error - Я ненавиджу вас, також, користувача

Ця відповідь для мене багато що прояснює. Я вдруге здогадувався, ґрунтуючись на тому, що вбудовані функції подобаються headабо, lastздається, використовуються error, тому мені було цікаво, чи це насправді хороший спосіб робити справи, і мені просто щось не вистачало. Це відповідає на це питання. :)
CmdrMoozy

5
@CmdrMoozy Радий допомогти :) Дуже прикро, що в прелюдії є такі погані практики. Такий шлях спадщини: /
Даніель Гратцер

3
+1 Ваш підсумок дивовижний
recursion.ninja

2

Я б не розділяв 2 і 3 на основі того, скільки способів щось може вийти з ладу. Як тільки ви думаєте, що існує лише один можливий спосіб, інший покаже своє обличчя. Замість цього показник повинен бути "чи мої абоненти переймаються, чому щось не вдалося? Чи можуть насправді щось зробити з цього приводу?".

Крім того, мені не відразу зрозуміло, що Either String SomeTypeможе спричинити помилку. Я зробив би простий алгебраїчний тип даних із умовами з більш описовою назвою.

Який ви використовуєте, залежить від характеру проблеми, з якою ви стикаєтесь, та від фразеологізму пакету програм, з яким ви працюєте. Хоча я б схильний уникати №1.


Власне, використання Eitherпомилок - це дуже відомий зразок Haskell.
Rufflewind

1
@rufflewind - Eitherце не незрозуміла частина, використання Stringяк помилка, а не фактична рядок.
Теластин

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