【问题标题】:Haskell monad flow understandingHaskell单子流理解
【发布时间】:2017-10-08 17:22:53
【问题描述】:

我将在底部用示例重申我的问题。

当我说mk 时,我指的是函数调用中的左右值m >>= k

对 monad 的理解有些模糊。如果m 是一个计算并且k 是一个lambda 表达式,这是否意味着k 有两个用途,它可以使用它来处理do 块中的绑定,也可以在Monad 实例中用于其他用途定义?如果一个值在 k 在其 do 块中被评估之前绑定,值会自动传递给它还是 Monad 实例方法定义,我们定义传播到 k 的唯一效果?

我看到的例子可能有点误导我在解释 monad 时总是将前一个绑定项直接传递给 lambda 表达式。即使使用的符号是pure-do-no-lambda,它是否总是传递给隐藏的lambda的最后一个绑定项,在引擎盖下。现在,使用带有多个参数的 lambda 并将其用作 m >>= k 中的 k 是不好的做法吗?或者我假设如果我们在 do 块中工作,在幕后,只有一个参数被传递给下一个“隐藏”的 lambda 表达式并且这个参数是之前直接绑定的项目,我错了吗?

我现在将用例子重申我的问题。

do
    a <- getLine
    b <- getLine
    putStrLn $ a ++ b

ab 绑定到从运行 getLine 返回的 IO 容器中的值。在引擎盖下,以下哪项是等效的(如果有的话)?

getLine >>= \a -> getLine >>= \b -> putStrLn (a ++ b)

getLine >>= \a -> getLine >>= \(a, b) -> putStrLn (a ++ b)

我们看到getLineIO 容器中的值被提取并传递给 lambda 表达式。

如果第一个是正确的,那不会导致错误,因为 a 将在 monad 定义中未定义?

我凭空取出了第二个。我没有看到任何证据表明它可能是这样,但即使它不是正确的答案,我们可以这样做吗?当然,在这种情况下,我们必须使与&gt;&gt;= 一起使用的所有 lambda 表达式都采用 2 元组。这种行为是否不仅仅由我们的&gt;&gt;= 定义决定,至少在使用 only-lambda-no-do 表示法时如此?

我们可以在 monad 定义中使用这个 lambda 表达式并传递一些东西给它,让它再次打印屏幕吗?我们应该吗?

如果我们想在&gt;&gt;= 定义中使用putStrLn,我们是否必须将一些任意值传递给k 才能得到putStrLn 函数?

谢谢。和平。

【问题讨论】:

    标签: haskell lambda functional-programming monads


    【解决方案1】:

    do 这样的符号:

    do
        a <- getLine
        b <- getLine
        putStrLn $ a ++ b
    

    等价于

    getLine >>= \a -> getLine >>= \b -> putStrLn (a ++ b)
    

    这又等同于

    getLine >>= (\a -> getLine >>= \b -> putStrLn (a ++ b))
    

    注意额外的括号。第一个getLine 由一个lambda 表达式组成,其中a 是“包含的元素”。在这个 lambda 表达式内部,调用了一个新的表达式。那个表达是

    getLine >>= \b -> putStrLn (a ++ b)
    

    此表达式仍在第一个 lambda 表达式“内部”,这意味着 a 仍在范围内。

    如果有帮助,您可以在表达式周围放置更多括号:

    getLine >>= (\a -> getLine >>= (\b -> putStrLn (a ++ b)))
    

    这些括号完全是多余的,但它们突出了各种表达式的作用域。请注意,ab 在调用 putStrLn (a ++ b) 时仍在作用域内。

    【讨论】:

      【解决方案2】:

      我只会给你一个部分答案,因为我不明白你到底在问什么。很抱歉。

      do
          a <- getLine
          b <- getLine
          putStrLn $ a ++ b
      

      等价于

      getLine >>= \a -> getLine >>= \b -> putStrLn (a ++ b)
      

      这行得通,因为

      \b -> putStrLn (a ++ b)
      

      创建一个函数,从它定义的范围内捕获a 如果在这个术语的范围内没有这样的a,编译器会抱怨a不在范围内。

      让我们稍微扩展一下,看看

      \a -> getLine >>= \b -> putStrLn (a ++ b)
      

      再次。这定义了一个函数,如果将其应用于某个值 x,则返回表达式的结果

      getLine >>= \b -> putStrLn (x ++ b)
      

      这里的变量a 被值x 替换。所以不用担心a

      【讨论】:

        【解决方案3】:

        未加糖的do block 将类似于getLine &gt;&gt;= \a -&gt; getLine &gt;&gt;= \b -&gt; putStrLn (a ++ b),由于本地范围,您不需要传递之前调用的所有参数,让我们表示它们:

        getLine >>= 
        (!This is first closure! \a -> getLine >>= 
        (!This is second, all variables from the first 
        closure are available, because in haskell function closure takes outer scope in! \b -> 
        putStrLn(a ++ b) !End of the second closure!) !End of the first closure!)
        

        现在关于 (>>=) 的类型。 GHCI 打印下一个 (&gt;&gt;=) :: Monad m =&gt; m a -&gt; (a -&gt; m b) -&gt; m b。 所以k 只是Monad m 可以使用的lambda,它所要做的就是为&gt;&gt;= “实现接口”。

        【讨论】:

          【解决方案4】:

          do 是否等同于以下内容?

          getLine >>= \a -> getLine >>= \b -> putStrLn (a ++ b)
          

          是的,这个。

          如果第一个是正确的,那会不会导致错误,因为 a 在 monad 定义中是未定义的?

          没有。 -&gt; 箭头语法是右关联的:

          getLine >>= (\a -> getLine >>= (\b -> putStrLn (a ++ b)))
          

          它形成了一个闭包,其中a 仍在范围内。

          【讨论】:

          • 请注意,&gt;&gt;= 运算符实际上是左关联的。只是 lambda 语法 \x -&gt; ... 是一种特殊的语法形式,可以“尽可能多地”吞噬。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-01-15
          • 2015-12-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多