2012-03-23 8 views
6

Esiste un modello stabilito per implementare la funzionalità di annullamento/ripristino in clojure o in fp in generale?per l'implementazione di undo/redo in clojure

In un linguaggio OO vorrei seguire lo schema di comando ma poiché si tratta di stato mi chiedo se sia idiomatico farlo in clojure.

Esistono librerie che potrebbero essere d'aiuto?

+0

Una domanda precedente dovrebbe essere se effettivamente avete bisogno di questa mutazione di stato in primo luogo. –

+0

@Alex Taggart: e ovviamente non lo fai (ma ho capito che era il tuo punto;) Ho scritto annulla/ripristina usando solo oggetti immutabili (in Java). Puoi scrivere un annulla/ripeti salvando solo (input dell'utente) e ricreando il tuo "stato" ripetendo i tuoi input fino al momento desiderato. Quindi, quando si desidera annullare da "t5 a t4" non si "riavvolge" da t5 a t4, ma si riproducono gli input da t0 a t4 (e poiché lo si fa in un "modo funzionale", si è garantito per finire con lo stato corretto). Funziona in molti casi e semplifica enormemente l'implementazione di undo/redo IMHO ... – TacticalCoder

risposta

5

Come con molti modelli di progettazione è possibile implementare questo come funzione in clojure. Dipende un po 'da come rappresenti lo stato nel tuo programma (ref, atomi, agenti) attraverso il processo è molto simile.

È sufficiente aggiungere una funzione watcher al proprio stato agent/ref/atom che aggiunge lo stato all'elenco di annullamenti ogni volta che è presente un aggiornamento. allora la tua funzione di annullamento è solo nella lista degli annullamenti. Questo ha il buon effetto di aggiungere il tuo a alla lista di annullamento, consentendo anche di ripetere

La mia prima impressione è che ref s potrebbe essere lo strumento corretto per questo perché sarai in grado di ripristinarli tutti in modo coordinato , a meno che, naturalmente, non sia possibile ridurre lo stato dei programmi a una singola identità (nel senso Clojure della parola), non sarebbe necessario un aggiornamento coordinato e un agente funzionerebbe.

+0

Grazie. Sembra una buona soluzione. Ma per vedere se ho capito bene: ho 3 referenze che rappresentano il mio stato. chiamo add-watch su ognuno di essi. Quando alcuni o tutti vengono modificati in una transazione, l'osservatore cattura un'istantanea di tutti e li mette in pila. La funzione di annullamento aprirà una nuova transazione e ripristinerà l'ultimo stato di istantanea in tutti i miei riferimenti. – nansen

+0

Sì. Suppongo che qualcosa debba assicurare che i tre osservatori non aggiungano lo stesso stato allo stack anche tre volte. –

+1

destra. Potrei anche vivere con l'aggregazione di tutte le informazioni di stato in un singolo atomo. Questo renderebbe le transizioni molto più facili. – nansen

1

Ok ho fatto funzionare come Arthur Ulfeldt suggerito:

(defn cmd-stack [state-ref] 
    (let [stack (atom ['() '()])] 
    (add-watch state-ref :cmdhistory 
      (fn [key ref old new] 
      (let [redo-stack '() 
        undo-stack (conj (second @stack) old)] 
      (reset! stack [redo-stack undo-stack])))) 
    stack)) 

(defn can-redo? [stack] 
    (not (empty? (first @stack)))) 

(defn can-undo? [stack] 
    (not (empty? (second @stack)))) 

(defn undo! [stack state-ref] 
    (let [redo-stack (first @stack) 
     undo-stack (second @stack) 
     current-state @state-ref 
     last-state (first undo-stack)] 
    (assert (can-undo? stack) "cannot undo") 
    (reset! state-ref last-state) 
    (reset! stack [(conj redo-stack current-state) (drop 1 undo-stack)]))) 

(defn redo! [stack state-ref] 
    (let [redo-stack (first @stack) 
     undo-stack (second @stack) 
     current-state @state-ref 
     last-state (first redo-stack)] 
    (assert (can-redo? stack) "cannot redo") 
    (reset! state-ref last-state) 
    (reset! stack [(drop 1 redo-stack) (conj undo-stack current-state)]))) 

Ma quello che ancora non capisco è il motivo. Dal momento che l'annullamento! e rifare! le funzioni aggiornano l'atomo che si sta osservando, l'osservatore non dovrebbe reagire a ciò e quindi incasinare lo stack dei comandi rimettendo il valore annullato su di esso?

+0

La questione di annullare un annullamento richiede di pensare. Vuoi che due annullamenti consecutivi siano equivalenti per annullare e poi ripetere? –

+0

questa risposta può essere meglio rappresentata come una modifica alla domanda originale. –

+0

@Arthur: prima domanda: no, ovviamente non mi aspetto un simile comportamento. Non era quello che intendevo. Intendevo dire che mi sarei aspettato che il codice sopra fosse sbagliato (come descrivi tu) ma in realtà si comporta correttamente. Almeno secondo i test unitari ;-). Il tuo secondo commento: stavo pensando anche a questo, ma poi ho scoperto che il post è in realtà una risposta alla mia domanda iniziale, solo uno che solleva un'altra domanda. – nansen