8

以下は、説明のための非常に単純化された例です。

カウンターにアトムを使用するなど、実装の詳細をカプセル化できます。

(defn make-counter
  ([] (make-counter 0))
  ([init-val]
   (let [c (atom init-val)]
     {:get (fn [] @c)
      :++ (fn [] (swap! c inc))})))

しかし、それは機能を追加するためにすべてを再定義する必要があることを意味します (継承なし):

(defn make-bi-counter
  ([] (make-bi-counter 0))
  ([init-val]
   (let [c (atom init-val)]
     {:get (fn [] @c)
      :++ (fn [] (swap! c inc))
      :-- (fn [] (swap! c dec))})))

一方、1 つの関数だけを拡張することが可能であれば、次のようになります。

(assoc c :-- (env (:++ c) (fn [] (swap! c dec))))

(def c (make-counter))
(def b (make-bi-counter))
user=> ((:-- b))
-1
user=> ((:-- b))
-2
user=> ((:get b))
-2

または、アトムを公開して、独立した機能を持たせることもできました。

(defn -- [a] (swap! a dec))
(def a (atom 0))
(-- a)

「継承」(または、より正確には拡張) が必要な場合は、カプセル化を放棄することが最善の選択肢のようです。

4

2 に答える 2

16

はい、慣用的なClojureは、データを関数から分離することだと思います。正確には、後で新しい関数を記述して古いデータを操作できるからです。

関数をデータにバンドルすると、データ構造をすべて変更または再生成しないと後で関数を変更できないことも意味します。REPL でインタラクティブに開発しているので、関数を変更するたびにすべてのデータ構造を探し出して修正する必要はありません。ハッシュマップのクロージャーは賢いですが、かなり壊れやすいので、本当に正当な理由がない限り、その方法には行きません。

インターフェースを (関数として) 定義するのに多少の規律が必要なだけで、インターフェースに固執し、atom を直接いじらないようにすることを忘れないでください。物事を自分自身から強制的に隠すことで、どのようなメリットが得られるかは不明です。

継承が必要な場合は、マルチメソッドが適しています。

(defmulti getc type)
(defmulti ++ type)
(defmulti -- type)

(derive ::bi-counter ::counter)

(defn make-counter
  ([] (make-counter 0))
  ([init-val]
     (atom init-val :meta {:type ::counter})))

(defn make-bi-counter
  ([] (make-bi-counter 0))
  ([init-val]
     (atom init-val :meta {:type ::bi-counter})))

(defmethod getc ::counter [counter]
  @counter)

(defmethod ++ ::counter [counter]
  (swap! counter inc))

(defmethod -- ::bi-counter[counter]
  (swap! counter dec))

例えば

user> (def c (make-counter))
#'user/c
user> (getc c)
0
user> (def b (make-bi-counter))
#'user/b
user> (++ c)
1
user> (++ b)
1
user> (-- b)
0
user> (-- c)
; Evaluation aborted.
;;  No method in multimethod '--' for dispatch value: :user/counter
于 2009-10-05T03:06:16.137 に答える
0

慣用的なClojureではないと確信していますが、保護された変数を確実にシミュレートできます。

あなたの例では、 cはシミュレートされたプライベート変数です。これを保護変数にしたい場合は、make-countermake-bi-counter の両方がアクセスできるように定義する必要があります。secretというハッシュをmake-counterに渡します。secretcが含まれている場合は、それを使用します。それ以外の場合は、独自に作成してください。

その後、make-bi-counterはcを含む秘密のオブジェクトを作成し、それをmake-counterコンストラクターに渡すことができます。これで、 make-bi-countermake-counterの両方が同じcにアクセスできるようになりました。

于 2010-11-21T16:38:53.137 に答える