Incerteza
Incerteza

Reputation: 34884

Implementing a loop in Haskell

I'm curious about implementing a loop in Haskell. How can I do something similar in Haskell (pseudo-code):

var i = 0
for (int i1 = 0; i1 < 10; i1++) {
  println(i1)
  i += 2
}

println(i)

Upvotes: 2

Views: 2763

Answers (5)

shang
shang

Reputation: 24802

In functional terms what you are doing is folding over a list of integers so that for each integer you print the element and increase an accumulator by 2. Since we are printing something (i.e. doing I/O) we need to fold in a monad but otherwise it's just your standard left-fold.

foldM (\i i1 -> print i1 >> return (i + 2)) 0 [0..9] >>= print

We fold with a lambda function that uses the same variable names as your code. I.e. i1 is the current element and i is the accumulator.

The next parameter is the initial value for the accumulator which corresponds to i = 0 in your code.

The final parameter is the (inclusive on both ends) list of numbers we fold over.

The >>= (bind operator) pipes the result of the fold (i.e. the final value of the accumulator i) to the print function.

EDIT: This is assuming that you meant to write

var i = 0
for (int i1 = 0; i1 < 10; i1++) {
  println(i1)
  i += 2
}

println(i)

instead of incrementing just i in both the for-clause and loop body.

Upvotes: 8

kqr
kqr

Reputation: 15028

Going by the two assumptions that

  1. By "similar" you mean similar in behaviour, and
  2. (the same assumption shang made) that you want to print out

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    20
    

I would in Haskell write

do
  let xs = [0..9]
  mapM_ print xs
  print (length xs * 2)

You see how the original computation got split up into three separate (and independent!) computations.

  1. We turn the loop variable i1 into a list. We know the bounds are 0 and 9 inclusive, so the loop variable can be represented by the list [0..9].
  2. We print the contents of the list, which is the same thing as printing the loop variable every iteration.
  3. We calculate "i" from what we know about the list, and then print it as well.

The third computation is especially interesting, because it highlights the difference between traditional imperative programming and Haskell, which is a lot about declarative programming.

Adding two to a number every iteration of a list is the same thing as taking the length of the list and multiplying it by two. The big difference, in my eyes, is that I can read i = length xs * 2 and understand what it means in the blink of an eye. Counting i up every iteration of a loop, however, takes some thinking to understand what it really means.

The fact that all three sub-computations are independent means they are a lot easier to test – you can test them one at a time and if they work individually, they will work together as well!

If you meant "similar" in the sense of "similar-looking code", refer to any of the STRef/IORef answers.

Upvotes: 7

Paul
Paul

Reputation: 8162

An easy yet flexible way to do a loop is to define a recursive function in a let or where clause:

main = loop 0 10
  where
    loop i n | i < n = putStrLn (show i) >> loop (i+2) n
    loop _ _         = return ()

This defines a function loop of two variables i and n. The first pattern has a guard (after the |), which checks the condition (i < n). If it is true this branch is selected and i is printed to the console before loop calls itself again now binding i to i+2. Otherwise the default branch is selected, which just returns () (=do nothing in the IO Monad).

Implementing loops like this with recursive functions is pretty flexible. If it is easy you want to go with higher-order-functions (such as for and map), but if you can't figure out how to translate some loop from an imperative setting, recursive functions can usually do the trick.

Upvotes: 1

leftaroundabout
leftaroundabout

Reputation: 120711

import Data.IORef
import Control.Monad

main = do
   i <- newIORef 0

   let loop = do
        iNow <- readIORef i
        when (iNow < 10) $ do
          print i
          modifyIORef i (+1)
          loop
   loop

But obviously, you should avoid this. mapM_ print [0..9] works much better.


I see you have to i variables in there with different increment. Well, it's obvious how to add that here. You can stay to the manual recursion through loop. A little better is replacing one IORef by a simple forM_. Preferrably, try not to use any IORef at all but only functional structures.

Upvotes: 1

Tikhon Jelvis
Tikhon Jelvis

Reputation: 68152

You would use a higher-order function like forM_. The name looks a little weird, but it's actually very systematic: the function is called for, it works on monads (M) and it does not return a value (_): forM_.

You could use it like this:

import Control.Monad

import Data.IORef

main = do i <- newIORef 0
          forM_ [0..9] $ \ i' -> do
            print i'
            i `modifyIORef` (+ 2)
          readIORef i >>= print

The forM_ function itself can be implemented with recursion. Here's a simple way to do it:

forM_ []     _ = return ()
forM_ (x:xs) f = f x >> forM_ xs f

Of course, using mutable state like this in Haskell is ugly. Not because it has to be ugly, but because it's so rarely used. You could imagine a C-like library that looked much nicer; take a look at this example.

Naturally, the best way to do this in Haskell is to take a more functional approach and forget about mutable variables. You could write something like this instead:

main = do forM_ [0..9] print
          print $ sum [i' * 2 | i' <- [0..9]]

We could also improve the looping code with some very simple function definitions. Just having a nice operator for reading, setting and modifying IOVars goes a long way:

(!) :: (a -> IO b) -> IORef a -> IO b
f ! var = readIORef var >>= f

(+=) :: Num a => IORef a -> a -> IO ()
var += n = var `modifyIORef` (+ n) 

ref :: a -> IO (IORef a)
ref = newIORef

This lets us write the loop as so:

import Data.IORef

main = do i <- ref 0
          forM_ [0..9] $ \ i' -> do
            print i'
            i += 2
          print !i

At this point, it almost looks exactly like OCaml! Quite an improvement for just a few operator definitions.

Upvotes: 3

Related Questions