Estados aninhados em Haskell

9

Estou tentando definir uma família de máquinas de estado com tipos um pouco diferentes de estados. Em particular, as máquinas de estados mais "complexas" possuem estados formados pela combinação dos estados de máquinas de estados mais simples.

(É semelhante a uma configuração orientada a objetos, na qual um objeto possui vários atributos que também são objetos.)

Aqui está um exemplo simplificado do que eu quero alcançar.

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

De um modo mais geral, quero uma estrutura generalizada em que esses aninhamentos sejam mais complexos. Aqui está algo que eu gostaria de saber como fazer.

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

Por contexto, é isso que eu quero alcançar com este mecanismo:

Quero projetar essas coisas chamadas "Stream Transformers", que são basicamente funções com estado: elas consomem um token, modificam seu estado interno e produzem algo. Especificamente, estou interessado em uma classe de Stream Transformers em que a saída é um valor booleano; chamaremos esses "monitores".

Agora, estou tentando projetar combinadores para esses objetos. Alguns deles são:

  • Um precombinador. Suponha que monseja um monitor. Então, pre moné um monitor que sempre produz Falsedepois que o primeiro token é consumido e imita o comportamento moncomo se o token anterior estivesse sendo inserido agora. Gostaria de modelar o estado de pre moncom StateWithTriggerno exemplo acima, pois o novo estado é um booleano junto com o estado original.
  • Um andcombinador. Suponha que m1e m2sejam monitores. Então, m1 `and` m2é um monitor que alimenta o token para m1, e depois para m2, e produz Truese as duas respostas forem verdadeiras. Gostaria de modelar o estado de m1 `and` m2com CombinedStateno exemplo acima, pois o estado de ambos os monitores deve ser mantido.
Agnishom Chattopadhyay
fonte
Para sua informação, _innerVal <$> geté apenas gets _innerVal(as gets f == liftM f get, e liftMé fmapespecializado apenas em mônadas).
chepner
Onde você está obtendo um StateT InnerState m Intvalor em primeiro lugar outerStateFoo?
chepner
6
Você está confortável com a lente? Esse caso de uso parece ser exatamente o que zoomserve.
Carl
11
@ Carl Eu vi algumas lentes, mas não as entendo muito bem. Talvez você possa explicar em uma resposta como usar o zoom?
Agnishom Chattopadhyay
5
Uma observação: Esta entrada não contém uma única pergunta.
Simon Shine

Respostas:

4

Para a sua primeira pergunta, como Carl mencionou, zoomde lensfaz exatamente o que você quer. Seu código com lentes pode ser escrito assim:

{-# 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

Edit: Enquanto estamos nisso, se você já está trazendo lens, innerStateFoopode ser escrito assim:

innerStateFoo :: Monad m => StateT InnerState m Int
innerStateFoo = innerVal <<+= 1
John
fonte
5

Por contexto, é isso que eu quero alcançar com este mecanismo:

Quero projetar essas coisas chamadas "Stream Transformers", que são basicamente funções com estado: elas consomem um token, modificam seu estado interno e produzem algo. Especificamente, estou interessado em uma classe de Stream Transformers em que a saída é um valor booleano; chamaremos esses "monitores".

Eu acho que o que você deseja alcançar não precisa de muito maquinário.

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)

Isso StreamTransformernão é necessariamente stateful, mas admite os stateful. Você não precisa (e a IMO não deve! Na maioria dos casos !!) buscar classes tipográficas para defini-las (ou mesmo nunca! :), mas esse é outro tópico).

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
fonte
Isso é muito legal, obrigado! Esse padrão é chamado de algo?
Agnishom Chattopadhyay
3
Eu chamaria isso de pura programação funcional! Mas eu sei que não é a resposta que você está procurando :) StreamTransformer é na verdade uma "máquina de Mealy" hackage.haskell.org/package/machines-0.7/docs/...
Alexander Vieth
Não, a primeira saída que desapareceu não é o que eu pretendia. Gostaria de atrasar a primeira saída para ser a segunda.
Agnishom Chattopadhyay
2
E assim por diante, para que cada saída seja atrasada em um passo? Isso pode ser feito.
Alexander Vieth
11
muito bom, obrigado por postar! (desculpe-me por comentar anteriormente sem pesar, leia o Q corretamente). Eu acho que o OP significava 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