Verschachtelte Staaten in Haskell

9

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 preKombinator. Angenommen, das monist ein Monitor. Dann pre monhandelt es sich um einen Monitor, der immer Falsenach dem Verbrauch des ersten Tokens erzeugt und dann das Verhalten nachahmt, monals würde das vorherige Token jetzt eingefügt. Ich möchte den Zustand von pre monmit StateWithTriggerim obigen Beispiel modellieren, da der neue Zustand zusammen mit dem ursprünglichen Zustand ein Boolescher Wert ist.
  • Ein andKombinator. Nehmen wir an , m1und m2sind Monitore. Dann m1 `and` m2ist ein Monitor, der das Token an m1 und dann an m2 weiterleitet und dann erzeugt, Truewenn beide Antworten wahr sind. Ich möchte den Status von m1 `and` m2mit CombinedStateim obigen Beispiel modellieren, da der Status beider Monitore beibehalten werden muss.
Agnishom Chattopadhyay
quelle
Zu Ihrer Information, _innerVal <$> getist gerecht gets _innerVal(wie gets f == liftM f getund liftMist nur fmapauf Monaden spezialisiert).
Chepner
Woher bekommst du überhaupt einen StateT InnerState m IntWert outerStateFoo?
Chepner
6
Fühlen Sie sich wohl mit Objektiv? Dieser Anwendungsfall scheint genau das zu sein, wofür er gedacht zoomist.
Carl
1
@ Carl Ich habe einige Objektive gesehen, verstehe sie aber nicht sehr gut. Vielleicht können Sie in einer Antwort erklären, wie man Zoom benutzt?
Agnishom Chattopadhyay
5
Eine Bemerkung: Dieser Eintrag enthält keine einzige Frage.
Simon Shine

Antworten:

4

Für Ihre erste Frage, wie Carl erwähnt, zoomvon lensgenau das tut , was Sie wollen. Ihr Code mit Objektiven könnte folgendermaßen geschrieben werden:

{-# 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: Während wir gerade dabei sind, wenn Sie sich bereits dafür lensdann innerStateFookann wie so geschrieben werden:

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

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.

Ich denke, dass das, was Sie erreichen wollen, nicht viel Maschinen benötigt.

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)

Dies StreamTransformerist 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).

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
quelle
Das ist sehr cool, danke! Wird dieses Muster etwas genannt?
Agnishom Chattopadhyay
3
Ich würde es einfach reine funktionale Programmierung nennen! Aber ich weiß, dass das nicht die Antwort ist, nach der Sie suchen :) StreamTransformer ist in der Tat eine " mehlige
Alexander Vieth
Nein, das erste Verschwinden der Ausgabe ist nicht das, was ich beabsichtigt habe. Ich möchte die erste Ausgabe auf die zweite verzögern .
Agnishom Chattopadhyay
2
Und so weiter, damit jeder Ausgang um einen Schritt verzögert wird? Das kann man machen.
Alexander Vieth
1
sehr schön, danke fürs posten! (Entschuldigung für das vorherige Kommentieren, ohne das Q richtig zu lesen). Ich denke, das OP bedeutete 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