以下は、説明のための非常に単純化された例です。
カウンターにアトムを使用するなど、実装の詳細をカプセル化できます。
(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)
「継承」(または、より正確には拡張) が必要な場合は、カプセル化を放棄することが最善の選択肢のようです。