Рубрики
Без рубрики

Почему функциональные программисты избегают исключений

Мы рассмотрим, почему функциональные программисты избегают исключений, и вместо этого возвращаем результат/любые типы из чистых функций. Tagged с помощью JavaScript, Python, функциональных, исключений.

Если вы спешите, вот 60 -секундная версия:

Мой Предыдущая статья вызвал разнообразие ужаса, императивного патриотизма и множества нюансов. Это напомнило мне о том, когда Ричарда Фейнмана попросили определить, как работают магниты, и он отказался Анкет Объединенный интервьюер поступил, что это был разумный вопрос в надежде понять, почему мистер Фейнман не ответит на него. Ричард Фейнман освещал множество причин, 2 из которых были:

  1. Вы должны знать более глубокие причины, прежде чем я смогу это объяснить
  2. Я не могу обмануть, используя аналогии с тем, что они сами требуют более глубоких значений, чтобы объяснить, как они работают.

В случае избежания асинхронного/ожидания ключевых слов в JavaScript это делает огромное предположение, которое вы знаете о функциональном программировании, императивном обработке исключений, о том, как различные языки подходят к нему или нет, проблемы между динамическими и сильно напечатанными языками, а также и да на.

В этой статье я хотел исправить это и сосредоточиться на более глубоких причинах, почему, в частности, прагматичны в отношении того, как функциональные программисты делают вещи, и почему теория или почему. Это означает понимание:

  • Почему чистые функции предпочтительнее
  • Как их легче проверить
  • Почему вы возвращаете ошибки в качестве значений, используя результат/Любые типы
  • Как вы сочиняете программное обеспечение, используя их

Педантичный или математический ответ

Я обнаружил, что, изучая конкретно, почему исключения не предпочтительнее в функциональном программировании, на самом деле они не являются антифункциональным программированием. Хуже того, я узнал, что многие утверждают, что они не нарушают чистые функции или референциальную прозрачность с множеством увлекательных подтверждающих доказательств. Некоторые утверждают, что они даже не побочные эффекты. Это становится все более запутанным, когда вы начинаете сравнивать строго напечатанные функциональные языки с динамическими или практиковать FP на языках без FP.

На практике исключения, такие как побочные эффекты, кажется, нарушают все причины, по которым вы используете чистые функции: Ваш код предсказуем, легче тестировать и приводит к лучшему программному обеспечению. Исключения гарантируют, что ваш код непредсказуемый, снижает значение тестов и приводит к худшему программному обеспечению . Но это не Что говорят математические определения . Они не согласны и не согласны с моими утверждениями; Скорее они просто говорят, что известные исключения не нарушают референциальную прозрачность. Да, есть хулители. Несмотря на это, это действительно потрясло мою веру.

Можно сказать, что они педантичные; Ссылаясь на истинное определение референциальной прозрачности, механизмы того, как исключения могут или не могут негативно повлиять на нее, и, следовательно, возможно, не нарушают правила чистой функции. Тем не менее, это общая проблема между учеными и инженерами: в то время как ученые дадут вам Математики отвечают , они не помогут вам выполнить вашу настоящую работу.

И это то, что вернуло меня к реальности. Я не здесь, чтобы обсудить семантику, я здесь, чтобы доставить рабочее программное обеспечение. Тем не менее, я уступим нюансам, если кто -то захочет углубиться в отношения между математикой, стоящей за этими конструкциями. До сих пор предпочтение программирования математического стиля над императивным или объектно -ориентированным, кажется, намного лучше достигает лучших результатов, даже если у меня нет 100% -ного железного обтянутого понимания всех нюансов правил.

Хорошая новость, несмотря на то, что она обнаружила глубокие нюансы вокруг исключений и их сложные отношения с математической чистотой FP в отрасли, как FP, так и других (то есть Go, Rust, Lua) в основном приняли прагматическую истину: исключения не чистые, акте Побочные эффекты и не полезны при написании программного обеспечения. У нас уже есть решение: возвращение ошибок в качестве значений из функций, используя типы результатов (или либо).

