2

Below is are two examples of mutually recursive function pairs. The first example terminates and produces the expected result. The second example is similar, except it uses the Maybe monad. fun1' does not terminate when called.

fun1 = 1 + fun2
fun2 = let x = fun1 
       in  2
-- terminates. result is 3.

fun1' = do a <- Just 1 
           b <- fun2'
           return $ a + b
fun2' = do x <- fun1'
           return 2
-- does not terminate.

Here are another two examples. Once again, the first example terminates with the expected result and the second example (using the Maybe monad) does not terminate.

fun1'' = fun2'' : [1]
fun2'' = (head . tail $ fun1'') + 1 
-- terminates. result is [2,1]

fun1''' = do a <- Just [1]
             b <- fun2'''
             return $ b : a              
fun2''' = do x <- fun1'''
             return $ (head . tail $ x) + 1
-- does not terminate.

I believe I have a situation that is semantically similar to the last example in my real code. What are my options for getting it to terminate? Will I be forced to abandon the Maybe monad?

Update This is the solution I ended up using;

fun1'''' = do a <- Just [1]
              b <- fun2''''
              return $ b : a
fun2'''' = do return $ (head . tail . fromJust $ fun1'''') + 1
-- terminates :)

The key difference is that fun2'''' no longer operates on fun1'''' using the bind operator. Instead it explicitly uses fromJust (and assumes that fun1'''' is not Nothing).

In my real code fun2 actually calls a number of other functions. These other functions are not mutually recursive with fun2, and can potentially return a Nothing result. Fortunately I can still use the bind operator implicitly within the do notation for accessing the other required values.

knick
  • 941
  • 8
  • 17

4 Answers4

4

The reason that fun1'/fun2' do not terminate is that the Maybe monad's bind operation (>>=) needs to check if the first argument is Nothing or if it is Just (...). So when you do x <- fun1' in fun2', even though you do not use x, you still need to check if fun1' is Nothing or Just (...) (you don't care about the (...), it would be bound to x which you don't use anyway). But to check that, you need to know if fun2' is Just or Nothing, because you're binding b to the result of it in fun1' (b <- fun2') -> infinite recursion.

The same doesn't happen in the first case, because in fun2, you don't use x, so fun1 never needs to be evaluated!

bennofs
  • 11,873
  • 1
  • 38
  • 62
2

The problem here is that your terminating function fun1 is not mutually exclusive even though that appears to be. Infact, your fun2' function actually just refers to the value 2. Example:

λ> let x = undefined in 2
2

The undefined part doesn't get evaluated at all. Hence your x = fun1 will not at all be evaluated in your fun2 function and therefore it terminates successfully since fun2 evaluates to 2.

Whereas in your fun2' example it doesn't get reduced to any value. So, it doesn't terminate. Infact, if you actually turn your fun2' function into let expression just like your fun2 example then it will terminate:

fun2' = let x = fun1'
        in (Just 2)

fun2'' is a mutually recursive function which refers to value 2:

λ> tail fun1''
[1]
λ> head $ tail fun1''
1
λ> (head $ tail fun1'') + 1
2

So fun2'' actually refers to a value 2.

Sibi
  • 47,472
  • 16
  • 95
  • 163
  • Ok, I see what you are saying about fun1/fun2 not actually being mutually recursive. This makes sense, since fun1 does not actually need to use fun2 to produce its result. However, I would have thought that fun1''/fun2'' could be considered mutually recursive, since neither can produce its result without the other. – knick Jul 12 '14 at 14:16
  • @knick Yes, you are correct. Updated it appropriately. – Sibi Jul 12 '14 at 14:29
1

I think you have no choice but to abandon the Maybe monad.

Remember that monads just represent sequences of computations that can be chained together. The Maybe monad represents computations that can fail and when one of the computations in the sequence fails, we get a Nothing in the end. Though it looks like the value of x is not required (and hence we expect a program halt due to lazy valuation) for the end result, it is in a sequence, and hence has to be evaluated resulting in an infinite recursion.

Instead try this:

fun1' = do a <- Just 1 
           b <- fun2'
           return $ a + b
fun2' = do Nothing
           fun1'
           return 2
main = print $ fun1'

This will stop because, the Nothing will bypass all other computations.

Priyatham
  • 2,821
  • 1
  • 19
  • 33
1

If you have a need to combine the Maybe monad with mutual recursion, then perhaps you want RecursiveDo notation, which allows monadically calculated values inside an mdo block to refer recursively to each other (for monads supporting the MonadFix class). The following compiles and gives the result Just ([2,1],2) that you probably would expect.

{-# LANGUAGE RecursiveDo #-}

maybeFuns = mdo
    fun1''' <- do a <- Just [1]
                  return $ fun2''' : a              
    fun2''' <- return $ (head . tail $ fun1''') + 1
    return (fun1''', fun2''')

You need to define it in one mdo block rather as separate functions, though. Although you could replace maybeFuns = by Just (fun1''', fun2''') = if you don't care about getting an error if some part evaluates to Nothing.

Ørjan Johansen
  • 18,119
  • 3
  • 43
  • 53