为什么我不能获得增加的计数器价值?

时间:2011-06-12 23:16:47

标签: haskell monads

我正在尝试使用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 .... 我知道那里肯定有一些愚蠢的问题,你们可以指出怎么样?

1 个答案:

答案 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)
...