如何对 Maybe 和 IO 使用 Do 表示法

How to use Do notation with both Maybe and IO

我正在努力掌握 Haskell 中的 do notation

我可以将它与 Maybe 一起使用,然后打印结果。像这样:

maybeAdd :: Maybe Integer
maybeAdd = do one <- maybe1
              two <- maybe2
              three <- maybe3
              return (one + two + three)

main :: IO ()
main = putStr (show $ fromMaybe 0 maybeAdd)

但是我没有使用单独的函数,而是尝试在主函数中使用带有 Maybe 的 do 表示法。但我没有任何运气。我尝试过的各种尝试包括:

main :: IO ()
main = do one <- maybe1
          two <- maybe2
          three <- maybe3
          putStr (show $ fromMaybe 0 $ return (one + two + three))
main :: IO ()
main = do one <- maybe1
          two <- maybe2
          three <- maybe3
          putStr (show $ fromMaybe 0 $ Just (one + two + three))
main :: IO ()
main = do one <- maybe1
          two <- maybe2
          three <- maybe3
          putStr (show $ (one + two + three))

所有这些都会导致各种类型的编译错误,不幸的是我没能破译正确的方法。

如何实现上述目标?也许可以解释为什么我尝试的方法也是错误的?

每个 do 块必须在单个 monad 中工作。如果你想使用多个 monad,你可以使用多个 do 块。尝试调整您的代码:

main :: IO ()
main = do -- IO block
   let x = do -- Maybe block
          one <- maybe1
          two <- maybe2
          three <- maybe3
          return (one + two + three)
   putStr (show $ fromMaybe 0 x)

你甚至可以使用

main = do -- IO block
   putStr $ show $ fromMaybe 0 $ do -- Maybe block
      one <- maybe1
      two <- maybe2
      three <- maybe3
      return (one + two + three)
   -- other IO actions here

但在某些情况下可读性较差。

MaybeT monad 转换器在这种特殊情况下会派上用场。 MaybeT monad transformer 只是一个类型定义类似;

newtype MaybeT m a = MaybeT {runMaybeT :: m (Maybe a)}

实际上像 MaybeTStateT 等转换器在 Control.Monad.Trans.MaybeControl.Monad.Trans.State 中很容易获得...出于说明目的,它的 Monad 实例可能类似于如下所示;

instance Monad m => Monad (MaybeT m) where
  return  = MaybeT . return . Just
  x >>= f = MaybeT $ runMaybeT x >>= g
            where
            g Nothing  = return Nothing
            g (Just x) = runMaybeT $ f x

所以你会注意到 monadic f 函数采用驻留在 Maybe monad 中的值,而 Maybe monad 本身位于另一个 monad 中(IO 在我们的例子中)。 f 函数完成它的工作并将结果包装回 MaybeT m a.

还有一个 MonadTrans class,您可以在其中获得一些转换器类型使用的常用功能。其中之一是 lift,它用于根据特定实例的定义将值提升到转换器中。对于 MaybeT 它应该看起来像

instance MonadTrans MaybeT where
    lift = MaybeT . (liftM Just)

让我们用 monad 转换器执行你的任务。

addInts :: MaybeT IO ()
addInts = do
          lift $ putStrLn "Enter two integers.."
          i <- lift getLine
          guard $ test i
          j <- lift getLine
          guard $ test j
          lift . print $ (read i :: Int) + (read j :: Int)
          where
          test = and . (map isDigit)

所以当被称为

λ> runMaybeT addInts
Enter two integers..
1453
1571
3024
Just ()

要注意的是,由于 monad 转换器也是 Monad 类型 class 的成员,因此可以无限期地嵌套它们,并且仍然在单个 do 表示法下做事。

编辑: 答案被否决,但我不清楚原因。如果方法有问题,请仔细说明我,以便帮助包括我在内的人更好地学习。

利用编辑 session 的机会,我想添加一个更好的代码,因为我认为基于 Chartesting 可能不是最好的主意,因为它不会考虑负 Ints。因此,让我们在使用 Maybe 类型时尝试使用 Text.Read 包中的 readMaybe

import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Class (lift)
import Text.Read (readMaybe)

addInts :: MaybeT IO ()
addInts = do
          lift $ putStrLn "Enter two integers.."
          i <- lift getLine
          MaybeT $ return (readMaybe i :: Maybe Int)
          j <- lift getLine
          MaybeT $ return (readMaybe j :: Maybe Int)
          lift . print $ (read i :: Int) + (read j :: Int)

我想现在效果更好了...

λ> runMaybeT addInts
Enter two integers..
-400
500
100
Just ()

λ> runMaybeT addInts
Enter two integers..
Not an Integer
Nothing