Clojure 用のCLOS (Common Lisp Object System) のようなものはありますか?
7 に答える
次の 2 つの理由から、Clojure 自体にはオブジェクト システムがありません。
- Clojure は、オブジェクト指向プラットフォームでホストされるように特別に設計されており、基礎となるプラットフォームのオブジェクト システムを単純に吸収します。つまり、ClojureJVM には JVM オブジェクト システムがあり、ClojureCLR には CLI オブジェクト システムがあり、ClojureScript には ECMAScript オブジェクト システムがあります。
- Rich Hickey はオブジェクトが嫌いです。
ただし、Clojure でオブジェクト システムを実装できることは明らかです。結局のところ、Clojure はチューリング完全です。
Michael Evinsは、彼がCategoriesと呼ぶ OO への新しいアプローチに取り組んでいます。彼は、Clojure を含むいくつかの Lisp の実装を持っています (ただし、すべてのポートが常に最新であることが保証されているわけではありません)。
Categories は、 Mikelが設計している新しい Lisp 方言であるBardにゆっくりと組み込まれています。これには、Categories が組み込まれています。 )
Clojure には CLOS がなく、CLOS を必要としませんが、実装することはできます。
Clojure は不変であることを望んでいるので、可変 OO を持つことはちょっとばかげていますが、一種の OO を持つことはできます。
- http://clojure.org/datatypes (defrecord を見てください --> 最高のクラスとハッシュマップ)
- http://clojure.org/protocols (インターフェイスに似ていますが、より優れています)
- http://clojure.org/multimethods (独自のディスパッチ関数を作成できるため強力です)
これら 3 つのことで、すべてのニーズを満たすことができるはずですが、ほとんどの場合、通常の関数と標準のデータ構造を使用するのが最善です。
OO パラダイムの使用は、疎結合コードの記述、モック化、およびテストに最適です。Clojure を使用すると、これを簡単に実現できます。
過去に遭遇した問題の 1 つは、他のコードに依存するコードでした。Clojure 名前空間は、うまく使用しないと実際に問題を悪化させます。理想的には、名前空間はモックアウトできますが、私が見つけたように...名前空間のモックアウトには多くの問題があります:
https://groups.google.com/forum/?fromgroups=#!topic/clojure/q3PazKoRlKU
ますます大きなアプリケーションの構築を開始すると、名前空間が相互に依存し始め、多数の依存関係を持たずに上位レベルのコンポーネントを個別にテストすることは非常に困難になります。ほとんどのソリューションには、関数の再バインドやその他の黒魔術が含まれますが、問題は、テスト時に元の依存関係がまだロードされていることです。これは、大きなアプリケーションがある場合、大きな問題になります。
データベース ライブラリを使用した後、代替手段を探すようになりました。データベース ライブラリは私に大きな苦痛をもたらしました。それらはロードに時間がかかり、通常はアプリケーションの中核にあります。データベース全体、ライブラリ、および関連する周辺機器をテスト コードに取り込まずにアプリケーションをテストすることは非常に困難です。
データベース コードに依存するシステムの部分を「スワップ アウト」できるように、ファイルをパッケージ化できるようにしたいと考えています。オブジェクト指向の設計方法論が答えを提供します。
答えがかなり長くなってしまい申し訳ありません...オブジェクト指向設計がどのように使用されるかよりも、なぜ使用されるのかについて、適切な根拠を示したかったのです。したがって、実際の例を使用する必要がありました。サンプル アプリケーションの構造ができるだけ明確に保たれるように、宣言を保持しようとしました。ns
既存の clojure スタイルのコード
この例carmine
では、redis クライアントである を使用します。korma や datomic に比べて比較的使いやすく、起動も速いですが、データベース ライブラリはデータベース ライブラリのままです。
(ns redis-ex.history
(:require [taoensso.carmine :as car]
[clojure.string :as st]))
(defmacro wcr [store kdir f & args]
`(car/with-conn (:pool ~store) (:conn ~store)
(~f (st/join "/" (concat [(:ns ~store)] ~kdir)) ~@args)))
(defn empty [store kdir]
(wcr store kdir car/del))
(defn add-instance [store kdir dt data]
(wcr store kdir car/zadd dt data))
(defn get-interval [store kdir dt0 dt1]
(wcr store kdir car/zrangebyscore dt0 dt1))
(defn get-last [store kdir number]
(wcr store kdir car/zrange (- number) -1))
(defn make-store [pool conn ns]
{:pool pool
:conn conn
:ns ns})
既存のテスト コード
すべての関数をテストする必要があります...これは新しいものではなく、標準の clojure コードです
(ns redis-ex.test-history0
(:require [taoensso.carmine :as car]
[redis-ex.history :as hist]))
(def store
(hist/make-store
(car/make-conn-pool)
(car/make-conn-spec)
"test"))
(hist/add-instance store ["hello"] 100 100) ;;=> 1
(hist/get-interval store ["hello"] 0 200) ;;=> [100]
オブジェクト指向ディスパッチメカニズム
「OO」は悪ではなく、実際には非常に有用であるという考えは、Misko Hevery 氏のこの講演を見て思いつきました。
http://www.youtube.com/watch?v=XcT4yYu_TTs
基本的な考え方は、大きなアプリケーションを構築したい場合、「機能」(プログラムの中身) を「配線」(インターフェースと依存関係) から分離する必要があるということです。依存関係が少ないほど良い。
私は clojure ハッシュマップを「オブジェクト」として使用します。これは、ライブラリの依存関係がなく、完全に汎用であるためです (Brian Marick が Ruby で同じパラダイムを使用することについて話しているのを参照してください - http://vimeo.com/34522837 )。
clojure コードを「オブジェクト指向」にするには、次の関数が必要です - ( send
smalltalk から盗み出された) これは、既存のキーに関連付けられている場合に、マップ内のキーに関連付けられた関数をディスパッチするだけです。
(defn call-if-not-nil [f & vs]
(if-not (nil? f) (apply f vs))
(defn send [obj kw & args]
(call-if-not-nil (obj kw) obj))
汎用ユーティリティ ライブラリ (hara.fn
名前空間の https://github.com/zcaudate/hara) で実装を提供します。自分で実装する場合は、4 行のコードです。
オブジェクト「コンストラクター」の定義
元の関数を変更make-store
して、マップに関数を追加できるようになりました。これで、あるレベルの間接化ができました。
;;; in the redis-ex.history namespace, make change `make-store`
;;; to add our tested function definitions as map values.
(defn make-store [pool conn ns]
{:pool pool
:conn conn
:ns ns
:empty empty
:add-instance add-instance
:get-interval get-interval
:get-last get-last})
;;; in a seperate test file, you can now test the 'OO' implementation
(ns redis-ex.test-history1
(:require [taoensso.carmine :as car]
[redis-ex.history :as hist]))
(def store
(hist/make-store
(car/make-conn-pool)
(car/make-conn-spec)
"test"))
(require '[hara.fn :as f])
(f/send store :empty ["test"])
;; => 1
(f/send store :get-instance ["test"] 100000)
;; => nil
(f/send store :add-instance ["test"]
{100000 {:timestamp 1000000 :data 23.4}
200000 {:timestamp 2000000 :data 33.4}
300000 {:timestamp 3000000 :data 43.4}
400000 {:timestamp 4000000 :data 53.4}
500000 {:timestamp 5000000 :data 63.4}})
;; => [1 1 1 1 1]
抽象化を構築する
したがって、関数は完全に自己完結型のオブジェクトをmake-store
構築するため、これを利用するように関数を定義できますstore
(ns redis-ex.app
(:require [hara.fn :as f]))
(defn get-last-3-elements [st kdir]
(f/send st :get-last kdir 3))
それを使用したい場合は...次のようにします:
(ns redis-ex.test-app0
(:use redis-ex.app
redis-ex.history)
(:require [taoensso.carmine :as car]))
(def store
(hist/make-store
(car/make-conn-pool)
(car/make-conn-spec)
"test"))
(get-last-3-elements ["test"] store)
;;=> [{:timestamp 3000000 :data 43.4} {:timestamp 4000000 :data 53.4} {:timestamp 5000000 :data 63.4}]
clojure によるモック - 'OO' スタイル
したがって、これの本当の利点は、get-last-3-elements
メソッドを完全に異なる名前空間に配置できることです。データベースの実装にはまったく依存しないため、この機能のテストには軽量のハーネスのみが必要です。
モックを定義するのは簡単です。redis-ex.usecase 名前空間のテストは、データベース ライブラリをロードせずに実行できます。
(ns redis-ex.test-app1
(:use redis-ex.app))
(defn make-mock-store []
{:database [{:timestamp 5000000 :data 63.4}
{:timestamp 4000000 :data 53.4}
{:timestamp 3000000 :data 43.4}
{:timestamp 2000000 :data 33.4}
{:timestamp 1000000 :data 23.4}]
:get-last (fn [store kdir number]
(->> (:database store)
(take number)
reverse))})
(def mock-store (make-mock-store))
(get-last-3-elements ["test"] mock-store)
;; => [{:timestamp 3000000 :data 43.4} {:timestamp 4000000 :data 53.4} {:timestamp 5000000 :data 63.4}]
以前の投稿では、Clojureでオブジェクト指向プログラミングのさまざまな機能の特定のサポートを実装することの価値と可能性についての質問として、この質問に対処しています。ただし、その用語に関連付けられているプロパティのファミリがあります。すべてのオブジェクト指向言語がそれらすべてをサポートしているわけではありません。また、Clojure は、「オブジェクト指向」のサポートと呼ぶかどうかに関係なく、これらのプロパティの一部を直接サポートしています。これらのプロパティのいくつかについて説明します。
Clojure は、そのマルチメソッド システムを使用して、階層的に定義された型のディスパッチをサポートできます。基本的な関数はdefmultiとdefmethodです。(質問が最初に回答されたとき、これらは利用できなかった可能性があります。)
CLOS の比較的珍しい機能の 1 つは、複数の引数の型でディスパッチする関数のサポートです。ここでの例が示すように、Clojure はその動作を非常に自然にエミュレートします。(この例では型自体を使用していませんが、それは Clojure のマルチメソッドの柔軟性の一部です。ここの最初の例と比較してください。)
CljOSは、Clojure 用のおもちゃの OOP ライブラリです。それは完全という言葉の意味ではありません。楽しむために作ったものばかりです。
古い投稿ですが、返信したいと思います。
clojureにはOOのサポートも、CLOSのサポートもありません。環境の基盤となるオブジェクトシステムは、相互運用性の意味でほとんど利用できず、clojureで独自のクラス/オブジェクト階層を作成するためのものではありません。ClojureはCLRまたはJVMライブラリに簡単にアクセスできるように作られていますが、OOPサポートはここで終了します。
ClojureはLispであり、クロージャとマクロをサポートします。これらの2つの機能を念頭に置いて、数行のコードで基本的なオブジェクトシステムを開発できます。
ここで重要なのは、Lisp方言で本当にOOPが必要なのかということです。私はノーとイエスと言うでしょう。いいえ。ほとんどの問題は、オブジェクトシステムがなくても、どのLispでもよりエレガントに解決できるためです。はい、そうだと思います。それでも時々OOPが必要になるので、すべてのオタクが独自に実装するよりも、標準のリファレンス実装を提供する方がよいからです。
ポール・グレアムのOnLispの本をご覧になることをお勧めします。オンラインで無料で相談できます。
これは本当に良い本で、Lispの本質を本当に理解しています。構文をclojureに少し適合させる必要がありますが、概念は同じままです。あなたの質問にとって重要なのは、最後の章の1つで、lispで独自のオブジェクトシステムを定義する方法を示しています。
補足として、clojureは不変性を受け入れます。clojureで可変オブジェクトシステムを作成することはできますが、不変性に固執する場合は、OOPを使用しても設計はまったく異なります。ほとんどの標準的なデザインパターンと構造は、可変性を念頭に置いて作られています。