gpt4 book ai didi

haskell - 每次都初始化计数器?

转载 作者:行者123 更新时间:2023-12-03 15:05:51 26 4
gpt4 key购买 nike

我试着做一个简单的计数器。然而,我的计数器并没有上升。在我看来,好像它们每次都被函数“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)

最佳答案

尽管如其他评论者所示,可以在 Haskell 中使用可变变量,但这不是一种好的风格:在大多数情况下不应该使用突变。
inc函数按值接受它的参数,也就是说,它不修改它的参数。此外,let 声明的变量保持它们的初始值,所以你不能改变它们。

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

  • 而不是就地修改某些东西,而是返回一个新值
  • 对于循环,使用递归

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

    在您的情况下,您需要执行几个 IO 操作并返回两个计数器的最终值。让我们从一个 Action 开始:
    let tryOnePing (c, f) i = do
    p <- ping conn "ping"
    return $ if p == "pong" then (c+1, f) else (c, f+1)

    这里我们声明了一个带有 2 个参数的局部函数:计数器的当前值,打包在一个元组中 (Int, Int) (其他语言的结构)和当前迭代 Int .该函数执行 IO 操作并返回计数器的修改值 IO (Int, Int) .这一切都在其类型中表示:
    tryOnePing :: (Int, Int) -> Int -> IO (Int, Int)
    ping返回值 IO String类型。要比较它,您需要一个 String没有 IO .为此,您应该使用 >>=功能:
    let tryOnePing (c, f) i = ping conn "ping" >>= \p -> {process the string somehow}

    由于这种模式很常见,所以可以这样写
    let tryOnePing (c, f) i = do
    p <- ping conn "ping"
    {process the string somehow}

    但含义完全相同(编译器将 do 符号转换为 >>= 的应用程序)。

    该处理显示了一些更常见的模式:
    if p == "pong" then (c+1, f) else (c, f+1)

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

    整个 If 构造的值是一个元组 (Int, Int)。 ping是一个 IO Action ,所以 tryOnePing也必须是 IO Action 。 return function 不是命令式返回,而是一种转换 (Int, Int) 的方法至 IO (Int, Int) .

    因此,当我们有 tryOnePing 时,我们需要编写一个循环来运行它 1000 次。您的 forM_不是一个好的选择:
  • 它在迭代之间没有通过我们的两个计数器
  • _表示它将计数器的最终值扔掉而不是返回

  • 这里不需要 forM_但是 foldM
    foldM tryOnePing (0, 0) [0 .. 10000]
    foldM执行由列表的每个元素参数化的 IO 操作,并在迭代之间传递一些状态,在我们的例子中是两个计数器。它接受初始状态,并返回最终状态。当然,由于它执行 IO Action ,它返回 IO (Int, Int),所以我们需要使用 >>=再次提取它以显示:
    foldM tryOnePing (0, 0) [0 .. 10000] >>= \(c, f) -> print (c, f)

    在 Haskell 中,您可以执行所谓的“eta 缩减”,即您可以从函数声明的两侧删除相同的标识符。例如。 \foo -> bar foobar 相同.所以在这种情况下, >>=你可以写:
    foldM tryOnePing (0, 0) [0 .. 10000] >>= print

    do 短得多符号:
     do
    (c, f) <- foldM tryOnePing (0, 0) [0 .. 10000]
    print (c, f)

    另请注意,您不需要有两个计数器:如果您有 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)

    最后,在 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)

    请注意,我们完全避免了递增。

    它可以写得更短,但需要更多的阅读和写作技巧。别担心,你很快就会学会这些技巧:
    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)

    关于haskell - 每次都初始化计数器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8219222/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com