我正在尝试使用IO创建一个无限增加1的简单计数器。
从那以后我一直在挠头......
理想情况下,我想按照
的方式做点什么tick = do putStr (counter)
counter + 1
where counter = 0
然后重复这个过程。然后重复前两个表达式。或者类似的东西:
tick = tick'
where
counter = 1
tick' counter | counter > 0 = do putStrLn (show counter)
tick' (counter + 1)
| otherwise = tick
这给了我错误:/
感谢任何帮助:)
答案 0 :(得分:7)
有几种方法可以使用可变单元格而不使用。你已经在第二次尝试时做到了,只有一点点错误。您需要将初始值传递给tick'
函数,而不是“设置它”(haskell不知道分配变量 - 只有定义。如果显示行x = y
,{{1} }它的整个生命周期都是x
。
y
tick = tick' 0
where ...
行没有做任何事情;它定义了一个从未使用过的名称。 counter = 0
函数中使用的counter
被绑定为其参数之一(并且阴影定义为0)。花点时间盯着它,看看是否有意义。
我们也可以采用一种很好的“高阶”方式。基本上我们想要运行无限长的代码块:
tick'
有一个名为do
print 0
print 1
print 2
...
的函数(请参阅下面的警告),它将采取一系列操作并构建一个操作。因此,如果我们可以构造列表sequence :: [IO a] -> IO [a]
,那么我们可以将它传递给[print 0, print 1, print 2, ...]
来构建我们正在寻找的无限长块。
请注意,这是Haskell中一个非常重要的概念:sequence
不打印这三个数字然后构造列表[print 0, print 1, print 2]
。相反,它本身就是 actions 的列表,其类型为[0,1,2]
。使列表无效;只有当你将一个动作绑定到[IO ()]
时才会被执行。例如,我们可以说:
main
这将两次打印main = do
let xs = [putStrLn "hello", getLine >> putStrLn "world"]
xs !! 0
xs !! 0
xs !! 1
xs !! 1
xs !! 0
,两次获得一行并在每次打印后hello
,然后再次打印world
。
通过这个概念,可以很容易地构建具有列表理解的动作列表hello
:
[print 0, print 1, ...]
我们可以简化一下:
main = sequence [ print x | x <- [0..] ]
所以main = sequence (map (\x -> print x) [0..])
main = sequence (map print [0..])
是我们正在寻找的动作列表map print [0..]
,然后我们将其传递给[print 0, print 1, ...]
,将它们链接在一起。
这种sequence
模式很常见,并且有自己的sequence
:
mapM
因此:
mapM :: (a -> IO b) -> [a] -> IO [b]
mapM f xs = sequence (map f xs)
关于你想要的简单。
关于性能的一个注意事项:由于我们没有使用这些函数的输出,因此我们应该使用main = mapM print [0..]
和sequence_
,以及为此目的而优化的尾部下划线。通常这在Haskell程序中由于垃圾收集无关紧要,但在这个特定的用例中由于各种细微之处而是一种特殊情况。您会发现,如果没有mapM_
s,程序的内存使用量会随着结果列表(在这种情况下为_
)构建但从未使用过而逐渐增长。
警告:我已经将[(),(),(),...]
和sequence
的类型签名专门用于mapM
,而不是一般的monad,以便读者没有了解同时具有类型和类型类的动作的正交概念。
答案 1 :(得分:3)
好吧,让我们回到基础。你想要什么似乎是一个IO动作,当绑定,打印和增加一个计数器?我将从这个假设开始工作。
你需要的第一件事是一些可变细胞,因为你每次都使用相同的动作。它需要在其内部具有可变性,以便在每次使用时执行不同的操作。对于这种情况,我会选择IORef
。
但保持隐藏IORef
有点棘手。特别是因为全局变量很糟糕。最好的方法是从另一个IO操作中创建IO操作,然后关闭IORef
。这样做会给你这样的事情:
import Data.IORef
mkCounter :: IO (IO ())
mkCounter = do
ref <- newIORef 0
return $ do
counter <- readIORef ref
print counter
writeIORef ref $ counter + 1
这可以通过这样做来实现:
main = do
tick <- mkCounter
tick
tick
tick
答案 2 :(得分:2)
你的第二次实施非常接近!
tick = tick'
where
counter = 1
tick' counter | counter > 0 = do putStrLn (show counter)
tick' (counter + 1)
| otherwise = tick
让我们看一下这个错误:
Couldn't match expected type `IO b0' with actual type `a0 -> IO b0'
In the expression: tick'
让我们添加一些类型以确保我们得到我们想要的东西。
tick
是IO操作。因为整体而言,我们并不关心行动所包含的价值
这是永远的运行。
tick :: IO a
现在我们的错误是:
Couldn't match expected type `IO a' with actual type `a0 -> IO b0'
In the expression: tick'
嗯,那几乎是一样的,没有帮助。让我们继续。
tick'
是一个接受一些整数并返回IO动作的函数
打印整数并在下一个值上重复tick'
。再说一次,我们不关心什么
将动作封装起来,因为它永远运行。
tick' :: Int -> IO b
等等,现在错误有意义了!我们定义了tick = tick'
,但这两个东西有着根本不同的类型。一个是动作(tick
),一个是返回动作(tick'
)的动作。我们需要做的就是给tick'
一些值来获取动作,所以让我们这样做。
你试图通过说where counter = 1
来做到这一点,但所有这一切都是在counter
语句中将tick = tick'
定义为1,并且因为counter
没有被提及在那里,没有使用它。
当您说tick' counter | ... =
时,您没有提到与上面一行相同的counter
。在那里,您定义了另一个名为counter
的变量,该变量仅在tick'
的定义范围内。
现在我们的代码如下:
tick :: IO a
tick = tick' 1
where
tick' :: Int -> IO b
tick' counter | counter > 0 = do putStrLn (show counter)
tick' (counter + 1)
| otherwise = tick
如果我们尝试编译它,ghc
不会抱怨,如果我们在ghci
中尝试它,它会按照预期运行:
% ghci
ghci> :l Tick.hs
Ok, modules loaded: Tick.
ghci> tick
1
2
3
...
25244
^C
Interrupted
ghci>
答案 3 :(得分:1)
对于一个简单的无限计数器,只需使用递归:
counter n = do print n
counter (n+1)
main = counter 1
在不使用可变状态的情况下实现tick
功能的另一种方法是使用monad变换器混合State
和IO
monad:
import Control.Monad.State
type Ticking a = StateT Int IO a
tick :: Ticking ()
tick = do
modify succ
get >>= liftIO . print
getCounterValue :: Ticking Int
getCounterValue = get
然后你可以用它来创建'滴答'IO
函数(带有麻烦:IO
这里的函数需要以liftIO
为前缀,因为它现在是Ticking a
monad不是IO a
):
ticking :: Ticking ()
ticking = do
liftIO $ putStrLn "Starting"
tick
tick
c <- getCounterValue
liftIO $ do
putStrLn ("Finished at " ++ show c)
putStrLn "Press any Enter to start infinite counter"
getChar
forever tick
可以使用IO
(使用初始计数器值)将其转换为“普通”runStateT
:
startTicking :: Ticking a -> Int -> IO a
startTicking = evalStateT
所以:
main :: IO ()
main = startTicking ticking 0
答案 4 :(得分:0)
类似于卡尔使用STM的答案的forkIO安全版本是
import Control.Concurrent.STM
import Control.Monad (replicateM_)
import Control.Monad(forever)
makeCounter :: IO (IO Int)
makeCounter = do
var <- newTVarIO 0
return $ do
atomically $ do
value <- readTVar var
modifyTVar var (+1)
readTVar var
-- a version that only counts from 1 to 10
main1:: IO ()
main1 = do
counter <- makeCounter
replicateM_ 10 $ counter >>= print
-- a version that counters forever
main2 :: IO ()
main2 = do
counter <- makeCounter
forever $ do
x<- counter
print x
main :: IO ()
main = do
counter <- makeCounter
tick<- counter
tick<- counter
print tick -- 2
参考: