Search code examples
haskellmonadsdo-notation

Why >> duplicates right-hand side operand in haskell


I'm searched for a solution for duplication of a string n times. I found duplicate n str = [1..n] >> str from this solution. I'm wonder why this method duplicates the str.

I search about >> and I found this points:

k >> f = k >>= \_ -> f

and

a >> b >> c >> d
-- is is equivalent to
do a
   b
   c
   d

Then I try this

ghci> do [1..3]; "a"
"aaa"

But still I didn't get how it's works. Could anyone explain this behavior ?


Solution

  • By Monad Laws,

    a >> b >> c >> d
    -- is equivalent to
    --                                        (values)   (computations)
    do a             do {      a            do {  _x   <-     a
       b                ;      b               ;  _y   <-     b
       c                ;      c               ;  _z   <-     c
       d                ; r <- d               ;   r   <-     d
                        ; return r             ;   return r
                        }                      }
    

    and writing it with Monad Comprehensions -- which for lists, non-coincidentally, are exactly the same as List Comprehensions -- it becomes

        [ r | _x <- a,  _y <- b,  _z <- c,  r <- d ]
    

    which means, in pseudocode,

    for each _x in a:             for each _x in a:
      for each _y in b:             for each _y in b:
        for each _z in c:             for each _z in c:
          for each r in d:              do  d
             do  yield r
    

    so we just "do" the same d, over and over, for each combination of the results of the actions above it in the nested fashion.

    For lists this means splicing the elements of d into the resulting list of results, repeatedly.

    In some sense generalized (nested) loops are what (monads)/applicative/functors are, describing the combined computation which is producing the final results from the deepest level just the same:(*)

      Functor                Applicative                  Monad
        
    for x in a:             for x in a                for x in a:
      do  yield (foo x)     and y in b:                 for y in (bar x):
                              do  yield (foo x y)         do  yield (foo x y)
    
      "loop"                  "loops"                    "nested loops"
                                                       (created on the fly)
    

    (*) No matter how many levels are there, seen as a whole, the "loops" combo produces its results one by one, just as each "loop" produces its enumeration in separation, one by one. It is so trivial, we don't even notice it with the imperative loops.

    Each specific type of Functor gives its meaning to the "for", "in" and "do yield", in the above. For lists it means splicing in the results, achieved with concat/concatMap.