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)
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:
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:
_
indicates that it throws the final value of the counters away instead of returning itYou 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)
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'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With