Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursion in a monad

Tags:

haskell

I am at a loss to understand recursion in a monad. From the haskell.org wiki here is an example:

main = f 3

f 0 = return []
f n = do v  <- getLine
         vs <- f (n-1)
         return $! v : vs

This program gets three lines from standard input recursively. What I cannot comprehend is what happens when you get to f 0 and how the recursion unwinds. How does the final value of the do block get constructed? Why is the final return line called repeatedly in the recursion? I know return is not function return as in imperative languages, but I cannot see how this line gets repeated.

I am aware that this is a raw beginners question, but I am stumped.

like image 310
andro Avatar asked Nov 21 '14 12:11

andro


2 Answers

In this particular case, you can just unfold completely. Perhaps this helps:

  f 3
=   { reduce f (and the subtraction) }
  do v  <- getLine
     vs <- f 2
     return $! v : vs
=   { reduce f (and the subtraction) }
  do v  <- getLine
     vs <- do v'  <- getLine
              vs' <- f 1
              return $! v' : vs'
     return $! v : vs
=   { reduce f (and the subtraction) }
  do v  <- getLine
     vs <- do v'  <- getLine
              vs' <- do v''  <- getLine
                        vs'' <- f 0
                        return $! v'' : vs''
              return $! v' : vs'
     return $! v : vs
=   { reduce f }
  do v  <- getLine
     vs <- do v'  <- getLine
              vs' <- do v''  <- getLine
                        vs'' <- return []
                        return $! v'' : vs''
              return $! v' : vs'
     return $! v : vs
=
  ...

At this point, there's no recursion left. All we've done is apply function definitions. From here, we can simplify further if we assume that the monad laws hold:

  ...
=   { vs'' <- return [] means that vs'' is [] }
  do v  <- getLine
     vs <- do v'  <- getLine
              vs' <- do v''  <- getLine
                        return $! v'' : []
              return $! v' : vs'
     return $! v : vs
=   { inline the innermost do block }
  do v  <- getLine
     vs <- do v'  <- getLine
              v'' <- getLine
              vs' <- return $! v'' : []
              return $! v' : vs'
     return $! v : vs
=   { inline return $! v'' : [] }
  do v  <- getLine
     vs <- do v'  <- getLine
              v'' <- getLine
              return $! v' : v'' : []
     return $! v : vs
=   { inline the innermost do block }
  do v   <- getLine
     v'  <- getLine
     v'' <- getLine
     vs  <- return $! v' : v'' : []
     return $! v : vs
=   { inline return $! v' : v'' : [] }
  do v   <- getLine
     v'  <- getLine
     v'' <- getLine
     return $! v : v' : v'' : []
like image 167
kosmikus Avatar answered Nov 16 '22 05:11

kosmikus


You can "pseudo-compile"/"unwind" the monadic block to see how it works:

f 3 = do v <- getLine
         vs <- f 2 -- (will return a list with 2 lines from stdin
         return $! v : vs
f 2 = do v <- getLine
         vs <- f 1 -- (will return singleton list with a line from stdin)
         return $! v : vs 
f 1 = do v <- getLine
         vs <- f 0 -- (will return an empty list)
         return $! v : vs 
f 0 = return []

So, it will getLine 3 times and then build up a list of the lines, the first line will be the first value in the list.

Every time you see a return in a monadic expression, you are putting a value inside it. Every time you see a <- (bind) in a monadic expression, you are taking values out of it. In the case of the IO monad, you are always putting and taking out a single of the monad. In contrast, the list monad may "take out" (bind) consecutive values.

like image 41
Thiago Negri Avatar answered Nov 16 '22 05:11

Thiago Negri