Haskell Monad - How does Monad on list work?

2019-07-21 16:39发布

In order to understand Monad, I came up with the following definitions:

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]

It works as expected:

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

I am not sure how it is working though. For example:

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

How does application (\x->([x+1]) to each list element of [1,2,3] result in [2,3,4] and not [[2],[3],[4]]

Or quite simply my confusion seems to stem from not understanding how this statement [ y | x <-xs, y <- f x] actually works

3条回答
▲ chillily
2楼-- · 2019-07-21 16:46

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 ]
查看更多
可以哭但决不认输i
3楼-- · 2019-07-21 17:00

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]
查看更多
Lonely孤独者°
4楼-- · 2019-07-21 17:04

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.

查看更多
登录 后发表回答