【问题标题】:Trying to understand Monads. The >> Operator试图理解 Monads。 >> 运算符
【发布时间】:2014-07-20 05:57:34
【问题描述】:

我是 Haskell 的新手,我正在通过 LearnYouAHaskell 学习。我只是无法理解 (>>) 运算符背后的原因。
默认实现是:

(>>) :: (Monad m) => m a -> m b -> m b  
m >> n = m >>= \_ -> n 

(据我所知)忽略第一个值并返回第二个值。但是,在 LearnYouAHaskell 中的示例中会发生这种情况:

ghci> 没有 >> 只有 3 个
没什么
ghci> 只有 3 >> 没有
什么都没有

所以它不会忽略第一个值。然而,通过一些研究,我发现了 here 的这句话

>> 函数绑定运算符忽略其第一个操作的值 并仅返回其第二个操作的结果作为整体结果。

所以我对这个运算符的用法困惑,我想问两件事:

  1. 它实际上做了什么?
  2. 什么时候有用?

【问题讨论】:

    标签: haskell monads operator-keyword


    【解决方案1】:

    >> 函数只忽略第一个值的结果,但它忽略第一个值的副作用。要理解您的示例,请查看 Maybe Monad 是如何定义的:

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

    >>函数是这样定义的:

    m >> k      = m >>= \_ -> k
    

    Nothing >> _ 会根据Maybe monad 的定义产生Nothing。在您的第二个示例中,Just 3 >> Nothing 扩展为 Just 3 >>= \_ -> Nothing 并生成 Nothing。举个例子说明它是如何只忽略第一个动作的值但不忽略副作用的,请考虑以下示例:

    λ> print 3 >> print 4
    3
    4
    

    您可以在上面的示例中看到,虽然它忽略了print 3 的结果,即(),但它并没有忽略它的副作用,即在屏幕上显示3

    >> 函数在您开始使用其他 Haskell 库后变得有用。我偶尔使用它们的两个地方是在处理解析器(parsec、attoparsec)和管道库时。

    【讨论】:

      【解决方案2】:

      它忽略第一个动作的价值,而不是动作本身。

      Just 3 >> Just 5
      

      动作Just 3 的值为3。它在\_ -> n 部分被忽略。总的结果是Just 5

      Just 3 >> Nothing
      

      动作Just 3 的值为3。它在\_ -> n 部分被忽略。总的结果是Nothing

      Nothing >> Just 3
      

      Nothing 操作根本不会产生任何价值。那么它将什么传递给>>=(或>>)的右操作数?它没有! >>= 用于 Maybe monad 的构建使得如果左侧操作是 Nothing,则根本不执行右侧操作,并且整体结果是 Nothing

      【讨论】:

        【解决方案3】:

        要完成 Sibi 回答 >> 在 C 或 C++ 等其他语言中可以看作是 ;..

        当你用 C 语言(或其他语言的等效语言)做时

        printf("foo"); printf("bar");

        您显然打印了foobar(副作用),但那些对printf 的调用也有一个返回值,在我们的例子中是打印的长度,即3 3。你有没有想过这些数字会发生什么? 他们被抛弃了,因为在 C 中,expr 1; exp 2,表示

        • 评估 expr1
        • 放弃它的结果
        • 评估 expr2

        (此时,您可能会问自己,如果编译器要丢弃其结果,为什么还要费心评估 expr1 ?因为副作用。在printf 的情况下,副作用是打印一些东西。你很少对返回值本身感兴趣。)

        所以; 可以看作是一个接受2 表达式并返回一个新表达式的运算符。这与>> 操作符所做的完全相同。

        当你写作时

         print "foo" >> print "bar"
        

        它完全等同于printf("foo");printf("bar"),除了(这是一个主要区别)>> 不像 C 中的; 那样神奇。>> 它是一个用户定义的运算符,可以为每种类型的 Monad 重新定义。 这就是 Haskell 程序员如此钟爱 Monad 的原因:简而言之,它允许您重新定义自己的 ; 行为。

        正如我们所见,在 C 语言中,; 只计算一个表达式并丢弃它的值。事实上,它有点复杂,因为它不是breakreturn。 Maybe monad 中的 Nothing 可以被视为 breakreturn>> 计算第一个表达式,如果是 Nothing 则停止。否则它会丢弃它的值并继续。

        你的第一个例子可以在C中看到(我认为它是有效的C)

        3; return
        

        return; 3
        

        第一个例子,计算3,丢弃它的值并返回。 第二,直接返回。

        回答你的问题when is it usefull?几乎所有时间都在使用 IO,即使您很少看到它。

        而不是写

         print "foo" >> print "bar"
        

        Haskell 提供了一种语法糖,它通过 do-notation 将(几乎)换行符转换为>>, 所以你会写

        do
          print "foo"
          print "bar"
        

        与前一个版本严格等价(实际上do符号版本被编译器转换为前一个版本)。

        它甚至也等价于(尽管很少使用)

        do print "foo"; print "bar"
        

        总而言之,>> 可以被视为等同于 ; 或换行符是其他语言,不同之处在于它的确切含义取决于 上下文(Monad 所作用的)。 Maybe monad 中的 >> 与 IO Monad 中的 >> 不同。

        【讨论】:

          【解决方案4】:

          首先让我们澄清您对Maybe monad 的困惑。考虑以下

          instance Monad Maybe where
            return = Just
            (Just x) >>= g = g x
            Nothing  >>= _ = Nothing
          

          正如你所看到的,Nothing >>= _Nothing。由于>> 只是>>= 参数被忽略的特例,所以结果是一样的。

          这是因为Maybes 通常用于表示可能失败的计算。这是在告诉我们“一旦失败,你就永远失败”。

          现在回答你的问题。

          1. 你提到的那句话已经回答了。
          2. 它在某些不需要操作结果的情况下很有用。例如,putStrLn 的结果是(),这既不有趣也没有任何用处。

          【讨论】:

            【解决方案5】:

            原因很简单:简单的话monad实现需要两个操作:

            • >>=
            • >>

            第一个执行动作并将其动作结果传递给另一个动作。例如:

            Prelude> :t getLine
            getLine :: IO String
            Prelude> :t putStrLn
            putStrLn :: String -> IO ()
            

            两个函数:第一个getLine只返回用IO包裹的字符串,它从stdin读取字符串并将其包裹到IO。第二个putStrLn 得到String 并打印出来。 Bind 有以下类型:

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

            它可以表示为IO String -> (String -> IO String) -> IO String,因此您可以通过执行getLine并将结果String传递给putStrLn,将这两个或多个函数与(>>=)结合起来:

            getLine >>= putStrLn
            

            因此您可以将这两个或多个功能组合在一个操作中。

            第二个>> 产生的结果几乎相同,但它不需要前面操作的结果。

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

            它只是执行,第一个动作,然后第二个动作,第二个动作在第一个动作的结果中是不需要的:

            putStrLn "Hello" >> putStrLn "World"
            

            【讨论】:

              猜你喜欢
              • 2018-03-31
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-12-14
              • 2021-07-20
              • 2016-11-22
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多