Имейте в виду, что вышесказанное имеет предвзятость Haskell. Я призываю вас к Google ” Исключения считаются вредными «И посмотрите некоторые ужасы, которые могут возникнуть, когда исключения помещают ваш государственный код (Java/C#/Python/JavaScript) в плохое состояние.

Предпочитают чистые функции

Когда люди говорят, предпочитают чистые функции, это связано с следующими причинами:

  • более предсказуемо
  • легче тестировать
  • легче поддерживать

Что это делает означает, хотя?

Предсказуемый

Мы говорим предсказуемо, потому что вы называете это, и это возвращает значение. Вот и все.

const isAnOk = safeParseJSON('{"foo": "bar"}')
const isAnError = safeParseJSON('')

Когда вы вкладываете в это исключения, теперь у вас есть 2 возможностей: оно либо возвращает значение, либо взрывается.

const result = JSON.parse('') // result is never used/set

Когда вы объединяете функции в программы, программа берет значение и возвращает значение. Вот и все.

Когда вы вкладываете в это исключения, теперь у вас есть возможности x * y: программа либо возвращает значение, либо x количество функций, возможно, взорвется в y -количестве способов; Это зависит от того, как вы проводите функции вместе.

Эта экспоненциальная сложность показывает, насколько непредсказуемым может быть код за исключением.

Легче тестировать

Легче по сравнению с какие? Как?

Чистые функции не имеют побочных эффектов, поэтому вам не нужно настраивать и срывать заглушки или издеваться. Не существует первоначального состояния для настройки и не состояния для сброса после этого. Нет шпиона, который вы должны утверждать после того, как вы позвоните своему коду.

Вместо этого вы придаете своей функции вход и утверждаете, что вывод – это то, что вы ожидаете.

expect(safeParseJSON('{"foo": "bar"}')).to.be(Ok)
expect(safeParseJSON('')).to.be(Error)

Легче поддерживать

По сравнению с чем? Что означает “проще”? Легко для кого -то знакомого с кодом? Это утверждение слишком туманное и полное чувств.

Тем не менее, многие согласятся, независимо от языка, что код, который не имеет каких -либо побочных эффектов, намного легче иметь дело, с изменением и модуль Вы должны учитывать, проверить и узнать об их возможных экспоненциальных изменениях в коде.

Используйте результат/Либо

Если вы предпочитаете чистые функции, это означает очень небольшие побочные эффекты, или они находятся на половине вашего кода. Но как вы справляетесь с вещами, которые идут не так? Вы возвращаетесь, если функция сработала или нет. Если это сработало, у него будут данные внутри. Если это не удалось, у него будет причина, по которой это не удалось. На языках FP они имеют результат или любой тип. В языках, у которых нет такого типа, вы можете подражать различным образом. Если код работает, вы возвращаете ОК с значением в нем. Если функция не удалась, вы возвращаете Ошибка с причиной, по которой в качестве строки четко написано в ней.

const safeParseJSON = string => {
    try {
        const result = JSON.parse(string)
        return Result.Ok(result)
    } catch(error) {
        return Result.Error(error?.message)
    }
}

Многие языки приняли обещание, также называемое будущим, способом делать дела. Некоторые языки использовали это, чтобы также справиться с асинхронными операциями, потому что они могут провалиться двумя способами, которые означают одно и то же: это сломалось или истекло. Например, большинство людей не собираются ждать 10 минут, пока их электронная почта появится, поэтому вы обычно увидите неудачи в течение 10-30 секунд, хотя технически ничто не пошло не так; Мы просто перестали пытаться после определенного количества времени. Версии JavaScript и Python не имеют этого времени, но есть Библиотеки, которые позволяют использовать это поведение Анкет

Это приводит к чистым функциям, которые всегда возвращают значение: a Результат Анкет Это может быть успешным или неудачи, но это всегда результат. Если это неудача, он не сломает всю вашу программу и не заставит вас написать Try/Catch. В то время как Обещание S может заменить, например, в JavaScript, убедитесь, что вы используете само обещание, а не значение, которое он возвращает через Async/ожидает. Это полностью обходит встроенную обработку исключений и заставляет вас снова использовать попытку/поймать.

Сочинение программ

То, как вы создаете программы FP, – это объединение всех этих чистых функций вместе. Некоторые из них могут быть сделаны, конечно, но большинство выполняются через какой -то тип Железнодорожное программирование Анкет Есть разнообразные способы сделать это на языках FP и без FP:

Это означает, что в Rescrict и F#у вас будет функция, и выйдет результат. Затем вы можете увидеть, сработала ли ваша программа или нет.

let parsePeople = str =>
    parsePeopleString(str) // <-- this function could be an Ok or Error
    -> filterHumans
    -> formatNames
    -> startCaseNames

Для JavaScript/Python он немного более нюансирован вокруг типов. Для Python мы предположим, что вы возвращаете результат в Pymonad или Returns.

def parse_people(str):
  return parse_people_string(str)
  .then(filter_humans)
  .then(format_names)
  .then(start_case_names)

Создание JavaScript с помощью обещаний

Для JavaScript, если вы не все в какой-то библиотеке, изначально вы можете сделать это с помощью обещания. Обещание уже является типом результата: он имеет значение, и если оно сработало, вы можете получить его с помощью, иначе сбой через улов. По умолчанию они также композируются, так что вы можете создавать цепочки обещаний, которые автоматически разворачивают Обещание Значения, используйте регулярные значения как есть или прервать в поймать В случае ошибки. Вы теряете эту способность, как только начнете использовать Async ждать, потому что теперь вы несете ответственность:

  • Обработка исключений
  • Вытягивая ценность
  • Если это обещание, асинхронно/ожидание его
  • Если это значение, используя его
  • Вставить в следующую функцию вниз по линии
  • Обработка, что делать, если вы получите исключение в каждом разделе кода

Для обещаний вы просто возвращаете ценность или другое обещание, и это просто выходит на другой конец готово к работе. Если нет, то вы поймете, что обрабатывает какие -либо ошибки. Это гарантирует, что любая функция вызывает вашу цепочку обещаний, потому что она всегда возвращает значение обещания.

2 огромные предположения:

  1. ты всегда определяешь улов
  2. Вы не используете результат

Смешивание в результате

Если некоторые функции не являются асинхронными, большинство программистов JavaScript подумают, что они могут просто вернуть тип результата, чтобы сохранить его синхронным. Не существует огромного штрафа в скорости/памяти, чтобы использовать обещание, но некоторые предпочли бы вместо этого использовать результат. Я бы посоветовал двум вещам, если вы не используете библиотеку: отдают обещание в результате. Обещание является местным и в основном действует как результат.

const parseJSONSafe = string => {
  try {
    const result = JSON.parse(result)
    return Promise.resolve(result)
  } catch(error) {
    return Promise.reject(error)
  }
}

Однако, если вы бы предпочли провести четкое разграничение между асинхронной операцией и возможным сценарием отказа, то вам придется развернуть его в конце цепочки обещаний, аналогично Rust или Python’s Dry/Returns. Есть много вспомогательных методов, как это сделать, исходя из того, какую библиотеку результатов вы используете. Мы будем использовать Фольклор ниже. Здесь мы определили безопасную обертку вокруг Json.parse :

const parseJSONSafe = string => {
  try {
    const result = JSON.parse(result)
    return Ok(result)
  } catch(error) {
    return Failure(error)
  }
}

При использовании его, он получит следующее обещание », и мы можем соответствовать шаблону, чтобы вывести ошибку или значение и преобразовать в обычное обещание.

const parse = () =>
  fetchJSON()
  .then(parseJSONSafe)
  .then(
    result =>
      result.matchWith({
        Failure: ({ value }) => Promise.reject(new Error(value)),
        Ok: ({ value }) => Promise.resolve(value)
  )

Выводы

Функциональные программисты избегают исключений, потому что они в основном действуют как побочные эффекты, как правило, чувствуют, что нарушают правила чистой функции в отношении отсутствия возвратной стоимости и, возможно, сбоя нашей программы. Если вы вместо этого выступаете за чистые функции, верните тип результата, когда все может потерпеть неудачу. Затем вы можете использовать предпочтительный способ сочинения функций. Тогда у вас есть чистые программы, которые имеют вход и вывод. Это означает, что как функции, так и сама программа гораздо проще для единичного тестирования. Вам больше не нужно писать Ожидайте (Thisting) .hrows (SomeExceptionType) . Вам не нужно писать Try/Catch/добавить свой код. Вы просто даете свои функции или программу и ввод, и утверждаете этот вывод.

Для побочных эффектов, если вы не можете заставить их вернуть значимое значение, вы можете просто утверждать, что они были вызваны вашими ожидаемыми входными данными с помощью шпионских методов Sinon или метода Assert TestDouble. Больше нет, больше нет, больше не нужно использовать, чтобы попробовать/поймать в нескольких местах для Ваш код. Это делает ваши функции и программы гораздо более предсказуемыми, особенно когда вы объединяете множество функций вместе.

Для нативной функциональности на нефункциональных языках, таких как JavaScript и Python, вы оберните небезопасную код. В приведенных выше примерах мы завершили json.parse с помощью Try/Catch и заставили его либо вернуть результат, либо обещание. На языках FP это уже вернет результат. Если вы программные, такие языки, как Rescrict и F#, поддерживают как типы результатов, так и сопоставление шаблонов на исключениях (которые, я думаю, является богохульством).

Оригинал: “https://dev.to/jesterxl/why-functional-programmers-avoid-exceptions-8oe”