Stati nidificati a Haskell


9

Sto cercando di definire una famiglia di macchine a stati con tipi di stati alquanto diversi. In particolare, le macchine a stati più "complesse" hanno stati che si formano combinando gli stati di macchine a stati più semplici.

(È simile a un'impostazione orientata agli oggetti in cui un oggetto ha diversi attributi che sono anche oggetti.)

Ecco un esempio semplificato di ciò che voglio ottenere.

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

Più in generale, desidero un framework generalizzato in cui questi nidificazione siano più complessi. Ecco qualcosa che vorrei sapere come fare.

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

Per il contesto, questo è ciò che voglio ottenere con questo macchinario:

Voglio progettare queste cose chiamate "Transformers Stream", che sono sostanzialmente funzioni stateful: consumano un token, mutano il loro stato interno e producono qualcosa. In particolare, sono interessato a una classe di trasformatori di flusso in cui l'output è un valore booleano; chiameremo questi "monitor".

Ora sto cercando di progettare combinatori per questi oggetti. Alcuni di loro sono:

  • Un precombinatore. Supponiamo che monsia un monitor. Quindi, pre monè un monitor che produce sempre Falsedopo che il primo token è stato consumato e quindi imita il comportamento di moncome se il token precedente fosse stato inserito ora. Vorrei modellare lo stato di pre monwith StateWithTriggernell'esempio sopra dato che il nuovo stato è un valore booleano insieme allo stato originale.
  • Un andcombinatore. Supponiamo che m1e m2siano monitor. Quindi, m1 `and` m2è un monitor che invia il token a m1, quindi a m2 e quindi produce Truese entrambe le risposte fossero vere. Vorrei modellare lo stato di m1 `and` m2with CombinedStatenell'esempio sopra dato che lo stato di entrambi i monitor deve essere mantenuto.

Cordiali saluti, _innerVal <$> getè solo gets _innerVal(come gets f == liftM f get, ed liftMè solo fmapspecializzato in monadi).
Chepner,

Dove stai ottenendo un StateT InnerState m Intvalore in primo luogo in outerStateFoo?
Chepner,

6
Sei a tuo agio con l'obiettivo? Questo caso d'uso sembra essere esattamente ciò che zoomserve.
Carl,

1
@Carl Ho visto degli obiettivi ma non li capisco molto bene. Forse puoi spiegare in una risposta come usare lo zoom?
Agnishom Chattopadhyay,

5
Un'osservazione: questa voce non contiene una sola domanda.
Simon Shine,

Risposte:


4

Per la tua prima domanda, come menzionato zoomda Carl, lensfa esattamente quello che vuoi. Il tuo codice con gli obiettivi potrebbe essere scritto in questo modo:

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

Modifica: mentre ci siamo, se lo stai già facendo, lensallora innerStateFoopuò essere scritto in questo modo:

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

5

Per il contesto, questo è ciò che voglio ottenere con questo macchinario:

Voglio progettare queste cose chiamate "Transformers Stream", che sono sostanzialmente funzioni stateful: consumano un token, mutano il loro stato interno e producono qualcosa. In particolare, sono interessato a una classe di trasformatori di flusso in cui l'output è un valore booleano; chiameremo questi "monitor".

Penso che ciò che vuoi ottenere non abbia bisogno di molti macchinari.

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)

Questo StreamTransformernon è necessariamente stato, ma ammette quelli con stato. Non è necessario (e IMO non dovrebbe! Nella maggior parte dei casi !!) cercare le typeclass per definire queste (o addirittura mai! :) ma questo è un altro argomento).

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)

È fantastico, grazie! Questo schema si chiama qualcosa?
Agnishom Chattopadhyay,

3
La chiamerei pura programmazione funzionale! Ma so che non è la risposta che stai cercando :) StreamTransformer è in realtà un hackage.haskell.org/package/machines-0.7/docs/…
Alexander

No, il primo output in via di estinzione non è quello che intendevo. Vorrei ritardare il primo output ad essere il secondo.
Agnishom Chattopadhyay,

2
E così via in modo che ogni uscita sia ritardata di un passo? Questo può essere fatto.
Alexander Vieth,

1
molto bello, grazie per la pubblicazione! (scusate per aver commentato in precedenza senza sollevare leggere la Q correttamente). Penso che l'OP intendesse 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,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.