Яка користь від того, що “немає винятків із виконання”, як стверджує Elm?


16

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

Я плутаюся в цьому питанні.

Наскільки я знаю, і коли добре використовується, виняток з виконання - це лише інструмент:

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

З іншого боку, мені важко налагодити програмне забезпечення, яке "проковтує" винятки. Напр

try { 
  myFailingCode(); 
} catch {
  // no logs, no crashes, just a dirty state
}

Отже, питання полягає в тому, у чому полягає сильна теоретична перевага того, що «немає винятків з виконання»?


Приклад

https://guide.elm-lang.org/

Жодних помилок виконання на практиці. Без нуля. Жодне невизначене не є функцією.


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

Останній приклад, який я знайшов, - це в'яз порівняно із C, C ++, C #, Java, ECMAScript тощо. Я оновив своє запитання @JimmyJames
16-16

2
Це перше, що я чув про це. Моя перша реакція - дзвонити на БС. Зверніть увагу на слова, які ми
маємо

@atoth Я відредагую заголовок Вашого питання, щоб зробити його зрозумілішим, оскільки є кілька неспоріднених питань, схожих на нього (наприклад, "RuntimeException" проти "Виняток" на Java). Якщо ви не любите новий заголовок, сміливо його відредагуйте ще раз.
Андрес Ф.

Гаразд, я хотів, щоб це було досить загальним, але я можу погодитися з цим, якщо це допоможе, дякую за ваш внесок @AndresF.!
atoth

Відповіді:


28

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

Порівнюйте це з Elm, коли помилки кодуються у вигляді результатів або Maybes , які є обома значеннями . Це означає, що ви отримуєте помилку компілятора, якщо не обробляєте помилку. Ви можете зберігати їх у змінній або навіть колекції, щоб відкласти їх поводження на зручний час. Ви можете створити функцію для обробки помилок у конкретному додатку, а не повторювати дуже схожі блоки спробувати. Ви можете зв'язати їх з обчисленнями, які досягли успіху, лише якщо всі його частини досягли успіху, і їх не потрібно забивати в один блок спроб. Вас не обмежує вбудований синтаксис.

Це не що інше, як "ковтання винятків". Це робить явні умови помилок у системі типів і надає набагато більш гнучку альтернативну семантику для їх обробки.

Розглянемо наступний приклад. Ви можете вставити це на http://elm-lang.org/try, якщо хочете бачити це в дії.

import Html exposing (Html, Attribute, beginnerProgram, text, div, input)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import String

main =
  beginnerProgram { model = "", view = view, update = update }

-- UPDATE

type Msg = NewContent String

update (NewContent content) oldContent =
  content

getDefault = Result.withDefault "Please enter an integer" 

double = Result.map (\x -> x*2)

calculate = String.toInt >> double >> Result.map toString >> getDefault

-- VIEW

view content =
  div []
    [ input [ placeholder "Number to double", onInput NewContent, myStyle ] []
    , div [ myStyle ] [ text (calculate content) ]
    ]

myStyle =
  style
    [ ("width", "100%")
    , ("height", "40px")
    , ("padding", "10px 0")
    , ("font-size", "2em")
    , ("text-align", "center")
    ]

Зверніть увагу, що String.toIntу calculateфункції є можливість виходу з ладу. У Java це може призвести до винятку виконання. Оскільки він читає введення користувача, у нього є досить хороші шанси. Натомість в'яз змушує мене боротися з цим, повертаючи Result, але зауважте, що мені не доведеться з цим боротися відразу. Я можу подвоїти вхід і перетворити його в рядок, а потім перевірити на предмет неправильного введення getDefaultфункції. Це місце набагато краще підходить для перевірки, ніж будь-яка точка, де сталася помилка або вгору в стеку викликів.

Те, як компілятор змушує нашу руку, також набагато тонше, ніж перевірені Java винятки. Вам потрібно використовувати дуже специфічну функцію, як Result.withDefaultвитягнути потрібне значення. Хоча технічно ви можете зловживати таким механізмом, сенсу немає. Оскільки ви можете відкласти рішення до тих пір, поки не знатимете гарне повідомлення за замовчуванням / помилку, яке потрібно поставити, немає причин не використовувати його.


8
That means you get a compiler error if you don't handle the error.- Ну, це було міркуванням «Перевірені винятки на Яві», але ми всі знаємо, наскільки це добре вийшло.
Роберт Харві

