Search code examples
haskellimperative-programming

Factorial using imperative-style programming


I have the following code:

while :: IO Bool -> IO () -> IO ()
while test body =
  do b <- test
     if b
       then do {body ; while test body}  -- same-line syntax for do
       else return ()

I need to implement the factorial function using imperative-style programming. what I have to do is to create and initialize variables using newIORef, modify their values using a while loop with readIORef and writeIORef, then have the IO action return a pair consisting of the input n and the final result.

This is what I have done so far:

fact :: Integer -> IO (Integer, Integer)
fact n = do r <- newIORef n --initialize variable
            while
              (do {v <- readIORef n; n})
              (do {v <- readIORef r; writeIORef (...)) --modify the value (?)
            readIORef r

This is my attempt to write the factorial function. This is obviously does not work. Any help would be appreciated.


Solution

  • I think maybe it's time to give you some working version:

    fact :: Integer -> IO (Integer, Integer)
    fact n = do
      i <- newIORef 1
      acc <- newIORef 1
      while (lessOrEqualN i) (step i acc)
      acc' <- readIORef acc
      return $ (n, acc')
      where
         lessOrEqualN iRef = do
           i' <- readIORef iRef
           return $ i' <= n
         step iRef accRef = do
           i' <- readIORef iRef
           acc' <- readIORef accRef
           writeIORef accRef (acc' * i')
           writeIORef iRef (i'+1)
    

    as you can see I used an loop reference i and an accumulator reference acc always reading, writing the changing values.

    To make this (hopefully) a bit more readable I extracted the test and the body of the while into lessOrEqualN and step.


    Of course there are easier ways to do this (modifyIORef) but I guess you have to use those.


    PS: you play with it a bit - maybe you want to handle negative values differently or whatever


    this might be a bit cleaner (putting both mutables into the same ref):

    fact :: Integer -> IO (Integer, Integer)
    fact n = do
      ref <- newIORef (1,1)
      while (lessOrEqualN ref) (step ref)
      (_,acc) <- readIORef ref
      return $ (n, acc)
      where
         lessOrEqualN ref = do
           (i,_) <- readIORef ref
           return $ i <= n
         step ref = do
           (i,acc) <- readIORef ref
           writeIORef ref (i+1, acc * i)