【问题标题】:Correct usage of the do notation正确使用 do 表示法
【发布时间】:2011-12-19 06:12:27
【问题描述】:

我正在尝试理解 Monad,我有以下代码

f a b c d =
   do one <- a + b
      two <- c * d
      three <- one + two
      return three

以上编译

但是当我遇到错误时

*主> f 1 2 3 4 :1:1: (Num (a0 -> t0), Monad ((->) a0), Monad ((->) t0)) 没有实例 因使用“f”而产生 可能的修复: 添加实例声明 (Num (a0 -> t0), Monad ((->) a0), Monad ((->) t0)) 在表达式中:f 1 2 3 4 在“it”的等式中:it = f 1 2 3 4 :1:9: (Num (a0 -> a0 -> t0)) 没有实例 源自文字“4” 可能的修复: 为 (Num (a0 -> a0 -> t0)) 添加一个实例声明 在`f'的第四个参数中,即`4' 在表达式中:f 1 2 3 4 在“it”的等式中:it = f 1 2 3 4

如果我知道为什么上面的代码不起作用,我想我会更接近理解 Monad
f 1 2 3 4

【问题讨论】:

    标签: haskell monads


    【解决方案1】:

    你想使用哪个 Monad?

    与您尝试做的最接近的匹配是函数 monad ((-&gt;) a)(因此出现 GHC 错误),但是由于您在每个阶段都提供了两个参数,所以这不起作用。

    不要试图通过将代码放入 do-blocks 来理解一般的 Monad:了解如何使用特定的 Monad(例如学习如何使用 IO、学习如何使用 monadic 解析库等),以及然后了解它们的共性以及 Monad 抽象是如何工作的。

    每个 Monad 都有自己独特的特征和工作方法(因为如果他们没有,那么拥有多个 Monad 有什么意义?)。

    我特别喜欢 Real World Haskell 的处理方法:自始至终都引入了各种 Monad,并带有辅助组合器来帮助管理样板文件,然后在 Chapter 7 中将它们全部组合在一起,正式引入了 Monad 类型类。

    【讨论】:

      【解决方案2】:

      我认为您对 &lt;- 的用途感到困惑。该操作符从 monad 中“解包”数据;在这种情况下是 IO monad。例如,如果您调用readLn,您将收到IO a 类型的结果。如果您想在 monad 中使用 a,您可以将 input &lt;- readLn 放在 do 构造中,这会将 a 的值绑定到 input。但是,a + b 的值不在 IO monad 或任何 monad 中。在您的示例使用中,a + b 只是Int 类型。因此,如果您想声明一个等于该变量的变量,请使用 let 语句:let one = a + b。您的其他声明也是如此。所以你会想把这个函数重写为:

      f a b c d = do
          let one = a + b
          let two = c * d
          let three = one + two
          return three
      

      附加说明:添加类型签名通常可以帮助调试这类事情。如果我将此类型签名添加到您的原始函数:f :: Int -&gt; Int -&gt; Int -&gt; Int -&gt; IO Int,我会得到一个更有帮助的错误,它说预期的类型是IO t0,但在表达式one &lt;- a + b 中实际类型是Int。这应该可以帮助您意识到 &lt;- 期望一个单子值作为其右侧参数,但它却收到了一个 Int

      【讨论】:

        【解决方案3】:

        问题是您将包装的一元值与纯值混淆了。

        首先要知道的是,do 表示法是常规函数调用的语法糖(&gt;&gt;=&gt;&gt;)。因此,这将有助于查看您的代码也脱糖。

        让我们尝试一些更简单的方法

         f a b =
           do one <- a + b
              return one
        

        这与您的代码有相同的问题,但更简单。为了理解为什么它不起作用,我们问:这实际上意味着什么?好吧,我们可以使用&gt;&gt;= 重写&lt;- 符号

         f a b = (a + b) >>= \x -> return x
        

        (这不是最简单的表示,但说明了重点)

        如果您在 GHCi 中测试以下内容

         >> :t (>>=)
         Monad m => m a -> (a -> m b) -> m b
        

        也就是说,函数&gt;&gt;= 接受:am 类型的参数和b 的从am 的函数,并返回bm

        这段代码呢?

        (a + b)
        

        将是一个数字。 另一半呢

         \x -> return x
        

        获取a 类型的对象并为任何a 返回m a 类型的对象

        所以,你需要有一个数字,这也是某种单子。你能想到这样的事情吗?目前尚不清楚这将是什么,因此有理由怀疑这是否应该进行类型检查。

        了解 monad 的一个好方法是看一些具体的例子。

        Maybe monad 表示可能失败的计算

         instance Monad Maybe where
              return = Just
              (>>=) (Just a) f = f a
              (>>=) Nothing _ = Nothing
        

        这让你可以用类似的语气说话

         f args = do x <- functionThatMightFail args
                     y <- anotherfunctionThatMightFail x
                     return y
        

        或者更简单的相同代码

        f args = do x <- functionThatMightFail args
                    anotherfunctionThatMightFail x
        

        或许

        f args = functionThatMightFail args >>= anotherfunctionThatMightFail
        

        另一方面,List monad 捕获了对列表的每个元素执行相同功能,然后将结果连接在一起的想法。简单的例子比比皆是:

        f = do x <- [1,2,3,4]
               [1..x]
        

        如果您了解这些,请使用State monad。它可以帮助您了解“monad 是计算模型”这一更一般的概念。然后我会检查 Parsec,当然还有 IO

        【讨论】:

          【解决方案4】:

          我将不同意其他所有人的观点,并且几乎可以肯定地说你正在做的事情只是 与 monads 无关。您可能只想使用一些像这样无聊的旧代码:

          f a b c d = three where
              one = a + b
              two = c * d
              three = one + two
          

          或更简洁地说:

          f a b c d = a + b + c * d
          

          【讨论】:

          • 这不是我说的吗? :p
          • 从语义的角度来看,每一种“让”或评估顺序管理都与 monad 相关。在 Haskell 中,很好的应用语法是为偏性 monad(有些人称之为“纯”)保留的,而其他 monad 的语法很笨拙。 OP 对运算符应该对哪些 values 采取行动有一个明确的想法,但是 Haskell 需要一个插入了额外 clunks 的 monadic 程序来管理 value-computation 区别,或者(可能在这里)撤退到单独的“纯”语言。 SHE 得到了 f a b c d = (|(|a + b|) + (|c * d|)|) 的笨拙,但这仍然笨拙。
          【解决方案5】:

          如果您尝试使用 Maybe monad 进行错误处理,这里有一个工作示例:

          module Bbb where
          
          import Data.Maybe
          
          f a b c d =
             do one <- return $ a + b
                two <- return $ c * d
                three <- return $ one + two
                return three
          
          main = print $ fromJust $ f 1 2 3 4
          

          import Data.Maybe 子句适用于fromJust,只是

          fromJust (Just x) = x
          

          如果您只是想将&lt;- 解释为作业,请不要:)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-01-10
            • 1970-01-01
            相关资源
            最近更新 更多