Извлечение значения Maybe в IO
Учитывая следующее:
> (liftM2 fromMaybe) (ioError $ userError "OOPS") (return $ Just "ok")
ghci дает мне
*** Exception: user error (OOPS)
Конечно, fromMaybe работает правильно:
> (liftM2 fromMaybe) (return $ "not me") (return $ Just "ok")
"ok"
Но кажется, что операция ввода-вывода выполняется, а затем отбрасывается:
> (liftM2 fromMaybe) (putStrLn "computing.." >> "discarded") (return $ Just "ok")
computing..
"ok"
Почему это происходит? Есть ли способ сделать IO monad ленивым?
В частности, данный value :: IO (Maybe a)
, какой (чистый, лаконичный) способ сказать
result <- (liftM2 fromMaybe) err value
и распечатать его или выбросить IOError соответственно?
Ответы
Ответ 1
Я не знаю, что создание IO
lazier - правильное направление здесь. То, что вы, похоже, хотите сделать, это сначала получить Maybe
, а затем устранить. Это можно записать несколькими способами, здесь один из вариантов:
test :: IO (Maybe a) -> IO a
test = (>>= maybe (ioError $ userError "oops") return)
Ответ 2
Если вы переводите с liftM2
на нотацию, очевидно, почему ваш код не работает:
do x <- ioError $ userError "OOPS"
y <- return $ Just "ok"
return $ fromMaybe x y
Это никогда не пройдет мимо первой строки, поскольку она безоговорочно бросает исключение.
Предложение Энтони будет работать нормально, но если вы не заботитесь о конкретном исключении, вы также можете использовать сопоставление с шаблонами:
do Just result <- value
Если шаблон не соответствует, это вызовет fail
, который в случае IO
monad выдает исключение.
> Just x <- return Nothing
*** Exception: user error (Pattern match failure in do expression at <interactive>:1:0-5)
Ответ 3
какой (чистый, лаконичный) способ... распаковать [] результат или выбросить IOError соответственно?
Я рекомендую вам не полагаться на ошибки бросания. Вместо этого обработайте "ошибку" явно:
maybeM :: Monad m => m b -> (a -> m b) -> m (Maybe a) -> m b
maybeM err f value = do
x <- value
case x of
Just y -> f y
Nothing -> err
-- This can be written simply as:
maybeM err f value = do
x <- value
maybe err f x
-- or even shorter! This is starting to look like Anthony answer :)
maybeM err f value = value >>= maybe err f
Входы и типы функций должны говорить сами за себя. Вы используете его, предоставляя ему действие для выполнения для случая Nothing
или функцию, выполняемую внутри значения для случая Just
. Для ваших конкретных входов это будет выглядеть так:
maybeM (ioError $ userError "OOPS") return (return $ Just "ok")
Итак, если вам абсолютно необходимо, то "сжатый способ распаковать результат или выбросить IOError" будет:
-- compare to fromJust, a function to be avoided
fromJustIO :: IO (Maybe a) -> IO a
fromJustIO = maybeM (ioError $ userError "OOPS") return
Обратите внимание, что сигнатура типа для этого практически Maybe a -> a
, что является сущностью magicMonadUnwrap :: Monad m => m a -> a
, в котором должны быть указаны некоторые красные флаги. Однако вы можете использовать это зверство простым способом:
result <- fromJustIO value
Хотя я снова категорически не рекомендую использовать исключения. Попробуйте обработать ошибки более элегантным способом, чем просто взрыва, используя maybeM
и предоставляя действие IO для выполнения в случае сбоя.