2012-03-23 14 views
6

¿Existe un patrón establecido para implementar la funcionalidad de deshacer/rehacer en clojure o en fp en general?patrón para implementar deshacer/rehacer en clojure

En un lenguaje OO me gustaría ir con el patrón de comando, pero como todo es cuestión de estado, me pregunto si es idiomático hacerlo en clojure.

¿Hay bibliotecas que podrían ser de ayuda?

+0

Una pregunta anterior debería ser si realmente necesita o no esta mutación de estado en primer lugar. –

+0

@Alex Taggart: y por supuesto que realmente no (pero considero que ese fue su punto;) He escrito deshacer/rehacer utilizando solo objetos inmutables (en Java). Puede escribir un deshacer/rehacer guardando solo (entradas de usuario) y recreando su "estado" repitiendo sus entradas hasta el tiempo deseado. Entonces, cuando desee deshacer desde "t5 a t4", no "rebobinará" de t5 a t4, pero reproducirá las entradas de t0 a t4 (y porque lo está haciendo de una "manera funcional", está garantizado para terminar con el estado correcto). Funciona en muchos casos y simplifica enormemente la implementación de deshacer/rehacer en mi humilde opinión ... – TacticalCoder

Respuesta

5

Al igual que con muchos patrones de diseño, puede implementar este como una función en clojure. Depende un poco de cómo representas el estado en tu programa (refs, átomos, agentes) a través del proceso es muy similar.

Usted simplemente agrega una función de observador a su estado agent/ref/atom que agrega el estado a la lista de deshacer cada vez que hay una actualización. entonces su función deshacer es solo buscarla en la lista de deshacer. Esto tiene el bonito efecto de agregar tu a la lista de deshacer, permitiendo rehacer también

Mi primera impresión es que ref s puede ser la herramienta correcta para esto porque podrás restaurarlos todos de forma coordinada , a menos que, por supuesto, pueda reducir el estado de sus programas a una única identidad (en el sentido Clojure de la palabra), entonces no necesitaría una actualización coordinada y un agente funcionaría.

+0

Gracias. Eso suena como una buena solución. Pero para ver si lo entiendo bien: tengo 3 refs que representan mi estado. llamo add-watch en cada uno de ellos. Cuando algunos o todos cambian en una transacción, el observador toma una instantánea de todos ellos y los coloca en una pila. La función deshacer abriría una nueva transacción y restauraría el último estado de instantánea en todas mis referencias. – nansen

+0

Sí. Supongo que algo necesita asegurarse de que los tres observadores no agreguen el mismo estado a la pila tres veces también. –

+1

derecha. Podría vivir agregando toda la información de estado en un solo átomo. Esto haría las transiciones mucho más fáciles. – nansen

1

Ok hice que funcione como Arthur Ulfeldt sugirió:

(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)]))) 

Pero lo que todavía no entiendo es por qué. Desde el deshacer! y rehacer! las funciones actualizan el átomo que se está observando, ¿no debería el observador reaccionar ante eso y así arruinar la pila de comandos volviendo a poner el valor desaprovechado?

+0

La cuestión de deshacer un deshacer requiere pensar. ¿Desea que dos pasos consecutivos sean equivalentes a deshacer y luego rehacer? –

+0

esta respuesta se puede representar mejor como una edición de la pregunta original. –

+0

@Arthur: primera pregunta: no, por supuesto que no esperaría tal comportamiento. Eso no fue lo que quise decir. Quería decir que esperaba que el código anterior fuera incorrecto (como usted describe) pero en realidad se comporta correctamente. Al menos según las pruebas unitarias ;-). Su segundo comentario: estaba pensando eso también primero, pero luego descubrí que la publicación es en realidad una respuesta a mi pregunta original, solo una que plantea otra pregunta. – nansen

Cuestiones relacionadas