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
incfunction accepts its argument by value, that is, it doesn’t modify its argument. Also, the variables declared byletkeep 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:
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 iterationInt. The function performs IO actions and returns modified values of the countersIO (Int, Int). This all is indicated in its type:pingreturns a value ofIO Stringtype. To compare it, you need aStringwithoutIO. To do that, you should use>>=function:As this pattern is common, it can be written like this
But the meaning is exactly the same (compiler translates
donotation into applications of>>=).The processing shows a few more common patterns:
Here
ifis not an imperativeifbut more like a ternarycondition ? value1 : value2operator in other languages. Also note, that ourtryOnePingfunction 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).
pingis an IO action, sotryOnePingmust be an IO action too. Thereturnfunction is not an imperative return but a way to convert(Int, Int)toIO (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_butfoldMfoldMperforms 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: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 foois the same as justbar. So in this case with>>=you can write:which is much shorter than
donotation: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:
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:
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: