Haskell "исключения"

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

Поэтому в основном я хочу, чтобы эти функции выдавали "исключения", которые должны быть явно обработаны вызывающим.

Я сначала подумал о возвращении чего-то вроде этого:

data Return r e = Success r | Exception e

И если вызывающий абонент не сможет сопоставить шаблон с Exception, они, надеюсь, получат предупреждение о компиляторе или, по крайней мере, имеют очевидную ошибку времени выполнения при возникновении проблемы.

Это лучший подход, и есть ли это готовое решение? Примечание. Мне нужно перехватывать "исключения" в чистом коде, а не в IO Monad.

Ответы

Ответ 1

Да, это хороший подход, и он находится в стандартной библиотеке: Return r e совпадает с Either e r. Вы даже можете писать код, как если бы вы использовали исключения в IO (тоже, без необходимости явно обрабатывать ошибки на каждом шаге с помощью сопоставления с образцом): экземпляр Monad для Either распространяется на ошибки, как и на Maybe monad делает (но с дополнительным значением e в случае ошибки). Например:

data MyError
    = Oops String
    | VeryBadError Int Int

mightFail :: T -> Either MyError Int
mightFail a = ...

foo :: T -> T -> Int -> Either MyError Int
foo a b c = do
    x <- mightFail a
    y <- mightFail b
    if x == y
        then throwError (VeryBadError x y)
        else return (x + y + c)

Если mightFail a или mightFail b возвращает Left someError, то foo a b c тоже будет; ошибки автоматически распространяются. (Здесь throwError является просто хорошим способом записи Left, используя функции из Control.Monad.Error, а также catchError, чтобы поймать эти исключения.)

Ответ 2

Тип Return r e, который вы описываете, является стандартным типом

data Either a b = Left a | Right b

Возможно, вы захотите использовать так называемую "monad ошибки" (более подходящим названием является "monad" исключения) пакета mtl. (Альтернативно, там ExceptionT в пакете monadLib, если вы не хотите использовать mtl.) Это позволяет вам обрабатывать ошибки в чистом коде, вызывая throwError и catchError. Здесь вы можете найти пример, который показывает, как его использовать.