Подтвердить что ты не робот

Как избежать ссылки на все переменные состояния при обновлении только нескольких?

Идиома, которую я использую для составления нескольких процедур (с памятью), выглядит следующим образом:

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 но для State (на самом деле есть пакет stateref), но я не уверен, что есть уже некоторые распространенные идиомы, которые использовали другие люди.

4b9b3361

Ответ 1

Это похоже на работу для объективов. В частности, модуль 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'}

Ответ 2

Хорошая ситуация для использования записей с функциями 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 превращает чистую геттерную функцию в действие состояния:

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 ()

Ответ 3

lens zoom combinator поднимает вычисление в монаде State в вычисление, которое выполняется в "более крупном" State monad.

zoom :: Lens' s t -> State t a -> State s a

Итак, учитывая "большое" состояние:

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

вы можете "увеличить масштаб" на части состояния:

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

incrSmall :: State Big ()
incrSmall = zoom (big2.medium1.small) incr

Конечно, это будет работать с большими кортежами, а также с записями, используя lens встроенный атрибуты полей кортежа.

zoom подпись реального типа более общая, чем простая, приведенная выше. Он использует ограничения MonadState для работы в стеке монадного трансформатора, а не в State.