我正在尝试使用Haskell中的monads进行全局计数器,我希望每次使用monad计数器时都获得递增的值,但我每次都得到相同的值! 代码清单如下:
module CounterMonad where
data Counter a = C (Int -> (Int, a))
--reset the counter
new :: Counter ()
new = C $ \_ -> (0, ())
-- increment the counter:
--inc :: Counter Int
--inc = C $ \n -> (n+1, n)
inc = get >>= \s -> (put (s+1))
-- returning the current value of the counter
get :: Counter Int
get = C $ \n -> (n, n)
--
put x = C $ \n -> (x, x)
--return is nop, >>= is sequential exectuion
instance Monad Counter where
return r = C $ \n -> (n, r)
(>>=) (C f) g = C $ \n0 -> let (n1, r1) = f n0
C g' = g r1
in g' n1
run :: Counter a -> a
run (C f) = snd (f 0)
tickC = do
inc
c <- get
return c
当我尝试执行run tickC
时,它总是返回1。
我想要的是每次我run tickC
,它返回递增的值,如1,,2,3,4 ....
我知道那里肯定有一些愚蠢的问题,你们可以指出怎么样?
答案 0 :(得分:6)
这是正确的行为。每次调用run tickC
时,run
函数都会在计数器设置为零的情况下评估操作。基本上,每次调用run
时,您都会得到一个初始化为零的计数器的不同“副本”。
如果您希望每次都有计数器增量,则必须在run
的同一次调用中执行所有操作。例如,
tickMany = do
x <- tickC
y <- tickC
z <- tickC
return [x, y, z]
> run tickMany
[1, 2, 3]
所有monad都是如此,包括IO
(忽略“不安全”操作)。由于运行IO
操作的唯一方法是main
,因此IO
monad会遍历使用它的每个函数。
因此,如果你想要一个全局计数器,那么该计数器必须由全局使用的monad(即每个需要访问它的函数)来管理。您可以全局使用Counter
monad,也可以将计数器放入IO
monad。将这样的状态放在你自己的monad中似乎是公认的设计实践,但当然,这取决于应用程序,IO
也很好。
您可能还希望查看Control.Monad.State
,这样您就可以更轻松地定义monad。类似的东西:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Monad.State
newtype Counter a = Counter (State Int a) deriving (Monad)
...