4
@RobertHarvey У певній мірі перевірені винятки Java були версією цього бідного чоловіка. На жаль, їх можна було проковтнути (як у прикладі ОП). Вони також не були реальними типами, що робить їх додатковим додатковим шляхом до потоку коду. Мови з кращими системами типу дозволяють кодувати помилки як першокласні значення, що і робиться в (скажімо) Haskell з Maybe, Eitherі т. Д. Elm виглядає так, що це сторінка з таких мов, як ML, OCaml або Haskell.
Андрес Ф.

3
@JimmyJames Ні, вони не змушують вас. Ви повинні "обробити" помилку лише тоді, коли ви хочете використовувати значення. Якщо я це роблю x = some_func(), мені не доведеться нічого робити, якщо я не хочу перевірити значення x, і в цьому випадку я можу перевірити, чи є у мене помилка чи "дійсне" значення; до того ж це помилка статичного типу для спроби використання одного замість іншого, тому я не можу цього зробити. Якщо типи Elm працюють як-небудь на зразок інших функціональних мов, я можу насправді робити такі речі, як складати значення з різних функцій, перш ніж я навіть дізнаюся, чи це помилки чи ні! Це характерно для мов FP.
Андрес Ф.

6
@atoth Але ви дійсно мають значні вигоди і там є дуже хороша причина (як це було пояснено в декількох відповідях на ваше запитання). Я щиро заохочую вас вивчити мову з синтаксисом, подібним до ML, і ви побачите, як звільнити це позбавлення від C-подібного синтаксису (ML, до речі, був розроблений на початку 70-х, що робить його приблизно сучасник С). Люди, які розробили такі типи систем, вважають цей тип синтаксису нормальним, на відміну від C :) Поки ви перебуваєте на цьому, вам також не завадило б навчитися Lisp :)
Андрес Ф.

6
@atoth Якщо ви хочете взяти одне з усього цього, візьміть це: завжди переконайтеся, що ви не станете здобиччю парадоксу Blub . Не дратуйтесь у новому синтаксисі. Можливо, це там через потужні риси, які вам незнайомі :)
Андрес Ф.

10

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

Це звучить зловісно. Ну, перевірка типу схожа на перевірку теореми. (Насправді, згідно із ізоморфізмом Керрі-Говарда - це одне й те саме.) Одне, що дуже властиво теоремам, - це те, що, коли ти доводиш теорему, ти доводиш саме те, що говорить теорема, не більше. (Ось, наприклад, чому, коли хтось каже "Я довів цю програму правильною", ви завжди повинні запитувати "будь ласка, визначте" правильну "".) Те саме стосується систем типів. Коли ми кажемо, що "програма є безпечною для типу", ми маємо на увазі не те, що можлива помилка не може виникнути. Можна сказати лише те, що помилки, які система типу обіцяє нам запобігти, не можуть виникнути.

Отже, програми можуть мати нескінченну кількість різних режимів виконання. З них нескінченно багато корисних, але також нескінченно багато з них є "неправильними" (для різних визначень "правильність"). Система статичного типу дозволяє нам довести, що певний обмежений, фіксований набір тих нескінченно багатьох неправильних поведінки часу виконання не може відбуватися.

Різниця між системами різних типів полягає в тому, що в тому, скільки, скільки складних режимів поведінки вони можуть виявити, не виникають. Системи слабкого типу, такі як Java, можуть довести лише основні речі. Наприклад, Java може довести, що метод, який вводиться як повернення a, Stringне може повернути a List. Але, наприклад, він може НЕ довести , що метод не буде не повертатися. Це також не може довести, що метод не кине виключення. І це не може довести, що воно не поверне неправильне String  - будь- Stringякий задовольнить перевірку типу. (І, звичайно, навіть nullзадовольнить його.) Є навіть дуже прості речі , які Java не можуть довести, що саме тому у нас є винятки , наприклад ArrayStoreException, ClassCastExceptionабо все улюбленець, то NullPointerException.

Більш потужні системи типу, як Agda, можуть також довести такі речі, як "поверне суму двох аргументів" або "повертає відсортовану версію списку, переданого як аргумент".

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

Отже, вони не говорять, що "ми не застосовуємо винятків". Вони говорять, що "типи, що були типовими винятками у типових мовах, з якими типичні програмісти, що приїжджають до Elm, мають досвід роботи, потрапляють у систему типу". Звичайно, хтось із Ідріса, Агди, Гуру, Епіграму, Ізабел / HOL, Coq або подібних мов побачить В'яза як досить слабкого в порівнянні. Заява більш орієнтована на типових програмістів Java, C♯, C ++, Objective-C, PHP, ECMAScript, Python, Ruby, Perl….


