Haskell Monad - How does Monad on list work?

为君一笑 提交于 2019-11-28 14:13:10

Wadler, School of Haskell, LYAH, HaskellWiki, Quora and many more describe the list monad.

Compare:

The regular (>>=) bind operator has the arguments flipped, but is otherwise just an infix concatMap.

Or quite simply my confusion seems to stem from not understanding how this statement actually works:

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

Since list comprehensions are equivalent to the Monad instance for lists, this definition is kind of cheating. You're basically saying that something is a Monadd in the way that it's a Monad, so you're left with two problems: Understanding list comprehensions, and still understanding Monad.

List comprehensions can be de-sugared for a better understanding:

In your case, the statement could be written in a number of other ways:

  • Using do-notation:

    (>>|) xs f = do x <- xs
                    y <- f x
                    return y
    
  • De-sugared into using the (>>=) operator:

    (>>|) xs f = xs >>= \x ->
                 f x >>= \y ->
                 return y
    
  • This can be shortened (one rewrite per line):

      (>>|) 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
    ≡ (>>|) = (>>=)
    

So from using list comprehensions, you haven't really declared a new definition, you're just relying on the existing one. If you wanted, you could instead define your instance Monadd [] without relying on existing Monad instances or list comprehensions:

  • Using concatMap:

    instance Monadd [] where
      (>>|) xs f = concatMap f xs
    
  • Spelling that out a little more:

    instance Monadd [] where
      (>>|) xs f = concat (map f xs)
    
  • Spelling that out even more:

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

The Monadd type class should have something similar to return. I'm not sure why it's missing.

Monads are often easier understood with the “mathematical definition”, than with the methods of the Haskell standard class. Namely,

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

Note that you can implement the standard version in terms of this, vice versa:

join mma = mma >>= id

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

For lists, join (aka concat) is particularly simple:

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

For the example you find confusing, you'd have

[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]

List comprehensions are just like nested loops:

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

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

Thus we have

[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]

The crucial step is marked (*). You can take it as the definition of what list comprehensions are.

A special case is when the foo function returns a singleton list, like in your question. Then it is indeed tantamount to mapping, as each element in the input list is turned into one (transformed) element in the output list.

But list comprehensions are more powerful. An input element can also be turned conditionally into no elements (working as a filter), or several elements:

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

The above is equivalent to

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

for some appropriate foo.

concat is list monad's join, and map is list monad's fmap. In general, for any monad,

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

The essence of Monad is: from each entity "in" a "structure", conditionally producing new elements in the same kind of structure, and splicing them in-place:

[     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 ]
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!