система исключения в Haskell намеренно под напряжением. Вы не можете перехватывать исключения в чистом коде, поэтому обработка исключений может происходить только на очень крупнозернистом уровне внутри монады IO
. Это довольно сложно - хотя это возможно - остановить исключение от сбоя вашей программы в целом. (Представьте себе, что вы можете написать только catch
в методе императивной программы main
!) Поэтому мы избегаем бросать исключения, когда это возможно; есть намного лучшая альтернатива.
«Правильный путь» для программирования стиля исключения в Haskell заключается в использовании системы типов. Здесь я использую Either
, чтобы представить возможность сбоя вычислений.
powered :: Int -> Either String Int
powered n
| n <= 0 = Left "Incorrect input"
| n==1 = Right 2 -- Right means "the right answer"
| otherwise = Right $ iter n double 1
Если мы не могли вычислить ответ, мы возвращаем Left
значение (Left :: a -> Either a b
), содержащее сообщение об ошибке с String
. В противном случае мы возвращаем Right
(Right :: b -> Either a b
), содержащий ответ.
Компилятор заставляет вызывающего абонента powered
проверять возвращаемое значение, чтобы выяснить, завершилось ли вычисление. Вы просто не можете получить результат вычисления без обработки или распространения возможной ошибки.
Мы можем пойти на один шаг дальше. Мы можем кодировать тот факт, что powered
ожидает положительное целое число в самой сигнатуре типа. Если мы правильно структурируем наш код, компилятор удостоверится, что никто не пытается называть его отрицательным целым числом.
-- file Natural.hs
-- don't export the 'Natural' value constructor: 'mkNatural' acts as our "gatekeeper"
module Data.Natural (Natural, toInt, mkNatural) where
newtype Natural = Natural {toInt :: Int} deriving (Eq, Show)
mkNatural :: Int -> Either String Natural
mkNatural x
| x <= 0 = Left "Must be greater than 0"
| otherwise = Right $ Natural x
Natural
представляет собой тип, который обертывания Int
. Как клиент модуля Data.Natural
, существует только один способ сделать Natural
: путем вызова «умного конструктора» mkNatural
, и вы увидите, что mkNatural
терпит неудачу, когда его аргумент не является натуральным числом. Таким образом, невозможно сделать Natural
без положительного целого числа. Мы также предоставляем обратный метод, toInt :: Natural -> Int
, для извлечения базового Int
из Natural
.
Теперь мы можем написать следующий тип подпись для powered
, что делает его невозможного вызвать функцию с недопустимым вводом:
powered :: Natural -> Natural
Это способ более выразительное: тип подпись четко говорится, что powered
является операция над натуральными числами, которая возвращает новое натуральное число. (Я оставлю это как упражнение для вас, чтобы реализовать powered
с этим типом.) К отделяя проблемы от ввода проверки в новый тип, мы закончили с более чистого кода.