仅更新少数状态变量时如何避免引用所有状态变量?
How do I avoid referring to all state variables when updating only a few?
我用来编写几个程序(带记忆)的成语如下:
p1 :: State (Int, String) ()
p1 = do
(a, b) <- get
... do something ...
put (a', b)
p2 :: State (Int, String) ()
p2 = do
(a, b) <- get
... do something else ...
put (a, b')
main = do
... initializing a0 b0 ...
print . flip evalState (a0, b0)
. sequence $ replicate 10 p1 ++ repeat p2
然而,随着状态变量数量的增加,这很快就会变得比必要的更冗长:
p1 :: State (Int, String, Bool, Int, String, Bool) ()
p1 = do
(a, b, c, d, e, f) <- get
... do something ...
put (a, b, c', d, e, f')
p2 :: State (Int, String, Bool, Int, String, Bool) ()
p2 = do
(a, b, c, d, e, f) <- get
... do something ...
put (a', b', c, d, e, f)
main = do
print . flip evalState (a0, b0, c0, d0, e0, f0)
. sequence $ replicate 10 p1 ++ repeat p2
我想知道,有没有一种方法可以只更新几个状态变量而不必引用所有未使用的变量?我在想 IORef
but for State
(实际上有一个包 stateref),但我不确定是否已经有一些其他人一直在使用的常见习语.
这似乎是 lenses. Especially the Control.Lens.Tuple
模块以及 .=
和 use
的工作:
p1 = do
a <- use _1
-- do something --
_1 .= a'
但是,通常情况下,最好为您所在州的事物命名,例如
{-# LANGUAGE TemplateHaskell #-
data Record = MkRecord { _age :: Int
, _name :: String
, _programmer :: Bool
} deriving (Show, Eq)
makeLenses ''Record
这样,你的领域就有了更好的名字:
p1 = do
a <- use age
-- do something --
age .= a'
请注意,如果您不想使用镜头,这仍然对您有帮助,因为您可以使用记录语法来更新数据:
p1 = do
r <- get
let a = _age r
--- do something
put $ r{_age = a'}
这是使用记录的好情况,使用 gets
和 modify
函数来操作状态的子部分:
data Env = Env
{ envNumber :: Int
, envText :: String
}
p1 :: State Env ()
p1 = do
a <- gets envNumber
-- ...
modify $ \r -> r { envNumber = a' }
p2 :: State Env ()
p2 = do
b <- gets envText
-- ...
modify $ \r -> r { envText = b' }
gets
将纯 getter 函数转换为状态动作:
gets :: (s -> a) -> State s a
envNumber :: Env -> Int
gets envNumber :: State Env Int
并且modify
将一个纯更新函数变成了一个状态动作:
modify :: (s -> s) -> State s ()
(\r -> r { envText = b' }) :: Env -> Env
modify (\r -> ...) :: State Env ()
lens
的 zoom
combinator 将 State
monad 中的计算提升为在 "larger" State
monad 中运行的计算。
zoom :: Lens' s t -> State t a -> State s a
所以,给定一个 "big" 状态:
data Big = Big {
_big1 :: Medium,
_big2 :: Medium
}
data Medium = Medium {
_medium1 :: Small,
_medium2 :: Small
}
data Small = Small { _small :: Int }
makeLenses ''Big
makeLenses ''Medium
makeLenses ''Small
您可以 "zoom in" 部分州:
incr :: State Int ()
incr = id += 1
incrSmall :: State Big ()
incrSmall = zoom (big2.medium1.small) incr
当然,这将适用于大元组和记录,使用 lens
的内置 tuple field accessors。
zoom
的真实类型签名比我上面引用的简单签名更通用。它使用 MonadState
约束在 monad 转换器堆栈下工作,而不是在 State
中具体工作。
我用来编写几个程序(带记忆)的成语如下:
p1 :: State (Int, String) ()
p1 = do
(a, b) <- get
... do something ...
put (a', b)
p2 :: State (Int, String) ()
p2 = do
(a, b) <- get
... do something else ...
put (a, b')
main = do
... initializing a0 b0 ...
print . flip evalState (a0, b0)
. sequence $ replicate 10 p1 ++ repeat p2
然而,随着状态变量数量的增加,这很快就会变得比必要的更冗长:
p1 :: State (Int, String, Bool, Int, String, Bool) ()
p1 = do
(a, b, c, d, e, f) <- get
... do something ...
put (a, b, c', d, e, f')
p2 :: State (Int, String, Bool, Int, String, Bool) ()
p2 = do
(a, b, c, d, e, f) <- get
... do something ...
put (a', b', c, d, e, f)
main = do
print . flip evalState (a0, b0, c0, d0, e0, f0)
. sequence $ replicate 10 p1 ++ repeat p2
我想知道,有没有一种方法可以只更新几个状态变量而不必引用所有未使用的变量?我在想 IORef
but for State
(实际上有一个包 stateref),但我不确定是否已经有一些其他人一直在使用的常见习语.
这似乎是 lenses. Especially the Control.Lens.Tuple
模块以及 .=
和 use
的工作:
p1 = do
a <- use _1
-- do something --
_1 .= a'
但是,通常情况下,最好为您所在州的事物命名,例如
{-# LANGUAGE TemplateHaskell #-
data Record = MkRecord { _age :: Int
, _name :: String
, _programmer :: Bool
} deriving (Show, Eq)
makeLenses ''Record
这样,你的领域就有了更好的名字:
p1 = do
a <- use age
-- do something --
age .= a'
请注意,如果您不想使用镜头,这仍然对您有帮助,因为您可以使用记录语法来更新数据:
p1 = do
r <- get
let a = _age r
--- do something
put $ r{_age = a'}
这是使用记录的好情况,使用 gets
和 modify
函数来操作状态的子部分:
data Env = Env
{ envNumber :: Int
, envText :: String
}
p1 :: State Env ()
p1 = do
a <- gets envNumber
-- ...
modify $ \r -> r { envNumber = a' }
p2 :: State Env ()
p2 = do
b <- gets envText
-- ...
modify $ \r -> r { envText = b' }
gets
将纯 getter 函数转换为状态动作:
gets :: (s -> a) -> State s a
envNumber :: Env -> Int
gets envNumber :: State Env Int
并且modify
将一个纯更新函数变成了一个状态动作:
modify :: (s -> s) -> State s ()
(\r -> r { envText = b' }) :: Env -> Env
modify (\r -> ...) :: State Env ()
lens
的 zoom
combinator 将 State
monad 中的计算提升为在 "larger" State
monad 中运行的计算。
zoom :: Lens' s t -> State t a -> State s a
所以,给定一个 "big" 状态:
data Big = Big {
_big1 :: Medium,
_big2 :: Medium
}
data Medium = Medium {
_medium1 :: Small,
_medium2 :: Small
}
data Small = Small { _small :: Int }
makeLenses ''Big
makeLenses ''Medium
makeLenses ''Small
您可以 "zoom in" 部分州:
incr :: State Int ()
incr = id += 1
incrSmall :: State Big ()
incrSmall = zoom (big2.medium1.small) incr
当然,这将适用于大元组和记录,使用 lens
的内置 tuple field accessors。
zoom
的真实类型签名比我上面引用的简单签名更通用。它使用 MonadState
约束在 monad 转换器堆栈下工作,而不是在 State
中具体工作。