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:
- Inserisci un carattere sul cursore (così tutti i caratteri sotto e alla destra del cursore, se ce ne sono, vengono spostati a destra).
- Sposta il cursore a sinistra (nessun effetto se è già in posizione zero)
- 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.