How do I avoid referring to all state variables when updating only a few?

后端 未结 3 719
名媛妹妹
名媛妹妹 2021-01-07 20:04

An idiom I use for composing a couple of procedures (with memory) is as follows:

p1 :: State (Int, String) ()
p1 = do
    (a, b) <- get
    ... do somethi         


        
相关标签:
3条回答
  • 2021-01-07 20:32

    This seems like a job for lenses. Especially the Control.Lens.Tuple module together with .= and use:

    p1 = do
       a <- use _1
       -- do something --
       _1 .= a'
    

    However, it's usually better if you give the things in your state proper names, e.g.

    {-# LANGUAGE TemplateHaskell #-
    
    data Record = MkRecord { _age :: Int
                           , _name :: String
                           , _programmer :: Bool
                           } deriving (Show, Eq)
    makeLenses ''Record
    

    That way, you have better names for your field:

    p1 = do
       a <- use age
       -- do something --
       age .= a'
    

    Note that this still helps you if you don't want to use lenses, since you can use record syntax to update your data:

     p1 = do
          r <- get
          let a = _age r
          --- do something
          put $ r{_age = a'}
    
    0 讨论(0)
  • 2021-01-07 20:35

    lens's zoom combinator lifts a computation in a State monad into a computation that runs in a "larger" State monad.

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

    So, given a "big" state:

    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
    

    you can "zoom in" on a part of the state:

    incr :: State Int ()
    incr = id += 1
    
    incrSmall :: State Big ()
    incrSmall = zoom (big2.medium1.small) incr
    

    Of course, this'll work on big tuples as well as records, using lens's built-in tuple field accessors.

    zoom's real type signature is more general than the simple one I quoted above. It uses MonadState constraints to work under a monad transformer stack, rather than in State specifically.

    0 讨论(0)
  • 2021-01-07 20:38

    This is a good situation to use records, with the gets and modify functions to manipulate subparts of the state:

    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 turns a pure getter function into a state action:

    gets :: (s -> a) -> State s a
    envNumber :: Env -> Int
    gets envNumber :: State Env Int
    

    And modify turns a pure update function into a state action:

    modify :: (s -> s) -> State s ()
    (\r -> r { envText = b' }) :: Env -> Env
    modify (\r -> ...) :: State Env ()
    
    0 讨论(0)
提交回复
热议问题