在IO中提取Maybe值

16

给定以下内容:

> (liftM2 fromMaybe) (ioError $ userError "OOPS") (return $ Just "ok")

ghci给我

*** Exception: user error (OOPS)

当然,fromMaybe函数是正确的:

> (liftM2 fromMaybe) (return $ "not me") (return $ Just "ok")
"ok"

但似乎IO操作正在被执行,然后被丢弃:

> (liftM2 fromMaybe) (putStrLn "computing.." >> "discarded") (return $ Just "ok")
computing..
"ok"

为什么会发生这种情况?有没有办法使IO单子变得更懒惰?

具体而言,针对 value :: IO (Maybe a) ,有没有一种简洁明了的方法来表达?

result <- (liftM2 fromMaybe) err value
并且将其解压缩并返回结果或相应地抛出IOError?
3个回答

12

我不确定让IO更慢是正确的方向。你似乎想要做的是先获取Maybe,然后消除它。这可以用多种方式来编写,以下是一种选项:

test :: IO (Maybe a) -> IO a
test = (>>= maybe (ioError $ userError "oops") return)

10

如果你从liftM2转换为do-notation,那么为什么你的代码失败就显而易见了:

do x <- ioError $ userError "OOPS"
   y <- return $ Just "ok"
   return $ fromMaybe x y

由于它无条件地抛出异常,因此代码永远不会执行第二行。

Anthony的建议可以正常工作,但如果您不关心抛出的特定异常,也可以使用模式匹配:

do Just result <- value
如果模式不匹配,这将调用 fail,在 IO 单子中会抛出异常。
> Just x <- return Nothing
*** Exception: user error (Pattern match failure in do expression at <interactive>:1:0-5)

5
“清晰简洁”的方式是什么,可以 unpack 结果或相应地抛出 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's 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操作来处理失败事件。


谢谢关心,但这有点没有根据。我在许多第二层函数中抛出错误,每个函数都会进行一些IO操作以获取一些配置信息。第一层配置函数使用“catch”尝试它们所有,并格式化任何错误。它使我能够将错误处理保持在一个地方,而不需要在所有第二层函数中线程“handleErr”函数。在这种情况下,错误仍然是一个坏主意吗? - So8res
4
个人而言,我会让第二层的函数生成Maybe值,然后第一层的配置函数可以使用像sequence<$><*>这样的方法将它们合并在一起,或者根据需要单独处理每个潜在的错误。这是一个品味问题,但我认为,在Haskell中,错误几乎总是不好的想法,因为我们有强大的抽象使我们能够组合Maybe。throw/catch控制流机制不符合Haskell鼓励的FP风格。 - Dan Burton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接