Семантика Haskell использует «нижнее значение» для анализа значения кода на Haskell. Это не то, что вы действительно используете непосредственно в программировании на Haskell, и возвращение None
- это совсем не то же самое.
Нижнее значение - это значение, приписываемое семантикой Haskell для любого вычисления, которое не может быть нормально оценено как значение. Одним из таких способов вычисления на Haskell может быть исключение! Так что, если вы пытались использовать этот стиль в Python, вы должны просто генерировать исключения как обычно.
Семантика Haskell использует нижнее значение, потому что Haskell ленив; Вы можете манипулировать «значениями», которые возвращаются вычислениями, которые еще не выполнялись. Вы можете передавать их в функции, вставлять их в структуры данных и т. Д. Такое неоцененное вычисление может вызвать исключение или цикл навсегда, но если нам на самом деле не нужно проверять значение, то вычисление никогда не будетзапустить и столкнуться с ошибкой, и наша общая программа может сделать что-то четко определенное и закончить. Поэтому, не желая объяснять, что означает код на Haskell, указав точное рабочее поведение программы во время выполнения, мы вместо этого объявляем, что такие ошибочные вычисления производят нижнее значение, и объясняем, как ведет себя это значение; в основном, что любое выражение, которое должно зависеть от каких-либо свойств нижнего значения (кроме существующего), также приведет к нижнему значению.
Чтобы оставаться «чистым», все возможные способы получения нижнего значения должны рассматриваться как эквивалентные. Это включает в себя «нижнее значение», которое представляет бесконечный цикл. Поскольку нет никакого способа узнать, что некоторые бесконечные циклы на самом деле бесконечны (они могут закончиться, если вы просто запустите их немного дольше), вы не можете исследовать какое-либо свойство нижнего значения. Вы не можете проверить, является ли что-то нижним, не можете сравнить это с чем-то еще, не можете преобразовать это в строку, ничего. Все, что вы можете сделать с одним, это поместить его (параметры функции, часть структуры данных и т. Д.) В нетронутое и неизученное состояние.
Python уже имеет такой вид дна; это «значение», которое вы получаете из выражения, которое выдает исключение или не завершается. Поскольку Python строгий, а не ленивый, такие «низы» не могут быть сохранены где-либо и потенциально могут остаться незамеченными. Таким образом, нет никакой реальной необходимости использовать концепцию нижнего значения, чтобы объяснить, как вычисления, которые не могут вернуть значение, все еще могут обрабатываться так, как если бы они имели значение. Но нет также причины, по которой вы не могли бы так думать об исключениях, если бы захотели.
Бросать исключения на самом деле считается «чистым». Он ловит исключения, которые нарушают чистоту - именно потому, что он позволяет вам что-то проверять относительно определенных нижних значений, а не обрабатывать их все взаимозаменяемо. В Haskell вы можете перехватывать только исключения, IO
которые допускают нечистое взаимодействие (так, как правило, это происходит на довольно внешнем слое). Python не обеспечивает чистоту, но вы все равно можете сами решать, какие функции являются частью вашего «внешнего нечистого слоя», а не чистыми функциями, и позволяете себе ловить только исключения.
Возвращение None
вместо этого совершенно другое. None
это не нижнее значение; Вы можете проверить, равно ли что-либо этому, и вызывающая функция, которая возвратилась None
, продолжит работать, возможно, используя None
не по назначению.
Так что, если вы хотели выбросить исключение и хотите «вернуть дно», чтобы подражать подходу Хаскелла, вы просто ничего не делаете. Пусть исключение распространяется. Это именно то, что имеют в виду программисты на Haskell, когда говорят о функции, возвращающей нижнее значение.
Но это не то, что имеют в виду функциональные программисты, когда говорят избегать исключений. Функциональные программисты предпочитают «тотальные функции». Они всегда возвращают действительное не нижнее значение своего типа возврата для каждого возможного ввода. Поэтому любая функция, которая может генерировать исключение, не является полной функцией.
Причина, по которой нам нравятся тотальные функции, заключается в том, что их гораздо проще рассматривать как «черные ящики», когда мы их комбинируем и манипулируем. Если у меня есть функция total, возвращающая что-то типа A, и функция total, которая принимает что-то типа A, тогда я могу вызвать секунду на выходе первого, ничего не зная о реализации того или другого; Я знаю, что получу правильный результат, независимо от того, как будет обновлен код любой функции в будущем (при условии, что их совокупность сохраняется, и до тех пор, пока они сохраняют одну и ту же сигнатуру типа). Такое разделение интересов может быть чрезвычайно мощным средством рефакторинга.
Это также необходимо для надежных функций высшего порядка (функций, которые манипулируют другими функциями). Если я хочу написать код, который получает совершенно произвольную функцию (с известным интерфейсом) в качестве параметра, я должен рассматривать его как черный ящик, потому что у меня нет способа узнать, какие входы могут вызвать ошибку. Если мне дают общую функцию, то никакие данные не приведут к ошибке. Аналогично, вызывающая сторона моей функции более высокого порядка не будет точно знать, какие аргументы я использую для вызова функции, которую они передают мне (если они не хотят зависеть от моих деталей реализации), поэтому передача функции total означает, что им не нужно беспокоиться о что я с этим делаю.
Поэтому функциональный программист, который советует вам избегать исключений, предпочел бы, чтобы вы вместо этого возвращали значение, которое кодирует либо ошибку, либо действительное значение, и требует, чтобы для его использования вы были готовы использовать обе возможности. Такие вещи , как Either
типы или Maybe
/ Option
типов являются одними из самых простых подходов , чтобы сделать это в более сильно типизированных языках (обычно используется со специальным синтаксисом или функций высшего порядка , чтобы помочь склеить вещи , которые нуждаются в A
с вещами , которые производят Maybe<A>
).
Функция, которая либо возвращает None
(если произошла ошибка), либо какое-либо значение (если ошибки не было) не следует ни одной из вышеуказанных стратегий.
В Python с утиной типизацией стиль Either / Maybe используется не очень часто, вместо этого создаются исключения, с тестами для проверки работоспособности кода, а не для того, чтобы функции были целостными и автоматически комбинируемыми на основе их типов. В Python нет средств для принудительного применения этого кода, чтобы правильно использовать такие вещи, как типы Maybe; даже если вы используете его как дисциплину, вам нужны тесты, чтобы на самом деле использовать ваш код для проверки этого. Таким образом, подход исключение / основание, вероятно, больше подходит для чисто функционального программирования в Python.