【问题标题】:Haskell Monad - How does Monad on list work?Haskell Monad - 列表中的 Monad 是如何工作的?
【发布时间】:2018-12-12 20:23:38
【问题描述】:

为了理解 Monad,我想出了以下定义:

class Applicative' f where
 purea :: a -> f a
 app :: f (a->b) -> f a -> f b

class Applicative' m =>  Monadd m where
 (>>|) :: m a -> (a -> m b) -> m b

instance Applicative' [] where
 purea x = [x]
 app gs xs = [g x | g <- gs, x <- xs]

instance Monadd [] where
 (>>|) xs f = [ y | x <-xs, y <- f x]

它按预期工作:

(>>|) [1,2,3,4] (\x->[(x+1)])
[2,3,4,5]

我不确定它是如何工作的。 例如:

[ y | y <- [[1],[2]]]
[[1],[2]]

(\x-&gt;([x+1]) 应用到[1,2,3] 的每个列表元素如何导致[2,3,4] 而不是[[2],[3],[4]]

或者很简单,我的困惑似乎源于不理解这句话 [ y | x &lt;-xs, y &lt;- f x] 的实际工作原理

【问题讨论】:

  • 你自己写的。 (&gt;&gt;|) xs f = [ y | x &lt;-xs, y &lt;- f x]。这是原始列​​表的不是“应用程序...对每个元素”,这是另外一回事。对每个元素的应用程序将是 map f xs = [f x | x &lt;- xs]
  • 是的,看来我还没有理解 [ y | x &lt;-xs, y &lt;- f x] 的工作原理
  • 在那个表达式中写下 x、xs、y 和 f x 的类型应该会有所帮助。

标签: list haskell monads


【解决方案1】:

使用“数学定义”通常比使用 Haskell 标准类的方法更容易理解 Monad。即,

class Applicative' m => Monadd m where
  join :: m (m a) -> m a

请注意,您可以按此实现标准版本,反之亦然:

join mma = mma >>= id

ma >>= f = join (fmap f ma)

对于列表,join(又名concat)特别简单:

join :: [[a]] -> [a]
join xss = [x | xs <- xss, x <- xs]  -- xss::[[a]], xs::[a]
-- join [[1],[2]] ≡ [1,2]

对于您觉得令人困惑的示例,您应该有

[1,2,3,4] >>= \x->[(x+1)]
  ≡   join $ fmap (\x->[(x+1)]) [1,2,3,4]
  ≡   join [[1+1], [2+1], [3+1], [4+1]]
  ≡   join [[2],[3],[4],[5]]
  ≡   [2,3,4,5]

【讨论】:

  • 但是就我定义的ApplicativeMonadd 实例而言,它是如何工作的?或者更确切地说,joinpurea app(&gt;&gt;|) 方面是如何实现的?
  • 好吧,通过加入 elementwise-apply 结果。我认为您没有意识到列表理解可以做到这一点(请参阅编辑,我展示了 join 如何通过理解来实现)。
【解决方案2】:

WadlerSchool of HaskellLYAHHaskellWikiQuora 以及更多描述列表 monad。

比较:

常规的(&gt;&gt;=) 绑定运算符将参数翻转,否则只是一个中缀concatMap

或者很简单,我的困惑似乎源于不了解该语句的实际工作原理:

(>>|) xs f = [ y | x <- xs, y <- f x ]

由于列表推导等同于列表的 Monad 实例,所以这个定义有点作弊。你基本上是在说某物是 Monad 的方式,它是 Monad,所以你有两个问题:理解列表推导,仍然理解 Monad。

可以对列表推导进行脱糖以更好地理解:

在您的情况下,该语句可以用多种其他方式编写:

  • 使用do-notation:

    (>>|) xs f = do x <- xs
                    y <- f x
                    return y
    
  • 改用(&gt;&gt;=) 运算符:

    (>>|) xs f = xs >>= \x ->
                 f x >>= \y ->
                 return y
    
  • 这可以缩短(每行重写一次):

      (>>|) xs f = xs >>= \x -> f x >>= \y -> return y -- eta-reduction
    ≡ (>>|) xs f = xs >>= \x -> f x >>= return         -- monad identity
    ≡ (>>|) xs f = xs >>= \x -> f x                    -- eta-reduction
    ≡ (>>|) xs f = xs >>= f                            -- prefix operator
    ≡ (>>|) xs f = (>>=) xs f                          -- point-free
    ≡ (>>|) = (>>=)
    

所以从使用列表推导开始,你并没有真正声明一个新的定义,你只是依赖于现有的定义。如果你愿意,你可以定义你的 instance Monadd [] 而不依赖于现有的 Monad 实例或列表推导:

  • 使用concatMap

    instance Monadd [] where
      (>>|) xs f = concatMap f xs
    
  • 再详细说明一下:

    instance Monadd [] where
      (>>|) xs f = concat (map f xs)
    
  • 详细说明:

    instance Monadd [] where
      (>>|) [] f = []
      (>>|) (x:xs) f = let ys = f x in ys ++ ((>>|) xs f)
    

Monadd 类型类应该具有类似于return 的内容。我不确定它为什么不见了。

【讨论】:

【解决方案3】:

List comprehensions 就像嵌套循环:

   xs >>| foo = [ y | x <- xs, y <- foo x]

--            =   for x in xs:
--                         for y in (foo x):
--                               yield y

所以我们有

[1,2,3,4] >>| (\x -> [x, x+10])
=
[ y | x <- [1,2,3,4], y <- (\x -> [x, x+10]) x]
=
[ y | x <- [1] ++ [2,3,4], y <- [x, x+10]]
=
[ y | x <- [1], y <- [x, x+10]] ++ [ y | x <- [2,3,4], y <- [x, x+10]]  -- (*)
=
[ y |           y <- [1, 1+10]]   ++ [ y | x <- [2,3,4], y <- [x, x+10]]
=
[ y | y <- [1]] ++ [ y | y <- [11]] ++ [ y | x <- [2,3,4], y <- [x, x+10]]
=
[1] ++ [11] ++ [ y | x <- [2,3,4], y <- [x, x+10]]
=
[1, 11] ++ [2, 12] ++ [ y | x <- [3,4], y <- [x, x+10]]
=
[1, 11] ++ [2, 12] ++ [3, 13] ++ [ y | x <- [4], y <- [x, x+10]]
=
[1, 11] ++ [2, 12] ++ [3, 13] ++ [4, 14]

关键步骤标记为(*)。您可以将其作为列表推导的定义

一个特殊情况是foo 函数返回一个单例列表,就像你的问题一样。那么确实相当于mapping,因为输入列表中的每个元素都变成了输出列表中的一个(转换后的)元素。

但列表推导更强大。一个输入元素也可以有条件地变成无元素(作为过滤器),或多个元素:

  [ a,          [a1, a2] ++        concat [ [a1, a2],         [  a1, a2,
    b,    ==>   [b1]     ++    ==           [b1],        ==      b1,
    c,          []       ++                 [],
    d ]         [d1, d2]                    [d1, d2] ]           d1, d2  ]

以上等价于

    concat (map foo [a,b,c,d]) 
    =  
    foo a ++ foo b ++ foo c ++ foo d

对于一些适当的foo

concat 是列表 monad 的 joinmap 是列表 monad 的 fmap。一般来说,对于任何单子,

    m >>= foo  =  join (fmap foo m)

Monad 的本质是:从“结构”中的每个实体,有条件地在同一种结构中产生新元素,并将它们就地拼接:

[     a     ,  b   ,  c  ,    d      ]
    /   \      |      |     /   \
[  [a1, a2] , [b1] ,  [] , [d1, d2]  ]  -- fmap foo    = [foo x | x <- xs]
                                        --             =     [y | x <- xs, y <- [foo x]]
[   a1, a2  ,  b1  ,        d1, d2   ]  -- join (fmap foo) = [y | x <- xs, y <-  foo x ]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-03-15
    • 2012-06-30
    • 2012-10-31
    • 2011-03-20
    • 2020-03-12
    • 2015-04-22
    相关资源
    最近更新 更多