Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Counters are initialized every time?

Tags:

haskell

I try to make a simple counter. My counters do not go up however. It seems to me as if they are re-initialized every time by the function "inc" or maybe the (n+1) does not work. How do I best fix this?

inc :: Int -> IO Int
inc n = return (n+1)

main :: IO ()
main = do
  let c = 0
  let f = 0
  putStrLn "Starting..."
  conn <- connect "192.168.35.62" 8081
  time $
    forM_ [0..10000] $ \i -> do
      p <- ping conn "ping"
      if p=="pong" then inc c
         else inc f
  printf "Roundtrips %d\n" (c::Int)
like image 735
J Fritsch Avatar asked Nov 21 '11 22:11

J Fritsch


2 Answers

Though mutable variables can be used in Haskell as shown by other commenters, it is not a good style: mutation should not be used in most cases.

The inc function accepts its argument by value, that is, it doesn't modify its argument. Also, the variables declared by let keep their initial values, so you cannot change them.

How do you write if no variable can ever be changed? The answer is:

  1. instead of modifying something in-place, return a new value
  2. for loops, use recursion

Fortunately, you rarely need to write recursion yourself, as most of recursive patterns are already in the standard library.

In your case you need to perform several IO actions and return the final value of the two counters. Let's start from one action:

let tryOnePing (c, f) i = do
    p <- ping conn "ping"
    return $ if p == "pong" then (c+1, f) else (c, f+1)

Here we declare a local function with 2 parameters: the current values of the counters, packed in a tuple (Int, Int) (a structure in other languages) and current iteration Int. The function performs IO actions and returns modified values of the counters IO (Int, Int). This all is indicated in its type:

tryOnePing :: (Int, Int) -> Int -> IO (Int, Int)

ping returns a value of IO String type. To compare it, you need a String without IO. To do that, you should use >>= function:

let tryOnePing (c, f) i = ping conn "ping" >>= \p -> {process the string somehow}

As this pattern is common, it can be written like this

let tryOnePing (c, f) i = do
    p <- ping conn "ping"
    {process the string somehow}

But the meaning is exactly the same (compiler translates do notation into applications of >>=).

The processing shows a few more common patterns:

if p == "pong" then (c+1, f) else (c, f+1)

Here if is not an imperative if but more like a ternary condition ? value1 : value2 operator in other languages. Also note, that our tryOnePing function accepts (c, f) and returns either (c+1, f) or (c, f+1). We used tuples as we need to work only with 2 counters. In case of big number of counters, we would need to declare a structure type and use named fields.

The value of the whole If construct is a tuple (Int, Int). ping is an IO action, so tryOnePing must be an IO action too. The return function is not an imperative return but a way to convert (Int, Int) to IO (Int, Int).

So, as we have tryOnePing, we need to write a loop to run it 1000 times. Your forM_ was not a good choice:

  1. It doesn't pass our two counters between iterations
  2. _ indicates that it throws the final value of the counters away instead of returning it

You need here not forM_ but foldM

foldM tryOnePing (0, 0) [0 .. 10000]

foldM performs an IO action parametrized by each element of the list and passes some state between iterations, in our case the two counters. It accepts the initial state, and returns the final state. Of course, as its performs IO actions, it returns IO (Int, Int), so we need to use >>= to extract it again for displaying:

foldM tryOnePing (0, 0) [0 .. 10000] >>= \(c, f) -> print (c, f)

In Haskell, you can perform so called 'eta reductions', that is you can remove same identifiers from both sides of a function declaration. E.g. \foo -> bar foo is the same as just bar. So in this case with >>= you can write:

foldM tryOnePing (0, 0) [0 .. 10000] >>= print

which is much shorter than do notation:

 do
   (c, f) <- foldM tryOnePing (0, 0) [0 .. 10000]
   print (c, f)

Also note that you don't need to have two counters: if you have 3000 successes then you have 7000 failures. So the code becomes:

main = do
    conn <- connect "192.168.35.62" 8081
    let tryOnePing c i = do
        p <- ping conn "ping"
        return $ if p == "pong" then c+1 else c
    c <- foldM tryOnePing 0 [0 .. 10000]
    print (c, 10000 - c)

Finally, in Haskell its good to separate IO actions from non-IO code. So it's better to collect all results from pings into a list and then to count successful pings in it:

main = do
    conn <- connect "192.168.35.62" 8081
    let tryOnePing i = ping conn "ping"
    pings <- mapM tryOnePing [0 .. 10000]
    let c = length $ filter (\ping -> ping == "pong") pings
    print (c, 10000 - c)

Note that we avoided incrementing altogether.

It can be written even shorter, but requires more skill to read and write. Don't worry, you will learn these tricks soon:

main = do
    conn <- connect "192.168.35.62" 8081
    c <- fmap (length . filter (== "pong")) $ mapM (const $ ping conn "ping") [0 .. 10000]
    print (c, 10000 - c)
like image 130
nponeccop Avatar answered Oct 30 '22 23:10

nponeccop


In Haskell data are immutable by default. This means that the c in inc c is always zero.

To get mutable variables in Haskell, you have to ask for them explicitly, i.e. by using IORefs. Using them you could write something like:

import Data.IORef

inc :: IORef Int -> IO ()
inc ref = modifyIORef ref (+1)

main :: IO ()
main = do
  c <- newIORef 0
  f <- newIORef 0
  putStrLn "Starting..."
  conn <- connect "192.168.35.62" 8081
  time $
    forM_ [0..10000] $ \i -> do
      p <- ping conn "ping"
      if p=="pong" 
         then inc c
         else inc f
  c' <- readIORef c
  printf "Roundtrips %d\n" c'
like image 24
bzn Avatar answered Oct 31 '22 01:10

bzn