5
Примітка до потенційних редакторів: мені дуже шкода використання подвійних і навіть потрійних негативів. Однак я залишив їх спеціально: системи типів гарантують відсутність певних видів поведінки під час виконання, тобто вони гарантують, що певні речі не відбудуться. І я хотів, щоб формулювання "не виявилося" недоторканим, що, на жаль, призводить до конструкцій на кшталт "вона не може довести, що метод не повернеться". Якщо ви можете знайти спосіб їх покращення, продовжуйте, але пам’ятайте про вищезазначене. Дякую!
Йорг W Міттаг

2
Хороша відповідь в цілому, але одна маленька нитка: "перевірка типу схожа на доказ теореми". Насправді, перевірка типу більше схожа на перевірку теореми: вони обидва роблять перевірку , а не дедукцію .
садівник

4

Elm може гарантувати відсутність виключення під час виконання з тієї ж причини. C може гарантувати відсутність виключення з виконання: Мова не підтримує концепцію винятків.

Elm має спосіб сигналізації про помилки під час виконання, але ця система не є винятком, це "Результати". Функція, яка може відмовити, повертає "Результат", який містить або регулярне значення, або помилку. В'язи сильно набрані, тому це явно в системі типів. Якщо функція завжди повертає ціле число, вона має типInt . Але якщо воно або повертає ціле число, або не вдається, тип повернення є Result Error Int. (Рядок - це повідомлення про помилку.) Це змушує вас чітко обробляти обидва випадки на сайті виклику.

Ось приклад із вступу (трохи спрощений):

view : String -> String 
view userInputAge =
  case String.toInt userInputAge of
    Err msg ->
        text "Not a valid number!"

    Ok age ->
        text "OK!"

Функція toInt може вийти з ладу, якщо вхід не піддається синтаксичному аналізу, тому його тип повернення є Result String int. Щоб отримати фактичне ціле значення, вам доведеться "розпакувати" за допомогою відповідності шаблонів, що в свою чергу змушує вас обробляти обидва випадки.

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


2
@atoth Ось приклад. Уявіть, що мова A допускає винятки. Тоді вам надана функція doSomeStuff(x: Int): Int. Зазвичай ви розраховуєте, що він поверне Int, але чи може це викинути і виняток? Не дивлячись на його вихідний код, ви не можете знати. На відміну від цього, мова B, що кодує помилки через типи, може мати таку ж функцію, яку оголосив так: doSomeStuff(x: Int): ErrorOrResultOfType<Int>(у Elm цей тип насправді названий Result). На відміну від першого випадку, зараз відразу очевидно, чи може ця функція вийти з ладу, і ви повинні її чітко обробляти.
Андрес Ф.

1
Як @RobertHarvey передбачає в коментарі до іншої відповіді, це, здається, в основному як перевірені винятки на Java. Те, що я дізнався, працюючи з Java в перші дні, коли перевіряли більшість винятків, - це те, що ви насправді не хочете, щоб вас примушували писати код, щоб завжди помилятися в момент їх виникнення.
JimmyJames

2
@JimmyJames Це не як перевірені винятки, оскільки винятки не складаються, їх можна ігнорувати ("проковтнути") і не є першокласними значеннями :) Я дійсно рекомендую вивчити статично набрану функціональну мову, щоб по-справжньому зрозуміти це. Це не якась новомодна річ, яку склав Elm - це те, як ви програмуєте на таких мовах, як ML або Haskell, і це відрізняється від Java.
Андрес Ф.

2
@AndresF. this is how you program in languages such as ML or HaskellУ Haskell, так; ML, ні. Роберт Харпер, головний довідник дослідника Standard ML та мови програмування, вважає винятки корисними . Типи помилок можуть заважати складу функції у випадках, коли ви можете гарантувати, що помилка не виникне. Винятки також мають різну ефективність. Ви не платите за винятки, які не кидаються, але ви сплачуєте за перевірку значень помилок кожен раз, і винятки - це природний спосіб виразити зворотній трек в деяких алгоритмах
Doval

2
@JimmyJames Сподіваємось, зараз ви бачите, що перевірені винятки та фактичні типи помилок лише поверхнево схожі. Перевірені винятки не поєднуються витончено, громіздкі у використанні та не орієнтовані на вираження (і тому їх можна просто "проковтнути", як це відбувається з Java). Неперевірені винятки менш громіздкі, тому вони є нормою скрізь, але на Яві, але вони, швидше за все, подолають вас, і ви не можете зрозуміти, дивлячись на декларацію функції, кидає вона чи ні, ускладнюючи вашу програму зрозуміти.
Андрес Ф.

