6

I am trying to understand the expression below. It converts the list of characters ['a','b','c'] to a list of strings ["a", "b", "c"]

liftM (:[]) "abc"

How does this happen?

akonsu
  • 28,824
  • 33
  • 119
  • 194

4 Answers4

14

The robotic monkey head operator (:[]) is just the section of the list cons (:) and the empty list [], i.e. (:[]) is equivalent to (\x -> x:[]); which in turn can also be written using list syntax as (\x -> [x]).

Rewritten this way, we have

liftM (\x -> [x]) "abc"

The string literal "abc" is also just syntax sugar for the character list ['a', 'b', 'c'], so we can in turn rewrite the above as

liftM (\x -> [x]) ['a', 'b', 'c']

liftM is just fmap from the Dark Days when Functor wasn't a superclass of Monad, giving

fmap (\x -> [x]) ['a', 'b', 'c']

The Functor instance of [] sets fmap = map, giving

map (\x -> [x]) ['a', 'b', 'c']

which reduces to

[['a'], ['b'], ['c']]

Or, going back to string notation

["a", "b", "c"]

Q.e.d.

Community
  • 1
  • 1
Cactus
  • 27,075
  • 9
  • 69
  • 149
  • 4
    +1 for the "robotic monkey operator" I've never heard the term and from now on my haskell programming experience will be even more awesome! – epsilonhalbe Aug 02 '15 at 09:10
12

Function liftM turns a function which takes input and produces output to a function which takes input in some monad and produces output in the same monad. Lets look at its definition:

liftM :: Monad m => (a -> b) -> m a -> m b
liftM f mx = mx >>= \x -> return (f x)

Strings in Haskell are lists of characters (type String = [Char]), so

"abc" = ['a', 'b', 'c'] :: [Char]

From your application compiler infers a = Char, b = [Char], m a = [Char], m = []. So m b = [[Char]] = [String]. List is a monad where return x = [x] and (>>=) = concatMap. So if we specialize above definition we get:

liftM f mx = concatMap (\x -> [f x]) mx

And if we apply the arguments we get:

concatMap (\x -> [[x]]) ['a', 'b', 'c'] =
concat $ map (\x -> [[x]]) ['a', 'b', 'c'] =
concat $ [[['a']], [['b']], [['c']]] =
[['a'], ['b'], ['c']] =
["a", "b", "c"]
aemxdp
  • 1,348
  • 1
  • 14
  • 34
8

liftM is equivalent to fmap, only specialised to monads. (:[]) uses (:) to make a function that produces lists of one element. Just like (+2) is a compact way of writing (\x -> x + 2), (:[]) is equivalent to (\x -> x : []), or (\x -> [x]).

Your expression, then, might have been written as:

fmap (\x -> [x]) "abc"

The existence of liftM reflects the fact that any legitimate Monad can be made into a Functor by doing fmap f m = m >>= \x -> return (f x). You can always replace liftM by fmap, so the only reasons to use it are:

  • to define fmap for free if you already have a Monad instance (and don't want to use the DeriveFunctor GHC extension), and

  • an entirely optional style choice (if you are writing obviously monadic code and feel that liftM looks better than fmap).

duplode
  • 33,731
  • 7
  • 79
  • 150
  • whow, thanks! where do I learn more about all this? so that I can also easily use terms such as "existence of liftM reflects the fact that any legitimate Monad can be made into a Functor"? : ) – akonsu Aug 01 '15 at 15:52
  • @akonsu By "legitimate", I mean one which follows the monad laws. A brief introduction to them is [the second half of the "Understanding monads" Wikibook chapter](https://en.wikibooks.org/wiki/Haskell/Understanding_monads#Monad_Laws). One thing it doesn't mention is that, for any type constructor, all implementations of `fmap` that follow the [first *functor* law](https://en.wikibooks.org/wiki/Haskell/The_Functor_class#The_functor_laws), `fmap id = id`, are necessarily (and automatically) equivalent. – duplode Aug 01 '15 at 16:07
6

liftM is defined as:

liftM f m = m >>= \x -> return (f x)

We're using liftM with a list (of characters), so we need to look at the list instance of Monad to see how >>= and return are defined:

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

Thus

liftM f xs = xs >>= \x -> return (f x)
           = concat (map (\x -> [f x]) xs)

The concat on the outside and the [ ] on the inside cancel each other out, so

liftM f xs = map (\x -> f x) xs
           = map f xs

In other words, liftM in the list monad is simply map.

map (:[]) ['a', 'b', 'c'] = [(: []) 'a', (: []) 'b', (: []) 'c']
                          = ['a' : [], 'b' : [], 'c' : []]
                          = [['a'], ['b'], ['c']]
                          = ["a","b","c"]

because a string is really just a list of characters.

melpomene
  • 84,125
  • 8
  • 85
  • 148