Определение MonadPlus для Haskell IO

Я просто написал быстрый код, и я хотел использовать функцию защиты в IO Monad. Тем не менее, существует определение MonadPlus для IO, что означает, что мы не можем использовать защиту в зоне ввода-вывода. Я видел пример использования трансформатора MabyeT для использования защиты в Maybe Monad, а затем снятие всех операций ввода-вывода, но я действительно не хочу делать это, если мне это не нужно.

Некоторый пример того, что я хочу, может быть:

handleFlags :: [Flag] -> IO ()
handleFlags flags = do
    when (Help `elem` flags) (putStrLn "Usage: program_name options...")
    guard (Help `elem` flags)
    ... do stuff ...
    return ()

Мне было интересно, есть ли хороший способ получить функцию защиты (или что-то подобное) в IO Monad через объявление для MonadPlus или иначе. Или, возможно, я делаю это неправильно; есть ли лучший способ написать это справочное сообщение в функции выше? Спасибо.

(P.S. Я мог бы использовать операторы if-then-else, но, похоже, как-то побеждает точку. Не говоря уже о том, что для множества опций это приведет к огромному количеству вложенности.)

Ответ 1

Рассмотрим определение MonadPlus:

class Monad m => MonadPlus m where
    mzero :: m a 
    mplus :: m a -> m a -> m a

Как бы вы реализовали mzero для IO? Значение типа IO a представляет собой вычисление IO, которое возвращает что-то типа a, поэтому mzero должно быть вычислением ввода-вывода, возвращающим что-то из любого возможного типа. Ясно, что нет способа вызвать значение для какого-либо произвольного типа, и в отличие от Maybe нет никакого "пустого" конструктора, который мы можем использовать, поэтому mzero обязательно будет представлять собой вычисление IO, которое никогда не возвращается.

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

Короче говоря, чтобы написать экземпляр MonadPlus для IO, что бы вы сделали, это следующее: Have mzero вывести исключение во время выполнения и иметь mplus оценить свой первый аргумент, вылавливая любые исключения, mzero. Если никаких исключений не возникает, верните результат. Если возникает исключение, оцените второй аргумент mplus, игнорируя исключения.

Тем не менее, исключения во время выполнения часто считаются нежелательными, поэтому я бы смутился, прежде чем идти по этому пути. Если вы действительно хотите сделать это таким образом (и не возражаете увеличить вероятность того, что ваша программа может упасть во время выполнения), вы найдете все, что вам нужно, чтобы реализовать выше в Control.Exception.

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

Ответ 2

Я делаю такие вещи с охранниками.

handleFlags :: [Flag] -> IO ()
handleFlags flags
  | Help `elem` flags = putStrLn "Usage: program_name options..."
  | otherwise = return ()

Ответ 3

Существуют функции, специально сделанные для этого: в Control.Monad - функции when и его аналог unless. Ответ Энтони может быть переписан как таковой:

handleFlags :: [Flag] -> IO ()
handleFlags flags =
    when (Help `elem` flags) $ putStrLn "Usage: program_name options..."

технические характеристики:

when :: (Applicative m) => Bool -> m () -> m ()
unless bool = when (not bool)

Ссылка на документы на hackage.haskell.org

Если требуется больше, здесь ссылка на другой пакет, в частности монад-ориентированный и с несколькими дополнительными утилитами: Control.Monad.IfElse