haskell - tipos - ¿Cómo evito referirme a todas las variables de estado cuando actualizo solo unas pocas?
variables de trayectoria (3)
El combinador de zoom
lens
eleva un cálculo en una mónada de State
a un cálculo que se ejecuta en una mónada de State
"más grande".
zoom :: Lens'' s t -> State t a -> State s a
Entonces, dado un estado "grande":
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
puede "acercar" una parte del estado:
incr :: State Int ()
incr = id += 1
incrSmall :: State Big ()
incrSmall = zoom (big2.medium1.small) incr
Por supuesto, esto funcionará tanto en tuplas grandes como en discos, utilizando los Control.Lens.Tuple incorporados en la lens
.
La firma de tipo real de zoom
es más general que la simple citada anteriormente. Utiliza MonadState
restricciones de MonadState
para trabajar bajo una pila de transformadores de mónada, en lugar de hacerlo específicamente en el State
.
Un lenguaje que uso para componer un par de procedimientos (con memoria) es el siguiente:
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
Sin embargo, a medida que crece el número de variables de estado, esto se vuelve rápidamente más detallado de lo necesario:
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
Como me preguntaba, ¿hay alguna forma de actualizar solo algunas variables de estado sin tener que referirse a todas las que no se utilizan? Estaba pensando en algo como IORef
pero para State
(de hecho, hay un paquete stateref ), pero no estoy seguro si ya hay algunos modismos comunes que otras personas han estado usando.
Esta es una buena situación para usar registros, con las funciones de gets
y modify
para manipular subpartes del estado:
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
convierte una función getter pura en una acción de estado:
gets :: (s -> a) -> State s a
envNumber :: Env -> Int
gets envNumber :: State Env Int
Y modify
convierte una función de actualización pura en una acción de estado:
modify :: (s -> s) -> State s ()
(/r -> r { envText = b'' }) :: Env -> Env
modify (/r -> ...) :: State Env ()
Esto parece ser un trabajo para lenses . Especialmente el módulo Control.Lens.Tuple
junto con .=
Y use
:
p1 = do
a <- use _1
-- do something --
_1 .= a''
Sin embargo, generalmente es mejor si le da a los nombres propios de su estado, por ejemplo:
{-# LANGUAGE TemplateHaskell #-
data Record = MkRecord { _age :: Int
, _name :: String
, _programmer :: Bool
} deriving (Show, Eq)
makeLenses ''''Record
De esa manera, tienes mejores nombres para tu campo:
p1 = do
a <- use age
-- do something --
age .= a''
Tenga en cuenta que esto todavía le ayuda si no desea usar lentes, ya que puede usar la sintaxis de registro para actualizar sus datos:
p1 = do
r <- get
let a = _age r
--- do something
put $ r{_age = a''}