我试着做一个简单的计数器.然而,我的柜台并没有上升.在我看来好像每次都通过函数"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保持其初始值,因此您无法更改它们.
如果没有变量可以改变,你怎么写?答案是:
幸运的是,您很少需要自己编写递归,因为大多数递归模式已经在标准库中.
在您的情况下,您需要执行多个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_不是一个好选择:
_ 表示它会抛出计数器的最终值而不是返回它你需要在这里没有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)
在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)
就像在外面的代码中一样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)
| 归档时间: |
|
| 查看次数: |
808 次 |
| 最近记录: |