Question

I have written the following code in Haskell:

import Data.IORef
import Control.Monad
import Control.Monad.Trans.Cont
import Control.Monad.IO.Class
fac n = do
          i<-newIORef 1
          f<-newIORef 1
          replicateM_ n $ do
            ri<-readIORef i
            modifyIORef f (\x->x*ri)
            modifyIORef i (+1)
          readIORef f

This is very nice code which implements factorial as an imperative function. But replicateM_ cannot fully simulate the use of a real for loop. So I tried to create something using continuations but I have failed here is my code:

ff = (`runContT` id) $ do
       callCC $ \exit1 -> do
         liftIO $ do
           i<-newIORef 1
           f<-newIORef 1
         callCC $ \exit2 -> do
           liftIO $ do 
             ri<-readIORef i
             modifyIORef (\x->x*ri)
             modifyIORef i (+1)
             rri<-readIORef i
             when (rri<=n) $ exit2(())
         liftIO $ do
           rf<-readIORef f
           return rf

Can you help me correct my code? Thanks

Was it helpful?

Solution

Since your a beginner to Haskell and not doing this simply to learn how continuations and IORefs work, you're doing it wrong.

The Haskell-y way to write an imperative loop is tail-calls or folds.

factorial n = foldl1' (*) [1..n]

factorial' n = go 1 n
   where go accum 0 = accum
         go accum n = go (n-1) (accum * n)

Also since Haskell's callCC in essence provides you an early return, using it to simulate loops is not going to work.

 callCC (\c -> ???)

Think about what we would have to put in for ??? in order to loop. somehow, we want to run callCC again if it returns a certain value, otherwise just keep going on our merry way.

But nothing we put in ??? can make the callCC run again! It's going to return a value no matter what we do. So instead we'll need to do something around that callCC

 let (continue, val) = callCC (someFunc val)
 in if continue
    then callCallCCAgain val
    else val

Something like this right? But wait, callCallCCAgain is recursion! It's even tail recursion! In fact, that callCC is doing no one any good

 loop val = let (continue, val') = doBody val
            in if continue
               then loop val'
               else val'

Look familiar? This is the same structure as factorial' above.

You can still use IORefs and something like the monad-loops package, but it's going to be an uphill battle always because Haskell isn't meant to be written like that.

Summary

When you want to directly do "loops" in haskell, use tail recursion. But really, try to use combinators like fold and map, they're like little specialized loops and GHC is fantastic at optimizing them. And definitely don't use IORefs, trying to program Haskell like it's C is just going to hurt your performance, readability, and everyone will be sad.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top