Estados Anidados en Haskell

9

Estoy tratando de definir una familia de máquinas de estados con tipos de estados algo diferentes. En particular, las máquinas de estado más "complejas" tienen estados que se forman combinando los estados de máquinas de estado más simples.

(Esto es similar a una configuración orientada a objetos donde un objeto tiene varios atributos que también son objetos).

Aquí hay un ejemplo simplificado de lo que quiero lograr.

data InnerState = MkInnerState { _innerVal :: Int }

data OuterState = MkOuterState { _outerTrigger :: Bool, _inner :: InnerState }

innerStateFoo :: Monad m => StateT InnerState m Int
innerStateFoo = do
  i <- _innerVal <$> get
  put $ MkInnerState (i + 1)
  return i

outerStateFoo :: Monad m =>  StateT OuterState m Int
outerStateFoo = do
  b <- _outerTrigger <$> get
  if b
    then
       undefined
       -- Here I want to "invoke" innerStateFoo
       -- which should work/mutate things
        -- "as expected" without
       -- having to know about the outerState it
       -- is wrapped in
    else
       return 666

En términos más generales, quiero un marco generalizado donde estos anidamientos sean más complejos. Aquí hay algo que deseo saber hacer.

class LegalState s

data StateLess

data StateWithTrigger where
  StateWithTrigger :: LegalState s => Bool -- if this trigger is `True`, I want to use
                                   -> s    -- this state machine
                                   -> StateWithTrigger

data CombinedState where
  CombinedState :: LegalState s => [s] -- Here is a list of state machines.
                                -> CombinedState -- The combinedstate state machine runs each of them

instance LegalState StateLess
instance LegalState StateWithTrigger
instance LegalState CombinedState

liftToTrigger :: Monad m, LegalState s => StateT s m o -> StateT StateWithTrigger m o
liftToCombine :: Monad m, LegalState s => [StateT s m o] -> StateT CombinedState m o

Para el contexto, esto es lo que quiero lograr con esta maquinaria:

Quiero diseñar estas cosas llamadas "Transformadores de flujo", que son básicamente funciones con estado: consumen un token, mutan su estado interno y generan algo. Específicamente, estoy interesado en una clase de Transformadores de Stream donde la salida es un valor booleano; llamaremos a estos "monitores".

Ahora, estoy tratando de diseñar combinadores para estos objetos. Algunos de ellos son:

  • Un precombinador Supongamos que mones un monitor. Entonces, pre mones un monitor que siempre produce Falsedespués de que se consume el primer token y luego imita el comportamiento de moncomo si el token anterior se estuviera insertando ahora. Me gustaría modelar el estado de pre monwith StateWithTriggeren el ejemplo anterior ya que el nuevo estado es booleano junto con el estado original.
  • Un andcombinador Supongamos que m1y m2son monitores. Luego, m1 `and` m2es un monitor que alimenta el token a m1, y luego a m2, y luego produce Truesi ambas respuestas fueron verdaderas. Me gustaría modelar el estado de m1 `and` m2with CombinedStateen el ejemplo anterior ya que se debe mantener el estado de ambos monitores.
Agnishom Chattopadhyay
fuente
Para su información, _innerVal <$> getes justo gets _innerVal(como gets f == liftM f get, y liftMestá fmapespecializado para mónadas).
chepner
¿Dónde obtienes un StateT InnerState m Intvalor en primer lugar outerStateFoo?
chepner
66
¿Te sientes cómodo con la lente? Este caso de uso parece ser exactamente para qué zoomsirve.
Carl
1
@Carl He visto algunas lentes pero no las entiendo muy bien. ¿Quizás pueda explicar en una respuesta cómo usar el zoom?
Agnishom Chattopadhyay
55
Una observación: esta entrada no contiene una sola pregunta.
Simon Shine

Respuestas:

4

Para su primera pregunta, como Carl mencionó, zoomde lenshace exactamente lo que quiere. Su código con lentes podría escribirse así:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens
import Control.Monad.State.Lazy

newtype InnerState = MkInnerState { _innerVal :: Int }
  deriving (Eq, Ord, Read, Show)

data OuterState = MkOuterState
  { _outerTrigger :: Bool
  , _inner        :: InnerState
  } deriving (Eq, Ord, Read, Show)

makeLenses ''InnerState
makeLenses ''OuterState

innerStateFoo :: Monad m => StateT InnerState m Int
innerStateFoo = do
  i <- gets _innerVal
  put $ MkInnerState (i + 1)
  return i

outerStateFoo :: Monad m =>  StateT OuterState m Int
outerStateFoo = do
  b <- gets _outerTrigger
  if b
    then zoom inner $ innerStateFoo
    else pure 666

Editar: mientras estamos en eso, si ya está trayendo lens, innerStateFoopuede escribirse así:

innerStateFoo :: Monad m => StateT InnerState m Int
innerStateFoo = innerVal <<+= 1
Juan
fuente
5

Para el contexto, esto es lo que quiero lograr con esta maquinaria:

Quiero diseñar estas cosas llamadas "Transformadores de flujo", que son básicamente funciones con estado: consumen un token, mutan su estado interno y generan algo. Específicamente, estoy interesado en una clase de Transformadores de Stream donde la salida es un valor booleano; llamaremos a estos "monitores".

Creo que lo que quieres lograr no necesita mucha maquinaria.

newtype StreamTransformer input output = StreamTransformer
  { runStreamTransformer :: input -> (output, StreamTransformer input output)
  }

type Monitor input = StreamTransformer input Bool

pre :: Monitor input -> Monitor input
pre st = StreamTransformer $ \i ->
  -- NB: the first output of the stream transformer vanishes.
  -- Is that OK? Maybe this representation doesn't fit the spec?
  let (_, st') = runStreamTransformer st i
  in  (False, st')

and :: Monitor input -> Monitor input -> Monitor input
and left right = StreamTransformer $ \i ->
  let (bleft,  mleft)  = runStreamTransformer left  i
      (bright, mright) = runStreamTransformer right i
  in  (bleft && bright, mleft `and` mright)

Esto StreamTransformerno es necesariamente con estado, pero admite los con estado. No es necesario (¡y la OMI no debería! ¡En la mayoría de los casos!) Buscar clases de tipos para definirlas (¡o incluso alguna vez! :) pero ese es otro tema).

notStateful :: StreamTransformer input ()
notStateful = StreamTransformer $ \_ -> ((), notStateful)

stateful :: s -> (input -> s -> (output, s)) -> StreamTransformer input output
stateful s k = StreamTransformer $ \input ->
  let (output, s') = k input s
  in  (output, stateful s' k)

alternateBool :: Monitor anything
alternateBool = stateful True $ \_ s -> (s, not s)
Alexander Vieth
fuente
Esto es genial, gracias! ¿Este patrón se llama algo?
Agnishom Chattopadhyay
3
¡Solo lo llamaría pura programación funcional! Pero sé que esa no es la respuesta que estás buscando :) StreamTransformer es, de hecho, un hackage
Alexander Vieth
No, la primera salida que se desvanece no es lo que pretendía. Me gustaría retrasar la primera salida para que sea la segunda.
Agnishom Chattopadhyay
2
¿Y así sucesivamente para que cada salida se retrase un paso? Eso se puede hacer.
Alexander Vieth
1
muy bien, gracias por publicar! (Perdón por comentar previamente sin tener que leer la Q correctamente). Creo que el OP quiso decir pre st = stateful (Nothing, st) k where k i (s,st) = let (o, st') = runStreamTransformer st i in ( maybe False id s , (Just o, st')).
Will Ness