计数器每次都初始化?
我试图做一个简单的计数器。然而,我的柜台不会上涨。在我看来,似乎每次都通过函数“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
声明的变量保持其初始值,所以你不能改变它们。
如果没有变量可以改变,你该怎么写?答案是:
- 而不是修改就地东西,返回一个新值
- for循环,使用递归
幸运的是,你很少需要编写递归自己,因为大多数的递归模式已经在标准库中。
在你的情况下,你需要执行多个IO操作并返回两个计数器的最终值。让我们从一个动作开始:
let tryOnePing (c, f) i = do
p <- ping conn "ping"
return $ if p == "pong" then (c+1, f) else (c, f+1)
这里我们声明了一个本地函数有两个参数:计数器的当前值,装在一个元组(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操作,所以tryOnePing
也必须是IO操作。 return
函数不是强制性返回,而是将(Int, Int)
转换为IO (Int, Int)
的一种方法。
因此,正如我们tryOnePing,我们需要编写一个循环来运行它1000次。您forM_
是不是一个好的选择:
- 它不会通过我们的迭代之间两个计数器
-
_
表明它抛出计数器的最终值远没有返回它
您的在这里需要不forM_
但foldM
foldM tryOnePing (0, 0) [0 .. 10000]
foldM
进行参数化的一个IO动作通过列表中的每个元素并在迭代之间传递一些状态,在我们的例子中是两个计数器。它接受初始状态,并返回最终状态。当然,作为其进行IO操作,返回IO(INT,INT),所以我们需要显示再次使用>>=
将其解压:
foldM tryOnePing (0, 0) [0 .. 10000] >>= \(c, f) -> print (c, f)
在Haskell中,您可以执行所谓的“埃塔减少” ,那就是你可以从函数声明的两边删除相同的标识符。例如。 \foo -> bar foo
与bar
相同。因此,在这种情况下,与>>=
你可以写:
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结果转换成一个列表,然后在它算成功坪:我们完全避免递增
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数据是不可变的。这意味着inc c
中的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'
谢谢!这样可行。我会阅读这个。代码的另一个问题是它报告了例如2秒,但运行15秒,直到它返回到控制台:-(。但感谢您的解决方案,这是很大的帮助。 –
IORefs这里不是惯用的和一个不好的风格可变的变量不需要执行他想要的任务 – nponeccop
变量在Haskell中是不可变的。当您拨打inc f
时,它会返回您立即忽略的值0 + 1
。 f
的值为0
,并且将始终如此。
谢谢!对于我的“防御”我必须说我知道关于不变性,但是我在这里找到了“n + 1”: http://hackage.haskell.org/packages/archive/mtl/1.1.0.2/doc/html/Control-Monad-State-Lazy.html and here http://www.haskell.org/tutorial /goodies.html 并想知道它可以工作。因为它编译我thoug ht, - 哇,这是有效的。这些蜱虫的目的是解决同样的问题,计数器是什么? –
谢谢。顺便说一下:当计时从答案#1的片段w IORefs我得到一半的运行时间相比,时间'平方
它可能是两个。使用分析和/或查看生成的低级别haskell核心语言来查看。同样使用-O2作为我的较短代码很大程度上依赖于优化的存在。 – nponeccop