2012-03-23 12 views
6

clojureまたはfpで元に戻す/やり直しの機能を実装するための確立されたパターンはありますか?clojureでの元に戻す/やり直しを実装するためのパターン

OO言語では私はコマンドパターンを使用しますが、それはすべての状態についてですが、それはクロージャーでそれを行うことは慣用的なのだろうかと思います。

ライブラリがありますか?

+0

先の質問は、最初にこの状態の変異が実際に必要かどうかです。 –

+0

@Alex Taggart:もちろん、あなたは本当にしません(しかし私はあなたの要点でした)。私は、Javaで不変オブジェクトのみを使って取り消し/やり直しを書いています。保存する(ユーザー入力)だけでアンドゥ/リドゥを書くことができますし、希望の時間まで入力を再生して "状態"を作り直すこともできます。だから、 "t5からt4"を取り消したいときは、t5からt4まで "巻き戻し"ませんが、t0からt4までの入力を再生します(そして、あなたは "機能的な方法"でそれをやっているので、正しい状態になることが保証されています)。多くのケースで動作し、取り消し/やり直しの実装を大幅に簡素化します。 – TacticalCoder

答えて

5

多くのデザインパターンと同様に、これをクロージャーの関数として実装できます。それはあなたのプログラム(refs、atom、agents)の状態をどのように表現するかはプロセスによって非常に似ています。

単純にウォッチャー機能を状態に追加する agent/ref/atomは、更新があるたびに状態を元に戻すリストに追加します。あなたの元に戻す機能は、元に戻すリストでそれを見ているだけです。これにより、元に戻すリストに追加してやり直しができるという効果があります。

私の最初の印象はrefです。これは正しい方法です。すべてを調整して復元することができるからですもちろん、プログラムの状態を単一のアイデンティティー(Clojureの意味で)に減らすことができない限り、コーディネートされた更新は必要なく、エージェントが機能します。

+0

ありがとうございます。それは良い解決策のように思えます。しかし、私はこの権利を得るかどうかを確認する:私は私の状態を表す3つの参照があります。私はそれぞれにadd-watchを呼び出します。ウォッチャーは、トランザクションの一部または全部がトランザクション内で変更されると、それらのすべてのスナップショットを取得してスタックに格納します。元に戻す機能は、新しいトランザクションを開き、すべてのREFで最後のスナップショットの状態を復元します。 – nansen

+0

はい。 3つのウォッチャーが同じ状態をスタックに3回追加しないようにする必要があると思います。 –

+1

が正しい。私は、すべての状態情報を1つの原子に集約することもできます。これにより、トランジションがずっと簡単になります。 – nansen

1

[OK]を私はアーサーUlfeldtが提案のようにそれが仕事作っ:

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

しかし、私はまだ非常に理解していないことは理由です。以来、元に戻す!やり直し!ウォッチャーがそれに反応してアンドゥ値を元に戻してコマンドスタックを壊してはならないはずです。

+0

元に戻すことの取り消しの問題には考えが必要です。 2つの連続した取り消しを元に戻してからやり直したいとしますか? –

+0

この回答は元の質問の編集としてよく表現されます。 –

+0

@Arthur:最初の質問:いいえ、私はもちろんそのような行動を期待しません。それは私が意味するものではありませんでした。私は、あなたが上記のコードが間違っている(あなたのように)と期待していたが、実際は正しく動作することを意味した。少なくとも単体テストによると;-)。あなたの2番目のコメント:私はあまりにも最初に考えていましたが、そのポストは実際に私のオリジナルの質問に対する答えであり、もう1つは別の質問を提起するものです。 – nansen

関連する問題