IO中的简单计数器

sho*_*tor 2 io haskell functional-programming

我正在尝试使用IO创建一个无限增加1的简单计数器.

从那以后我一直在挠头.

理想情况下,我想做一些事情

tick = do putStr (counter)
          counter + 1
    where counter = 0
Run Code Online (Sandbox Code Playgroud)

然后重复这个过程.然后重复前两个表达式.或者类似的东西:

tick = tick'
       where 
           counter = 1
           tick' counter | counter > 0 = do putStrLn (show counter)
                                            tick' (counter + 1)
                         | otherwise = tick
Run Code Online (Sandbox Code Playgroud)

这给了我错误:/

任何帮助表示赞赏:)

luq*_*qui 7

如果使用可变细胞,有几种方法可以做到这一点.你已经在第二次尝试时做到了,只有一点点错误.您需要将初始值传递给tick'函数,而不是"设置它"(haskell不知道分配变量 - 只有定义.如果该行x = y出现,x将是y整个生命周期).

tick = tick' 0
    where ...
Run Code Online (Sandbox Code Playgroud)

counter = 0条线没有做任何事情; 它定义了一个从未使用过的名称.在counter在所使用的tick'功能被绑定为它的一个参数(和阴影定义为0的一个).花点时间盯着它,看看是否有意义.

有一个很好的"高阶"方式,我们也可以做到这一点.基本上我们想要运行无限长的代码块:

do
    print 0
    print 1
    print 2
    ...
Run Code Online (Sandbox Code Playgroud)

有一个函数叫sequence :: [IO a] -> IO [a](见下面的警告),它将采取一系列动作并构建一个动作.因此,如果我们可以构造列表,[print 0, print 1, print 2, ...]那么我们可以将其传递sequence给构建我们正在寻找的无限长块.

请注意,这是Haskell中一个非常重要的概念:[print 0, print 1, print 2]不打印这三个数字然后构造列表[0,1,2].相反,它本身就是一个动作列表,其类型是[IO ()].使列表无效; 只有当你将一个动作绑定到main它才会执行它.例如,我们可能会说:

main = do
    let xs = [putStrLn "hello", getLine >> putStrLn "world"]
    xs !! 0
    xs !! 0
    xs !! 1
    xs !! 1
    xs !! 0
Run Code Online (Sandbox Code Playgroud)

这将两次打印hello,两次获得一行并world在每次打印后打印,然后再次打印hello.

有了这个概念,很容易[print 0, print 1, ...]用列表理解来构建动作列表:

main = sequence [ print x | x <- [0..] ]
Run Code Online (Sandbox Code Playgroud)

我们可以简化一下:

main = sequence (map (\x -> print x) [0..])
main = sequence (map print [0..])
Run Code Online (Sandbox Code Playgroud)

我们正在寻找map print [0..]的动作列表也是如此[print 0, print 1, ...],然后我们将sequence它们传递给它们在一起的链.

这种模式sequence很常见,并且有其独特之处mapM:

mapM :: (a -> IO b) -> [a] -> IO [b]
mapM f xs = sequence (map f xs)
Run Code Online (Sandbox Code Playgroud)

从而:

main = mapM print [0..]
Run Code Online (Sandbox Code Playgroud)

关于你想要的简单.

关于性能的一个注意事项:因为我们没有使用这些函数的输出,所以我们应该使用sequence_并且mapM_使用尾随下划线,这些是为此目的而优化的.通常这在Haskell程序中由于垃圾收集无关紧要,但在这个特定的用例中由于各种细微之处而是一种特殊情况.您会发现,如果没有_s,程序的内存使用量会随着结果列表(在本例中[(),(),(),...])构建但从未使用过而逐渐增长.

警告:我已经给出了类型签名sequencemapM专门的IO,而不是一般的monad,因此读者不必同时了解具有类型和类型类的动作的正交概念.