Ich versuche, eine Familie von Zustandsautomaten mit etwas anderen Arten von Zuständen zu definieren. Insbesondere haben die "komplexeren" Zustandsmaschinen Zustände, die durch Kombinieren der Zustände einfacherer Zustandsmaschinen gebildet werden.
(Dies ähnelt einer objektorientierten Einstellung, bei der ein Objekt mehrere Attribute hat, die auch Objekte sind.)
Hier ist ein vereinfachtes Beispiel dafür, was ich erreichen möchte.
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
Generell möchte ich ein allgemeines Framework, in dem diese Verschachtelungen komplexer sind. Hier ist etwas, das ich wissen möchte, wie es geht.
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
Für den Kontext ist dies das, was ich mit dieser Maschinerie erreichen möchte:
Ich möchte diese Dinge entwerfen, die als "Stream-Transformatoren" bezeichnet werden und im Grunde genommen statusbehaftete Funktionen sind: Sie verbrauchen einen Token, mutieren ihren internen Zustand und geben etwas aus. Insbesondere interessiert mich eine Klasse von Stream-Transformatoren, bei denen die Ausgabe ein Boolescher Wert ist. Wir werden diese "Monitore" nennen.
Jetzt versuche ich, Kombinatoren für diese Objekte zu entwerfen. Einige von ihnen sind:
- Ein
pre
Kombinator. Angenommen, dasmon
ist ein Monitor. Dannpre mon
handelt es sich um einen Monitor, der immerFalse
nach dem Verbrauch des ersten Tokens erzeugt und dann das Verhalten nachahmt,mon
als würde das vorherige Token jetzt eingefügt. Ich möchte den Zustand vonpre mon
mitStateWithTrigger
im obigen Beispiel modellieren, da der neue Zustand zusammen mit dem ursprünglichen Zustand ein Boolescher Wert ist. - Ein
and
Kombinator. Nehmen wir an ,m1
undm2
sind Monitore. Dannm1 `and` m2
ist ein Monitor, der das Token an m1 und dann an m2 weiterleitet und dann erzeugt,True
wenn beide Antworten wahr sind. Ich möchte den Status vonm1 `and` m2
mitCombinedState
im obigen Beispiel modellieren, da der Status beider Monitore beibehalten werden muss.
quelle
_innerVal <$> get
ist gerechtgets _innerVal
(wiegets f == liftM f get
undliftM
ist nurfmap
auf Monaden spezialisiert).StateT InnerState m Int
WertouterStateFoo
?zoom
ist.Antworten:
Für Ihre erste Frage, wie Carl erwähnt,
zoom
vonlens
genau das tut , was Sie wollen. Ihr Code mit Objektiven könnte folgendermaßen geschrieben werden:Edit: Während wir gerade dabei sind, wenn Sie sich bereits dafür
lens
danninnerStateFoo
kann wie so geschrieben werden:quelle
Ich denke, dass das, was Sie erreichen wollen, nicht viel Maschinen benötigt.
Dies
StreamTransformer
ist nicht unbedingt zustandsbehaftet, lässt aber zustandsbehaftete zu. Sie müssen nicht (und IMO sollte es nicht! In den meisten Fällen !!) nach Typklassen greifen, um diese zu definieren (oder tatsächlich jemals! :), aber das ist ein anderes Thema).quelle
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'))
.