2

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

Це питання можна інтерпретувати будь-якою кількістю способів, але оскільки ви згадували Елма в коментарях, контекст є більш зрозумілим.

Elm - це, між іншим, статична мова програмування. Однією з переваг систем такого типу є те, що компілятор виявляє багато класів помилок (хоча і не всіх), перш ніж програма фактично використовується. Деякі види помилок можуть бути закодовані у типах (наприклад, Elm's ResultтаTask ), замість того, щоб їх кидали як винятки. Це означає, що дизайнери Elm означають: багато помилок будуть виявлені під час компіляції, а не в "час запуску", і компілятор змусить вас боротися з ними, а не ігнорувати їх і сподіватися на найкраще. Зрозуміло, чому це перевага: краще, щоб програміст дізнався про проблему ще до того, як це зробить користувач.

Зауважте, що коли ви не використовуєте винятків, помилки кодуються іншими, менш дивними способами. З документації Elm :

Однією з гарантій Elm є те, що ви не побачите помилок виконання на практиці. NoRedInk використовує Elm у виробництві вже близько року, і їх досі не було! Як і всі гарантії Elm, це зводиться до принципового вибору мовного дизайну. У цьому випадку нам допомагає той факт, що Elm трактує помилки як дані. (Ви помічали, що ми багато чого робимо тут?)

Дизайнери в'язів трохи сміливі, стверджуючи, що "немає винятків із часу виконання" , хоча вони кваліфікують це як "на практиці". Вони, ймовірно, означають "менш несподівані помилки, ніж якщо ви кодували в javascript".


Я це неправильно читаю, чи вони просто грають у семантичну гру? Вони забороняють назву "виняток часу виконання", але потім просто замінюють її іншим механізмом, який передає інформацію про помилки в стек. Це звучить як просто зміна реалізації виключення для подібної концепції або об'єкта, що реалізує повідомлення про помилку по-іншому. Це навряд чи зруйнує землю. Це як будь-яка статично набрана мова. Порівняйте перехід із COM HRESULT на виняток .NET. Різний механізм, але все ж є винятком часу виконання, незалежно від того, як його називати.
Майк підтримує Моніку

@Mike Якщо чесно кажучи, я детально не переглянув Elm. Судячи з документації, вони мають тип Resultі Taskякі дуже схожі на більш звичний Eitherі Futureз інших мов. На відміну від винятків, значення цих типів можна комбінувати, і в якийсь момент ви повинні чітко обробити їх: чи вони представляють дійсне значення чи помилку? Я не читаю розуму, але ця відсутність дивного програміста - це, мабуть, те, що дизайнери Elm мали на увазі під "відсутністю винятків із часу запуску" :)
Andres F.

@Mike Я згоден, що це не руйнує землю. Відмінність від винятків із виконання часу полягає в тому, що вони не є явними у типах (тобто ви не можете знати, не дивлячись на його вихідний код, чи може кинути фрагмент коду); Помилки кодування у типах дуже явні і не дозволяють програмісту не помічати їх, що призводить до більш безпечного коду. Це робиться багатьма мовами FP і насправді немає нічого нового.
Андрес Ф.

1
Відповідно до ваших коментарів, я думаю, що в цьому є більше, ніж "статичні перевірки типу" . Ви можете додати його до JS, використовуючи Typescript, що набагато менш обмежує, ніж нова екосистема "make-or-break".
ато

1
@AndresF. Технічно кажучи, найновішою особливістю системи типу Java є Параметричний Поліморфізм, який утворюється наприкінці 60-х. Отже, кажучи так, сказати "сучасне", коли ви маєте на увазі "не Java", є дещо правильним.
Йорг W Міттаг

0

В'яз стверджує:

Жодних помилок виконання на практиці. Без нуля. Жодне невизначене не є функцією.

Але ви запитуєте про винятки з виконання . Є різниця.

У Elm нічого не повертає несподіваного результату. НЕ можна писати дійсну програму в Elm, яка створює помилки під час виконання. Таким чином, вам не потрібні винятки.

Отже, питання повинно бути:

Яка користь від того, що "немає помилок виконання"?

Якщо ви можете написати код, який ніколи не має помилок виконання, ваші програми ніколи не завершаться.

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