Counters are initialized every time?

前端 未结 4 930
独厮守ぢ
独厮守ぢ 2021-01-18 02:17

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

相关标签:
4条回答
  • 2021-01-18 02:32

    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)
    
    0 讨论(0)
  • 2021-01-18 02:37

    Variables are immutable in Haskell. When you call inc f it returns a value of 0 + 1 which you promptly ignore. The value of f is 0 and will remain so for all time.

    0 讨论(0)
  • 2021-01-18 02:48

    Just like in code outside of IO, you can string together a series of computations using a fold. foldM operates within a monad, e.g.

    main = do
        conn <- connect "192.168.35.62" 8081
        let tryOnePing (c, f) i = do
            p <- ping conn "ping"
            return $ if p == "pong" then (c+1, f) else (c, f+1)
        (c, f) <- foldM tryOnePing (0, 0) [0 .. 10000]
        print (c, f)
    
    0 讨论(0)
  • 2021-01-18 02:55

    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'
    
    0 讨论(0)
提交回复
热议问题