États imbriqués à Haskell

9

J'essaie de définir une famille de machines à états avec des types d'états quelque peu différents. En particulier, les machines à états les plus "complexes" ont des états qui sont formés en combinant les états des machines à états plus simples.

(Ceci est similaire à un paramètre orienté objet où un objet a plusieurs attributs qui sont également des objets.)

Voici un exemple simplifié de ce que je veux réaliser.

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

Plus généralement, je souhaite un cadre généralisé où ces emboîtements sont plus complexes. Voici quelque chose que je souhaite savoir faire.

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

Pour le contexte, voici ce que je veux réaliser avec cette machine:

Je veux concevoir ces choses appelées "Stream Transformers", qui sont essentiellement des fonctions avec état: elles consomment un jeton, modifient leur état interne et produisent quelque chose. Plus précisément, je suis intéressé par une classe de transformateurs de flux où la sortie est une valeur booléenne; nous appellerons ces "moniteurs".

Maintenant, j'essaie de concevoir des combinateurs pour ces objets. Certains d'entre eux sont:

  • Un precombinateur. Supposons que ce monsoit un moniteur. Ensuite, pre monest un moniteur qui produit toujours Falseaprès la consommation du premier jeton, puis imite le comportement moncomme si le jeton précédent était inséré maintenant. Je voudrais modéliser l'état de pre monavec StateWithTriggerdans l'exemple ci-dessus car le nouvel état est un booléen avec l'état d'origine.
  • Un andcombinateur. Supposons que m1et m2sont moniteurs. Ensuite, m1 `and` m2est un moniteur qui envoie le jeton à m1, puis à m2, puis produit Truesi les deux réponses sont vraies. Je voudrais modéliser l'état de m1 `and` m2avec CombinedStatedans l'exemple ci-dessus car l'état des deux moniteurs doit être maintenu.
Agnishom Chattopadhyay
la source
FYI, _innerVal <$> getest juste gets _innerVal(comme gets f == liftM f get, et liftMest juste fmapspécialisé aux monades).
chepner
Où obtenez-vous une StateT InnerState m Intvaleur en premier lieu outerStateFoo?
chepner
6
Êtes-vous à l'aise avec l'objectif? Ce cas d'utilisation semble être exactement à quoi zoomsert.
Carl
1
@Carl J'ai vu des lentilles mais je ne les comprends pas très bien. Peut-être pouvez-vous expliquer dans une réponse comment utiliser le zoom?
Agnishom Chattopadhyay
5
Une observation: cette entrée ne contient pas une seule question.
Simon Shine

Réponses:

4

Pour votre première question, comme Carl l'a mentionné, zoomde lensfait exactement ce que vous voulez. Votre code avec des lentilles pourrait être écrit comme ceci:

{-# 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: Pendant que nous y sommes, si vous apportez déjà, lensalors innerStateFoopeut être écrit comme suit:

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

Pour le contexte, voici ce que je veux réaliser avec cette machine:

Je veux concevoir ces choses appelées "Stream Transformers", qui sont essentiellement des fonctions avec état: elles consomment un jeton, modifient leur état interne et produisent quelque chose. Plus précisément, je suis intéressé par une classe de transformateurs de flux où la sortie est une valeur booléenne; nous appellerons ces "moniteurs".

Je pense que ce que vous voulez réaliser n'a pas besoin de beaucoup de machines.

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)

Ce StreamTransformern'est pas nécessairement avec état, mais admet ceux avec état. Vous n'avez pas besoin (et l'OMI ne devrait pas! Dans la plupart des cas !!) d'atteindre les classes de caractères afin de les définir (ou même jamais! :) mais c'est un autre sujet).

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
la source
C'est très cool, merci! Ce modèle s'appelle-t-il quelque chose?
Agnishom Chattopadhyay
3
Je l'appellerais simplement de la programmation fonctionnelle pure! Mais je sais que ce n'est pas la réponse que vous cherchez :) StreamTransformer est en fait une "machine Mealy" hackage.haskell.org/package/machines-0.7/docs/…
Alexander Vieth
Non, la première sortie disparaissant n'est pas ce que je voulais. Je voudrais retarder la première sortie pour être la deuxième.
Agnishom Chattopadhyay
2
Et ainsi de suite pour que chaque sortie soit retardée d'une étape? Cela peut être fait.
Alexander Vieth
1
très sympa, merci d'avoir posté! (désolé d'avoir commenté précédemment sans avoir lu correctement le Q). Je pense que le PO signifiait 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