2012-03-23 10 views
6

Czy istnieje ustalony wzorzec do implementacji funkcji cofania/przywracania w clojure lub ogólnie w fp?wzorzec implementacji cofnij/ponów w clojure

W języku OO chciałbym iść z wzorcem polecenia, ale jak to jest wszystko o stanie zastanawiam się, czy jest to idiomatyczne robienie tego w clojure.

Czy są jakieś biblioteki, które mogą być pomocne?

+0

Poprzednim pytaniem powinno być, czy faktycznie potrzebujesz tej mutacji stanu w pierwszej kolejności. –

+0

@Alex Taggart: i oczywiście naprawdę nie (ale rozumiem, że to był twój punkt;) Napisałem cofanie/ponawianie używając tylko niezmiennych obiektów (w Javie). Możesz napisać cofnięcie/ponowienie tylko poprzez zapisanie (dane użytkownika) i ponowne odtworzenie "stanu" poprzez ponowne odtworzenie danych wejściowych do żądanego czasu. Więc jeśli chcesz cofnąć od "t5 do t4", nie "zwijasz" od t5 do t4, ale odtwarzasz wejścia od t0 do t4 (i ponieważ robisz to w "funkcjonalny sposób", jesteś gwarantowane, że skończy się prawidłowym stanem). Działa w wielu przypadkach i znacznie upraszcza wdrażanie undo/redo IMHO ... – TacticalCoder

Odpowiedz

5

Podobnie jak w przypadku wielu wzorców projektowych, można go zaimplementować jako funkcję w clojure. To zależy w niewielkim stopniu od tego, jak reprezentujesz stan w swoim programie (refs, atomy, agenci) w procesie jest bardzo podobny.

Po prostu chcesz dodać funkcję obserwatora do swojego stanu agent/ref/atom, który dodaje stan do listy cofania za każdym razem, gdy pojawia się aktualizacja. wtedy twoja funkcja cofania po prostu wyszukuje je na liście cofania. Ma to dobry efekt dodania twojego do listy cofania, pozwalając na ponowienie również

Moje pierwsze wrażenie jest takie, że ref s może być właściwym narzędziem do tego, ponieważ będziesz mógł przywrócić je wszystkie w skoordynowany sposób , chyba że możesz ograniczyć swoje programy do jednej tożsamości (w sensie Clojure), to nie potrzebujesz skoordynowanej aktualizacji, a agent będzie działał.

+0

Dzięki. To brzmi jak dobre rozwiązanie. Ale żeby sprawdzić, czy mam rację: mam 3 referencje reprezentujące mój stan. Na każdym z nich dzwonię do add-watch. Gdy niektóre lub wszystkie z nich zostaną zmienione w transakcji, obserwator robi migawkę wszystkich z nich i umieszcza ją na stosie. Funkcja cofania otwiera nową transakcję i przywraca ostatni stan migawki we wszystkich moich zmianach. – nansen

+0

Tak. Przypuszczam, że coś musi zapewnić, że trzej obserwatorzy nie dodadzą tego samego stanu do stosu trzy razy. –

+1

w prawo. Równie dobrze mógłbym żyć z agregowaniem wszystkich informacji o stanie w jeden pojedynczy atom. Dzięki temu przejścia stałyby się o wiele łatwiejsze. – nansen

1

Ok zrobiłem to działa jak Arthur Ulfeldt zasugerował:

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

Ale co ja nadal nie bardzo rozumiem dlaczego. Od cofnięcia! i ponów! funkcje aktualizują obserwowany atom, czy obserwator nie powinien zareagować na to, a tym samym zepsuć stos poleceń przez przywrócenie cofniętej wartości?

+0

Pytanie o cofnięcie cofnięcia wymaga myślenia. Czy chcesz, aby dwa kolejne cofnięcia były równoważne cofnięciu, a następnie powtórzeniu? –

+0

tę odpowiedź można lepiej przedstawić jako odpowiedź na oryginalne pytanie. –

+0

@Arthur: pierwsze pytanie: nie, nie spodziewam się takiego zachowania. Nie o to mi chodziło. Chodziło mi o to, że spodziewałbym się, że powyższy kod będzie błędny (tak jak opisujesz), ale zachowuje się właściwie. Przynajmniej według testów jednostkowych ;-). Twój drugi komentarz: Myślałem o tym za wcześnie, ale potem odkryłem, że post jest właściwie odpowiedzią na moje pierwotne pytanie, tylko tym, który rodzi kolejne pytanie. – nansen

Powiązane problemy