计数器每次都被初始化?

J F*_*sch 7 haskell

我试着做一个简单的计数器.然而,我的柜台并没有上升.在我看来好像每次都通过函数"inc"重新初始化,或者(n + 1)不起作用.我该如何最好地解决这个问题?

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)
Run Code Online (Sandbox Code Playgroud)

npo*_*cop 21

虽然可变变量可以在Haskell中使用,如其他评论者所示,但它不是一个好的风格:在大多数情况下不应该使用变异.

inc函数按值接受其参数,即不修改其参数.另外,声明的变量let保持其初始值,因此您无法更改它们.

如果没有变量可以改变,你怎么写?答案是:

  1. 而不是就地修改某些内容,返回一个新值
  2. for循环,使用递归

幸运的是,您很少需要自己编写递归,因为大多数递归模式已经在标准库中.

在您的情况下,您需要执行多个IO操作并返回两个计数器的最终值.让我们从一个动作开始:

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

这里我们声明一个带有2个参数的本地函数:计数器的当前值,打包在元组(Int, Int)(其他语言中的结构)和当前迭代中Int.该函数执行IO操作并返回计数器的修改值IO (Int, Int).这一切都在其类型中指出:

tryOnePing :: (Int, Int) -> Int -> IO (Int, Int)
Run Code Online (Sandbox Code Playgroud)

ping返回IO Stringtype 的值.要比较它,你需要一个String没有IO.要做到这一点,你应该使用>>=函数:

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

由于这种模式很常见,因此可以这样写

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

但意思完全相同(编译器将do符号转换为应用程序>>=).

处理显示了一些更常见的模式:

if p == "pong" then (c+1, f) else (c, f+1)
Run Code Online (Sandbox Code Playgroud)

if不是必要的,if但更像是condition ? value1 : value2其他语言的三元运算符.另请注意,我们的tryOnePing函数接受(c,f)并返回(c+1, f)(c, f+1).我们使用元组,因为我们只需要使用2个计数器.如果计数器数量很多,我们需要声明一个结构类型并使用命名字段.

整个If结构的值是一个元组(Int,Int).ping是IO操作,因此也tryOnePing必须是IO操作.该return函数不是命令性的返回,而是转换(Int, Int)为的方式IO (Int, Int).

因此,正如我们有tryOnePing,我们需要编写一个循环来运行它1000次.你forM_不是一个好选择:

  1. 它不会在迭代之间传递我们的两个计数器
  2. _ 表示它会抛出计数器的最终值而不是返回它

你需要在这里没有forM_,但foldM

foldM tryOnePing (0, 0) [0 .. 10000]
Run Code Online (Sandbox Code Playgroud)

foldM执行由列表的每个元素参数化的IO动作,并在迭代之间传递一些状态,在我们的例子中是两个计数器.它接受初始状态,并返回最终状态.当然,当它执行IO动作时,它返回IO(Int,Int),所以我们需要>>=再次提取它以显示:

foldM tryOnePing (0, 0) [0 .. 10000] >>= \(c, f) -> print (c, f)
Run Code Online (Sandbox Code Playgroud)

在Haskell中,您可以执行所谓的"eta reduction",即可以从函数声明的两侧删除相同的标识符.例如\foo -> bar foo和刚刚一样bar.所以在这种情况下>>=你可以写:

foldM tryOnePing (0, 0) [0 .. 10000] >>= print
Run Code Online (Sandbox Code Playgroud)

这比do记谱法短得多:

 do
   (c, f) <- foldM tryOnePing (0, 0) [0 .. 10000]
   print (c, f)
Run Code Online (Sandbox Code Playgroud)

另请注意,您不需要有两个计数器:如果您有3000个成功,那么您有7000个失败.所以代码变成:

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)
Run Code Online (Sandbox Code Playgroud)

最后,在Haskell中,将IO操作与非IO代码分开是很好的.因此,最好将ping中的所有结果收集到列表中,然后计算成功的ping:

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)
Run Code Online (Sandbox Code Playgroud)

请注意,我们完全避免了增量.

它写得更短,但需要更多的阅读和写作技巧.别担心,你很快就会学到这些技巧:

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)
Run Code Online (Sandbox Code Playgroud)


bzn*_*bzn 7

在Haskell中,默认情况下数据是不可变的.这意味着cin inc c总是为零.

要在Haskell中获取可变变量,您必须明确地询问它们,即使用IORefs.使用它们你可以写下这样的东西:

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'
Run Code Online (Sandbox Code Playgroud)

  • 这里的IORef不是惯用的,也不是坏的风格.实现他想要的任务不需要可变变量. (5认同)

eph*_*ent 5

就像在外面的代码中一样IO,您可以使用折叠将一系列计算串联起来. foldM在monad中运行,例如

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)
Run Code Online (Sandbox Code Playgroud)