Stany zagnieżdżone w Haskell

9

Próbuję zdefiniować rodzinę maszyn stanowych z nieco odmiennymi rodzajami stanów. W szczególności bardziej „złożone” maszyny stanów mają stany, które powstają przez połączenie stanów prostszych maszyn stanów.

(Jest to podobne do ustawienia obiektowego, w którym obiekt ma kilka atrybutów, które również są obiektami.)

Oto uproszczony przykład tego, co chcę osiągnąć.

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

Mówiąc bardziej ogólnie, chcę uogólnionego frameworka, w którym zagnieżdżenia te są bardziej złożone. Oto coś, co chcę wiedzieć, jak to zrobić.

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

W kontekście chcę to osiągnąć dzięki tej maszynie:

Chcę zaprojektować te rzeczy o nazwie „Transformatory strumieniowe”, które są w zasadzie funkcjami stanowymi: zużywają token, mutują swój stan wewnętrzny i wysyłają coś. W szczególności interesuje mnie klasa transformatorów strumieniowych, w której dane wyjściowe są wartością logiczną; nazywamy te „monitorami”.

Teraz próbuję zaprojektować kombinatory dla tych obiektów. Niektórzy z nich są:

  • preSyntezatora. Załóżmy, że monto monitor. Następnie pre monjest monitorem, który zawsze produkuje Falsepo zużyciu pierwszego tokena, a następnie naśladuje zachowanie montak, jakby poprzedni token był wstawiany teraz. Chciałbym modelować stan pre monz StateWithTriggerw powyższym przykładzie, ponieważ nowy stan jest wartością logiczną wraz ze stanem oryginalnym.
  • andSyntezatora. Załóżmy, że m1i m2są monitory. Następnie m1 `and` m2jest monitorem, który podaje token do m1, a następnie do m2, a następnie generuje, Truejeśli obie odpowiedzi są prawdziwe. Chciałbym modelować stan m1 `and` m2z CombinedStatew powyższym przykładzie, ponieważ stan obu monitorów musi być utrzymany.
Agnishom Chattopadhyay
źródło
FYI, _innerVal <$> getjest po prostu gets _innerVal( gets f == liftM f geti specjalizuje się liftMtylko fmapw monadach).
chepner,
Skąd czerpiesz StateT InnerState m Intwartość outerStateFoo?
chepner,
6
Czy czujesz się dobrze z obiektywem? Ten przypadek użycia wydaje się być dokładnie tym, do czego zoomsłuży.
Carl
1
@Carl Widziałem niektóre soczewki, ale nie rozumiem ich zbyt dobrze. Może możesz w odpowiedzi wyjaśnić, jak używać zoomu?
Agnishom Chattopadhyay
5
Uwaga: ten wpis nie zawiera ani jednego pytania.
Simon Shine

Odpowiedzi:

4

Na swoim pierwszym pytaniu, jak Carl wspomniano, zoomze lensrobi dokładnie to, co chcesz. Twój kod z soczewkami można zapisać w następujący sposób:

{-# 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: Skoro już jesteśmy przy nim, jeśli jesteś już przynosząc lensnastępnie innerStateFoomożna zapisać tak:

innerStateFoo :: Monad m => StateT InnerState m Int
innerStateFoo = innerVal <<+= 1
Jan
źródło
5

W kontekście chcę to osiągnąć dzięki tej maszynie:

Chcę zaprojektować te rzeczy o nazwie „Transformatory strumieniowe”, które są w zasadzie funkcjami stanowymi: zużywają token, mutują swój stan wewnętrzny i wysyłają coś. W szczególności interesuje mnie klasa transformatorów strumieniowych, w której dane wyjściowe są wartością logiczną; nazywamy te „monitorami”.

Myślę, że to, co chcesz osiągnąć, nie wymaga wiele maszyn.

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)

To StreamTransformernie jest koniecznie stanowe, ale przyznaje te stanowe. Nie musisz (a IMO nie powinno! W większości przypadków !!) sięgać po typy, aby je zdefiniować (a nawet kiedykolwiek! :), ale to już inny temat).

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
źródło
To bardzo fajne, dzięki! Czy ten wzór nazywa się czymś?
Agnishom Chattopadhyay
3
Nazwałbym to po prostu programowaniem funkcjonalnym! Ale wiem, że nie jest to odpowiedź, której szukasz :) StreamTransformer to w rzeczywistości hackage „Mealy machine”. Haskell.org/package/machines-0.7/docs/…
Alexander Vieth
Nie, zniknięcie pierwszego wyjścia nie jest tym, co zamierzałem. Chciałbym opóźnić pierwsze wyjście jako drugie.
Agnishom Chattopadhyay
2
I tak dalej, aby każde wyjście było opóźnione o jeden krok? Można to zrobić.
Alexander Vieth,
1
bardzo miło, dziękuję za wysłanie wiadomości! (przepraszam, że wcześniej komentowałem bez falowania, przeczytaj poprawnie Q). Myślę, że OP miał na myśli 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')).
Czy Ness