L'uso di DSL in uno stato monade un buon approccio alla costruzione di complessi calcoli stateful?

6

In primo luogo, mi dispiace se quel titolo non ha senso. Sono un po 'fuori dalla mia profondità qui con la terminologia.

Quindi immagina che sto scrivendo un editor di testo in Haskell. Ai fini di questa domanda, consideriamo un editor di testo giocattolo che funziona solo su una singola riga di testo e ha tre operazioni primitive:

  1. Inserisci un carattere sul cursore (così tutti i caratteri sotto e alla destra del cursore, se ce ne sono, vengono spostati a destra).
  2. Sposta il cursore a sinistra (nessun effetto se è già in posizione zero)
  3. Sposta il cursore a destra (nessun effetto se è già alla fine della riga)

Ovviamente ho a che fare con uno stato (il "buffer" del mio editor di testo e la posizione del suo cursore) e alcune operazioni che possono essere applicate in alcune sequenze per modificare lo stato. Quindi, implementerò questa cosa usando una monade di stato che contiene due elenchi, uno per i caratteri dietro il cursore e l'altro per quelli dietro di esso. Scriverò tre semplici funzioni nella monade di stato per fornire le mie tre operazioni primitive:

data ListState a = ListState {
                     before :: [a],
                     after :: [a]
                   }

insert :: MonadState (ListState a) m => a -> m ()
insert x = do
  ListState bs as <- get
  put $ ListState bs (x:as)

moveForward :: MonadState (ListState a) m => m ()
moveForward = do
  ListState bs as <- get
  case as of
    (a:as') -> put $ ListState (a:bs) as'
    _       -> return ()

moveBackward :: MonadState (ListState a) m => m ()
moveBackward = do
  ListState bs as <- get
  case bs of
    (b:bs') -> put $ ListState bs' (b:as)
    _       -> return ()

Cool! Ora posso usare questi tre primitivi per fare cose più complicate:

insertList :: MonadState (ListState a) m => [a] -> m ()
insertList xs = forM_ (reverse xs) insert

foo :: MonadState (ListState Char) m => m ()
foo = do
  insertList "This is a idea!"
  replicateM_ 10 moveForward
  insertList "bad "
  replicateM_ 3 moveBackward
  insertList "n't"

main = let state = ListState "" ""
           state' = execState foo state
       in  putStrLn $ reverse (before state') ++ after state'

Usando il mio DSL primitivo, ho definito una funzione utilizzando ripetute chiamate a insert per inserire un'intera lista in una volta, e un'altra funzione foo che combina tutte queste funzioni per costruire una stringa in modo tortuoso:

(mdunsmuir@altair) mdunsmuir/projects >> ./ToyDSL 
This isn't a bad idea!

Mi trovo a seguire molto questo modello: definire alcuni tipi di dati per contenere uno stato a cui voglio applicare una sequenza di operazioni, quindi creare un DSL che inizia con le operazioni atomiche più basilari e con operazioni più complesse definite come combinazioni di questi primitivi.

La mia domanda è piuttosto aperta: per questo tipo di problema (immagina un editor di testo più realistico implementato nello stesso modo), questo è un approccio generalmente valido? Quali altri approcci si potrebbe usare? La cosa principale che trovo riguardo a questo approccio è che se non segui un approccio disciplinato alla progettazione della tua DSL, puoi imbatterti in molti spiacevoli problemi che io associo alla programmazione imperativa.

    
posta mdunsmuir 20.01.2015 - 09:38
fonte

1 risposta

5

Anche se questa può essere una buona idea a un livello, se vuoi puoi evitare di far passare monadi dappertutto. È più facile leggere, scrivere, comporre e riutilizzare se scrivi normalmente le tue funzioni, quindi modifica in un contesto monadico in base alle esigenze:

insert x (Buffer before after) = Buffer (x:before) after

insertList xs (Buffer before after) = Buffer (reverse xs ++ before) after

moveForward (Buffer before (x:after)) = Buffer (x:before) after

moveBackward (Buffer (x:before) after) = Buffer before (x:after)

modify2 f a = do {x <- get; put (f a x) } 

insertM = modify2 insert
insertListM = modify2 insertList
moveForwardM = modify moveForward
moveBackwardM = modify moveBackward
    
risposta data 20.01.2015 - 22:44
fonte

Leggi altre domande sui tag