Рассмотрим определение MonadPlus
:
class Monad m => MonadPlus m where
mzero :: m a
mplus :: m a -> m a -> m a
Как бы вы реализовали mzero
для IO
?Значение типа IO a
представляет вычисление IO, которое возвращает что-то типа a
, поэтому mzero
должно быть вычислением IO, возвращающим что-то любого возможного типа.Ясно, что нет никакого способа вызвать в воображении значение для некоторого произвольного типа, и в отличие от Maybe
нет никакого «пустого» конструктора, который мы можем использовать, поэтому mzero
будет обязательно представлять вычисление ввода-вывода, которое никогда не вернет .
Как вы пишете вычисления ввода-вывода, которые никогда не возвращаются?Либо перейдите в бесконечный цикл, либо, в принципе, сгенерируйте ошибку времени выполнения.Первый имеет сомнительную полезность, поэтому последний - то, с чем вы застряли.
Короче говоря, чтобы написать экземпляр MonadPlus
для IO
, вы должны сделать следующее: Иметь mzero
сгенерировать исключение во время выполнения и mplus
оценить его первый аргумент, перехватывая любые исключения, выданные mzero
.Если исключений нет, верните результат.Если возникает исключение, вместо этого оцените второй аргумент mplus
, игнорируя при этом исключения.
При этом исключения во время выполнения часто считаются нежелательными, поэтому я буду колебаться, прежде чем идти по этому пути.Если вы действительно хотите сделать это таким образом (и не возражаете против увеличения вероятности сбоя вашей программы во время выполнения), вы найдете все, что вам нужно для реализации вышеизложенного, в Control.Exception
.
На практикеВозможно, я бы либо использовал подход монадного преобразователя, если бы я хотел много guard
использовать в результате вычисления монадических выражений, либо если большинство условных выражений зависят от чистых значений, предоставленных в качестве аргументов функции (которые помечаются в вашем примереа) используйте охранников, как в ответе @ Энтони.