The imperative loop in Haskell

I am trying to understand the monad system in Haskell. About 80% of my previous programming experience is in C, but, ironically, the urgent part of Haskell is the hardest to understand. Enumerating the manipulations and lazy assessment was much clearer. Anyway, I want ghc to accept this code. I know the code does not make sense. Most obviously, I pass Bool where IO Bool is expected. But this is not the only problem. I know this is a stupid question, but please help me understand my understanding of the Haskell language.

 import Control.Monad while :: Monad m => m Bool -> m () -> m () while cond action = do c <- cond when c $ do action while cond action main :: IO () main = do i <- 0 while (i < 10) $ do i <- i + 1 print i 

This is how I finally did it. I know that allocaArray not needed, but it was a lot of fun to use. Haskell really has no boundaries, very powerful.

 import Control.Monad import Data.IORef import Foreign.Ptr import Foreign.Storable import Foreign.Marshal.Array while :: Monad m => m Bool -> m () -> m () while cond action = do c <- cond if c then do action while cond action else return () main :: IO () main = do let n = 10 allocaArray n $ \p -> do i <- newIORef 0 while (liftM (< n) (readIORef i)) $ do i2 <- readIORef i poke (advancePtr p i2) i2 modifyIORef i (+ 1) writeIORef i 0 while (liftM (< n) (readIORef i)) $ do i2 <- readIORef i (peek $ advancePtr p i2) >>= print modifyIORef i (+ 1) 
+5
source share
3 answers

There are two things that keep your code from type checking:

  • The while function expects an IO Bool , but you give it i < 10 , which is an expression of type Bool . To turn a Bool into an IO Bool , simply use return .

  • When you write i <- 0 , you are trying to use literal zero as a monadic value, but it is not. remember, that

     main = do i <- 0 ... 

    equivalently

     main = 0 >>= \i -> do ... 

To fix this, you can also push 0 through return .

Therefore you get

 main :: IO () main = do i <- return 0 while (return (i < 10)) $ do i <- return (i + 1) print i 

However, this still will not do what you intend to do: the reason is that the first (leftmost) i in i <- return (i + 1) is different from i in i <- return 0 . You shade the variable by creating a new variable with the same name, which you then print. Thus, you do not encounter any counter at all.

I don’t want to spoil the fun, but if you are really stuck: there is a monad-loops package that provides a couple of useful functions of the monoidal loop, including the whileM function.

+6
source

The problem with this approach is that i not a mutable variable. You can use IORef , however a more functional approach would be to pass the current state through each iteration. You can rewrite the body and whileM conditions to accept the current value:

 whileM :: Monad m => (a -> Bool) -> (a -> ma) -> a -> m () whileM test act init = when (test init) $ (act init) >>= whileM test act 

then you can do

 whileM (< 10) (\i -> print i >> return (i + 1)) 0 
+9
source

Local state solution (state and associated monad transformer), as opposed to global state ( IORef and friends):

 import Control.Monad import Control.Monad.State while :: Monad m => m Bool -> m () -> m () while cond action = do c <- cond when c $ do action while cond action main :: IO () main = do runStateT (while cond body) 1 return () body :: StateT Integer IO () body = do x <- get liftIO $ print x put (x + 1) return () cond :: StateT Integer IO Bool cond = do x <- get return (x < 10) 

The body of the cycle and the condition of the cycle are explicit and are called for clarity; can write, for example. while (liftM (< 10) get) body .

+3
source

All Articles