【问题标题】:Function passed to list monad bind can be the identity without a compiler error传递给 list monad bind 的函数可以是没有编译器错误的标识
【发布时间】:2014-12-01 15:31:35
【问题描述】:

将列表单子绑定函数应用于简单的列表和标识函数时:

[[1,2],[3,4]] >>= \x -> x

我明白了

[1,2,3,4]

然而Monad类型类的定义:

class Monad m where
  (>>=) :: m a -> (a -> m b) -> m b

似乎建议该函数,在我的例子中是 lambda 函数\x -> x,应该返回与传入的类型不同的类型。在这种情况下,我预计会出现编译器错误,但我没有。我在 ghci 中运行它。

为什么在这种情况下编译器不抛出错误?

【问题讨论】:

  • 不,am b 不必不同。在您的情况下,您有 a = [Int]b = Intm = []
  • flip (>>=) id :: Monad m => m (m b) -> m b
  • 两个类型变量可以是不同的类型。不过,它们没有必须不同;在任何特定调用中,它们都可以相等。

标签: haskell functional-programming monads ghci


【解决方案1】:

标识函数id :: a -> a,或明确的\x -> x 是多态的。这意味着它可以专门用于您构造的任何类型,方法是用某种类型替换 a

在您的情况下(>>= id) 编译器会查看

的第二个参数的类型
(>>=) :: m c -> (c -> m d) -> m d

并以id 的类型尝试unify 他们:

a -> a    -- id
c -> m d  -- the second argument of >>=

当我们替换a = m dc = m d 时,这以最一般的方式得到满足。所以id在表达式(>>= id)中最一般的类型是

id :: m d -> m d

整个表达式的类型是

(>>= id) :: (Monad m) => m (m d) -> m d

这是join 函数。

【讨论】:

    【解决方案2】:

    amb 是类型变量,在给定情况下,没有什么可以阻止 a 等于 m b。这就是多态性的概念:如果某事物的类型为a 而对a 没有任何约束,那么它也有类型Int[[Bool]]c -> [Int] -> d 和(如这里)m b

    所以对于这个特定的调用,a ~ [Int]b ~ Intm ~ [],因此(>>=) 的类型是[[Int]] -> ([Int] -> [Int]) -> [Int]

    【讨论】:

      【解决方案3】:

      内部列表在输出中被视为外部列表,但 list 仍然是 list

      另一种说法是

      foreach x in [[1,2],[3,4]]: 
          foreach y in x: 
              emit y
      

      foreach x in [1,2,3,4]: 
          emit x
      

      就发射的元素而言是“相同的”。

      我发现带有排列子实体的类型演示在视觉上非常吸引人:

      (>>=) :: m a -> (a -> m b) -> m b
      [[1,2],[3,4]] :: [[Int]]    -- actually, (Num a) => [[a]], but never mind that
      \x -> x :: a -> a
      
      (>>=) :: m a           -> (  a   -> m b) -> m b       
      (>>=)    [[1,2],[3,4]] :: (  a   -> m b) -> m b       m a ~ [[Int]]
      (>>=)    [[1,2],[3,4]] :: (  a   -> [b]) -> [b]       m   ~ []
      (>>=)    [[1,2],[3,4]] :: ([Int] -> [b]) -> [b]       a   ~ [Int]
      (>>=)    [[1,2],[3,4]]    (\ x   ->  x ) :: [b]       [b] ~ [Int]
      (>>=)    [[1,2],[3,4]]    (\ x   ->  x ) :: [Int]     b   ~ Int
                                                           -- actually, (Num b) => b
      

      这里,原来是\ x -> x :: (Num b) => [b] -> [b],而不仅仅是a -> a

      你看,当([Int] -> [b])(\ x -> x)的类型匹配时,创建[Int] ~ [b]的等价,[Int]中的[]来自“内部列表”,@中的a 987654332@;并且[b] 中的[] 来自“外部列表”,m b 中的m;但是列表就是列表,如上所述。

      这就是允许将两个列表级别拆分(“连接”)为一个的原因——“扁平化”一个列表,或者更一般地说,将一个 monad 的两个“级别”“连接”为一个。


      查看它的另一种方法是使用特定列表版本扩展一元代码:

      [[1,2],[3,4]] >>= \x -> x
      === concatMap id [[1,2],[3,4]]          === concat [ x | x <- [[1,2],[3,4]]]
      === concat [id [1,2], id [3,4]]         === [ y | x <- [[1,2],[3,4]], y <- x]
      === [1,2,3,4]                           === [1,2,3,4]
      

      对于concatMap f 中的f,重要的是它是一个列表生成函数:f :: a -&gt; [b]

      concatMap id === concat :: [[a]] -&gt; [a] 是一个完全合法的函数。是的,concatjoin 用于列表单子:

      ma >>= f === join (fmap f ma)   -- or, for lists,
               === concat (map f ma)
               === concatMap f ma     -- the definition that we used above
      

      【讨论】:

        猜你喜欢
        • 2020-10-23
        • 2011-06-14
        • 1970-01-01
        • 2021-02-16
        • 2018-02-12
        • 2022-07-23
        • 2014-03-30
        • 2021-10-10
        • 2016-11-13
        相关资源
        最近更新 更多