Извлечение значения 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 для выполнения в случае сбоя.