Ответ 1
В Haskell я бы обернул этот код в какую-то комбинацию монадов, используя Maybe и Lither, и при необходимости переведя и распространяя ошибки. В конце концов, все это доходит до монады IO, где я могу выводить статус пользователю.
На другом языке я просто бросаю исключение и улавливаю в соответствующем месте. Непосредственная. Я не трачу много времени на познавательную неопределенность, пытаясь разгадать, какую комбинацию мне нужно.
Я бы не сказал, что вы неизбежно подходите к нему неправильно. Скорее, ваша ошибка заключается в том, что эти два сценария различны; это не так.
"Просто бросить и поймать" эквивалентно тому, чтобы накладывать на всю вашу программу ту же концептуальную структуру, что и некоторая комбинация методов обработки ошибок Haskell. Точная комбинация зависит от систем обработки ошибок на языке, на котором вы сравниваете это, что указывает на то, почему Haskell кажется более сложным: он позволяет смешивать и сопоставлять структуры обработки ошибок на основе необходимости, а не давать вам неявное, одно -size-fits-most solution.
Итак, если вам нужен особый стиль обработки ошибок, вы его используете; и вы используете его только для кода, который ему нужен. Код, который ему не нужен - из-за того, что он не генерирует и не обрабатывает соответствующие типы ошибок, помечается как таковой, то есть вы можете использовать этот код, не беспокоясь о том, что создается такая ошибка.
В связи с синтаксической неуклюжестью этот неудобный субъект. Теоретически это должно быть безболезненно, но:
- Haskell был языком, ориентированным на исследования, и в его ранние времена многие вещи все еще были в движении, и полезные идиомы еще не были популяризированы, поэтому старый код, плавающий вокруг, скорее всего, будет плохой моделью для подражания
- Некоторые библиотеки не так гибки, как они могут быть в том, как обрабатываются ошибки, либо из-за ископаемости старого кода, как указано выше, либо просто отсутствия полировки.
- Я не знаю никаких руководств о том, как лучше всего структурировать новый код для обработки ошибок, чтобы новички оставались на своих устройствах.
Я бы предположил, что вероятность того, что вы "делаете это неправильно" каким-то образом, и может избежать большей части этого синтаксического беспорядка, но что, вероятно, не разумно ожидать, что вы (или любой средний программист Haskell) найдете лучшие подходы самостоятельно.
Что касается стеков трансформаторов монады, я думаю, что стандартный подход заключается в newtype
весь стек для вашего приложения, получения или реализации экземпляров для соответствующих классов типов (например, MonadError
), затем используйте класс типа функций, которые обычно не нуждаются в lift
ing. Монадические функции, которые вы пишете для ядра вашего приложения, должны использовать стек newtype
d, поэтому также не нужно поднимать. Я думаю, что о единственной вещи с низким смысловым смыслом, которые вы не можете избежать, liftIO
.
Работа с большими пакетами трансформаторов может быть фактической головной болью, но только тогда, когда имеется много вложенных слоев разных трансформаторов (накапливаются чередующиеся слои StateT
и ErrorT
с ContT
, посланными посередине, то просто попробуйте сказать мне, что на самом деле сделает ваш код). Это редко то, что вы на самом деле хотите.
Изменить. В качестве второстепенного добавления я хочу обратить внимание на более общий момент, который возник со мной при написании пара комментариев.
Как я заметил, и @sclv продемонстрировал красиво, правильная обработка ошибок действительно сложна. Все, что вы можете сделать, это перетасовать эту сложность вокруг, а не исключать ее, потому что независимо от того, что вы выполняете несколькими операциями, которые могут создавать ошибки независимо, и ваша программа должна каким-то образом обрабатывать любую возможную комбинацию, даже если эта "обработка" - это просто падение и умирают.
Тем не менее, Haskell действительно отличается по сути от большинства языков в одном отношении: как правило, обработка ошибок является явной и первоклассной, что означает, что все открыто и свободно управляется. Оборотной стороной этого является потеря неявной обработки ошибок, а это означает, что даже если вы хотите напечатать сообщение об ошибке и умереть, вы должны сделать это явно. Так что на самом деле обработка ошибок в Haskell проще, из-за первоклассных абстракций для него, но игнорировать ошибки сложнее. Тем не менее, такая ошибка "все руки покинуть корабль" не обрабатывается практически никогда не является правильной в каком-либо реальном мире, производственном использовании, поэтому кажется, что неловкость отмахивается.
Итак, хотя верно, что сначала вещи сложнее, когда вам нужно явно обращаться с ошибками, важно помнить, что все, что есть для него. Когда вы узнаете, как использовать правильные абстракции обработки ошибок, сложность в значительной степени попадает на плато и на самом деле не становится значительно сложнее по мере расширения программы; и чем больше вы используете эти абстракции, тем естественнее они становятся.