如何只更新几个时,如何避免引用所有状态变量?

xzh*_*zhu 19 haskell state-monad

我用来编写几个程序(带内存)的习语如下:

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
Run Code Online (Sandbox Code Playgroud)

但是,随着状态变量数量的增加,这很快就会变得比必要的更冗长:

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
Run Code Online (Sandbox Code Playgroud)

正如我想知道的那样,有没有办法只更新一些状态变量而不必参考所有未使用的变量?我的想法是这样IORef,但对于State(其实有一个包stateref),但我不知道是否有其他已经人们一直使用的是一些常见的成语.

Zet*_*eta 17

这似乎是镜头的工作.特别是Control.Lens.Tuple模块.=use:

p1 = do
   a <- use _1
   -- do something --
   _1 .= a'
Run Code Online (Sandbox Code Playgroud)

但是,通常情况下,如果你给你所在国家的名称提供正确的名称,例如

{-# LANGUAGE TemplateHaskell #-

data Record = MkRecord { _age :: Int
                       , _name :: String
                       , _programmer :: Bool
                       } deriving (Show, Eq)
makeLenses ''Record
Run Code Online (Sandbox Code Playgroud)

这样,您的字段名称就更好了:

p1 = do
   a <- use age
   -- do something --
   age .= a'
Run Code Online (Sandbox Code Playgroud)

请注意,如果您不想使用镜头,这仍然有用,因为您可以使用记录语法来更新数据:

 p1 = do
      r <- get
      let a = _age r
      --- do something
      put $ r{_age = a'}
Run Code Online (Sandbox Code Playgroud)

  • 在这种情况下,也许值得一提的是* lens *中的`zoom`。 (2认同)

Jon*_*rdy 11

这是一个使用记录的好情况,使用getsmodify函数来操作状态的子部分:

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' }
Run Code Online (Sandbox Code Playgroud)

gets 将纯粹的getter函数转换为状态动作:

gets :: (s -> a) -> State s a
envNumber :: Env -> Int
gets envNumber :: State Env Int
Run Code Online (Sandbox Code Playgroud)

并将modify纯更新函数转换为状态操作:

modify :: (s -> s) -> State s ()
(\r -> r { envText = b' }) :: Env -> Env
modify (\r -> ...) :: State Env ()
Run Code Online (Sandbox Code Playgroud)


Ben*_*son 5

lenszoom组合器Statemonad中的计算提升为以“更大” Statemonad 运行的计算。

zoom :: Lens' s t -> State t a -> State s a
Run Code Online (Sandbox Code Playgroud)

因此,给定一个“大”状态:

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
Run Code Online (Sandbox Code Playgroud)

您可以在部分状态下“放大”:

incr :: State Int ()
incr = id += 1

incrSmall :: State Big ()
incrSmall = zoom (big2.medium1.small) incr
Run Code Online (Sandbox Code Playgroud)

当然,使用lens内置的tuple字段访问器,这将适用于大元组和记录。

zoom的实型签名比我上面引用的简单签名更通用。它使用MonadState约束在monad变压器堆栈下工作,而不是State专门在约束下工作。