【问题标题】:Haskell: variable scope in list monadHaskell:列表单子中的变量范围
【发布时间】:2018-01-04 18:45:30
【问题描述】:

我正在阅读一本关于列表单子的在线 Haskell 书籍。在书中,list monad 是这样定义的:

instance Monad [] where  
    return x = [x]  
    xs >>= f = concat (map f xs)  
    fail _ = []

然后有一个这样的 list monad 用法示例:

Prelude> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)  
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]

我是 Haskell 的新手,我对这个示例的疑问是,变量“n”如何在 return (n,ch) 的 lambda 表达式中可用。 n 在另一个 lambda 表达式中定义,我不明白为什么在一个 lambda 表达式中定义的变量在后续 lambda 表达式中可用。我尝试根据列表单子定义转换如下示例:

Prelude> concat (map (\ch -> return (n, ch)) (concat (map (\n -> ['a', 'b']) [1, 2])))

<interactive>:32:29: error: Variable not in scope: n

但正如您所见,我收到一条错误消息,指出变量 n 在另一个 lambda 表达式的范围内不可用。也许这本书只是介绍了列表单子定义的简化版本?

【问题讨论】:

    标签: list haskell monads


    【解决方案1】:
    [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)  
    

    不被解析为

    [1,2] >>= (\n -> ['a','b']) >>= (\ch -> return (n,ch))
    

    但作为

    [1,2] >>= \n -> (['a','b'] >>= (\ch -> return (n,ch)))
    

    您对concat / map 的翻译反映了“错误”的解析。我们可以将其调整为正确的。

    第一个&gt;&gt;=变成

    concat (map (\n -> ???) [1,2])
    

    现在我们可以根据需要翻译内部的&gt;&gt;= 替换???

    ??? = concat (map (\ch -> ???2) ['a','b'])
    ???2= return (n,ch)
    

    结果:

    concat (map (\n -> concat (map (\ch -> return (n,ch)) ['a','b'])) [1,2])
    

    【讨论】:

      【解决方案2】:

      因为你的表情:

      [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)
      

      相当于:

      --                  body of the lambda expression
      --                _________^_______________________
      --               /                                 \
      [1,2] >>= (\n -> (['a','b'] >>= \ch -> return (n,ch)))
      --        \________________ _________________________/
      --                         v
      --              entire right operand
      

      所以在第一个&gt;&gt;= 的右边你写了一个 lambda 表达式

      [] monad 的定义是:

      instance Monad [] where  
          return x = [x]  
          xs >>= f = concat (map f xs)
      

      所以你写了:

      [1,2] >>= (\n -> (['a','b'] >>= \ch -> return (n,ch)))
       -> concat (map (\n -> (['a','b'] >>= \ch -> return (n,ch)) [1,2])
       -> concat (map (\n -> concat (map (\ch -> return (n,ch)) ['a','b'])) [1,2])
       -> concat (map (\n -> concat (map (\ch -> [(n,ch)]) ['a','b'])) [1,2])
      

      所以n\ch -&gt; [(n,ch)] 表达式的范围内。通过最后一条语句,我们得到:

      Prelude> concat (map (\n -> concat (map (\ch -> [(n,ch)]) ['a','b'])) [1,2])
      [(1,'a'),(1,'b'),(2,'a'),(2,'b')]
      

      【讨论】:

      • &gt;&gt;= 的固定性无关紧要,列表和 monad 也是如此。真正的重点是 lambda 表达式的主体总是尽可能向右延伸。
      • 好吧,尽可能向右忽略固定性。如果你写\x -&gt; x == 3 == 4,它将被解析为\x -&gt; (x == 3 == 4),然后给出错误,因为==被声明为infix(非关联)而不是infixlinfixr。 Haskell 98 实际上要求将其解析为 (\x -&gt; x == 3) == 4,但 GHC 从未实现过,显然 Haskell 2010 不再需要它。
      【解决方案3】:

      @chi 和 @Willem Van Onsem 的答案非常到位,但只是为了多样化,我想提一下,列表推导式只是这个单子工作的语法糖;

      Prelude> [(x,y) | x <- [1,2], y <- ['a', 'b']]
      [(1,'a'),(1,'b'),(2,'a'),(2,'b')]
      

      然而,由于列表也是 Haskell 中应用函子类的实例,另一种在不接触 monad 实例的情况下完成这项工作的好方法是;

      Prelude> (,) <$> [1,2] <*> ['a','b']
      [(1,'a'),(1,'b'),(2,'a'),(2,'b')]
      

      我想稍微阐述一下后者,

      (,) 实际上是一个带有两个参数和类型签名的函数; a -&gt; b -&gt; (a,b)。而&lt;$&gt;fmap 的内联形式。因此,当我们应用(,) &lt;$&gt; [1,2] 时,我们得到的是一个应用函子(函数列表),例如[(,) 1, (,) 2]。现在我们可以应用具有类型签名Applicative f =&gt; f (a -&gt; b) -&gt; f a -&gt; f b 的应用运算符&lt;*&gt; 在类型签名f 中不应与函数混淆。它指定了一个函子,这里恰好是[] (list) 类型。这就是说,&lt;*&gt; 将从函子中解包包含的函数并将其应用于提供的函子的包含值,以以相同的函子类型返回这些应用程序的结果。很明显,由于列表应用程序是由所有元素一对一定义的,因此结果是所有组合的元组列表。

      【讨论】:

        猜你喜欢
        • 2020-11-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-08
        • 1970-01-01
        • 2018-04-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多