【问题标题】:How to use Do notation with both Maybe and IO如何在 Maybe 和 IO 中使用 Do 表示法
【发布时间】:2020-07-24 00:13:03
【问题描述】:

我正在努力掌握 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)

但我没有使用单独的函数,而是尝试在 main 函数中使用带有 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))

所有这些都会导致各种类型的编译错误,遗憾的是我未能破译以找到正确的方法。

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

【问题讨论】:

  • 出于这个原因,有 Monad Transformers 抽象。对于您的情况,即MaybeT IO ()。然后你将有一个单独的 do 块。
  • 感谢~Redu 的参与。我猜 MaybeT 住在某个地方的另一个模块中。那会在哪里?它的使用情况如何?现在 main 的类型是 MaybeT IO () 吗?我不认为这是正确的
  • 我试图通过以下回答来解决您的问题。

标签: haskell monads do-notation


【解决方案1】:

每个 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

但在某些情况下可能不太可读。

【讨论】:

  • Each do block must working with a single monad - 我认为这是我缺少的部分。感谢您指出这一点!
【解决方案2】:

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

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 类,您可以在其中拥有一些变压器类型使用的常见功能。其中一个是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 类型类的成员,因此可以无限期地嵌套它们,并且仍然在单个 do 符号下执行操作。

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

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

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

【讨论】:

  • 虽然我得到了你的目标,但我觉得这个答案会更清楚,如果至少在最初的例子中,它试图更接近地匹配问题中的代码,而忽略了 @987654350 @s 和getLines。作为一个次要的风格建议,使用liftIO 而不是lift 来提升IO 动作可能会更好,因为它更容易转换为 mtl 代码,或者使用多个转换器层IO 的顶部。
  • @duplode 很公平。无论如何,如果代码很好,我对我的反对意见感到满意,因为这也是我学习实践的一部分。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多