Я просто написал быстрый код, и я хотел использовать функцию защиты в 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