::rect
-> :user/rect
Clojureは、新しい状況ごとに新しいデータ型を作成するという従来のオブジェクト指向アプローチを避け、少数の型に基づいて大規模な関数ライブラリを構築することを好みます。しかし、Clojureは、柔軟で拡張可能なシステムアーキテクチャを実現する上で、実行時ポリモーフィズムの価値を十分に認識しています。 Clojureは、1つ以上の引数の型、値、属性、メタデータ、およびそれらの間の関係に基づいてディスパッチをサポートするマルチメソッドシステムを通じて、高度な実行時ポリモーフィズムをサポートします。
Clojureマルチメソッドは、_ディスパッチ関数_と1つ以上の_メソッド_の組み合わせです。 _ ** defmulti ** _を使用してマルチメソッドを定義する場合、ディスパッチ関数を指定する必要があります。この関数は、_ディスパッチ値_を生成するために、マルチメソッドの引数に適用されます。次に、マルチメソッドは、ディスパッチ値、またはディスパッチ値が派生した値に関連付けられたメソッドを見つけようとします。定義されている場合(defmethod経由)、引数とともに呼び出され、それがマルチメソッド呼び出しの値になります。ディスパッチ値に関連付けられたメソッドがない場合、マルチメソッドはデフォルトのディスパッチ値(デフォルトは_ **:default ** _)に関連付けられたメソッドを探し、存在する場合はそれを使用します。そうでない場合、呼び出しはエラーです。
マルチメソッドシステムはこのAPIを公開します:defmultiは新しいマルチメソッドを作成し、defmethodはディスパッチ値に関連付けられたマルチメソッドの新しいメソッドを作成してインストールし、remove-methodはディスパッチ値に関連付けられたメソッドを削除し、prefer-methodは、メソッドがそうでない場合にメソッド間に順序を作成します。あいまいになります。
派生は、Java継承(クラス値の場合)またはClojureのアドホック階層システムを使用することによって決定されます。階層システムは、名前(シンボルまたはキーワード)間の派生関係、およびクラスと名前間の関係をサポートします。derive関数はこれらの関係を作成し、isa?関数はそれらの存在をテストします。isa?はinstance?ではないことに注意してください。
階層関係は(derive child parent)で定義できます。子と親はシンボルまたはキーワードのいずれかであり、名前空間で修飾する必要があります
::リーダー構文に注意してください。:: keywordsは名前空間を解決します。
::rect
-> :user/rect
deriveは基本的な関係作成者です
(derive ::rect ::shape)
(derive ::square ::rect)
parents / ancestors / descendantsおよびisa?を使用すると、階層をクエリできます
(parents ::rect)
-> #{:user/shape}
(ancestors ::square)
-> #{:user/rect :user/shape}
(descendants ::shape)
-> #{:user/rect :user/square}
`(= x y)`は `(isa?x y)`を意味します
(isa? 42 42)
-> true
`isa?`は階層システムを使用します
(isa? ::square ::shape)
-> true
クラスを子として使用することもできます(ただし、親としては使用できません。クラスの子にする唯一の方法はJava継承を使用することです)。
これにより、既存のJavaクラス階層に新しい分類法を重ねることができます
(derive java.util.Map ::collection)
(derive java.util.Collection ::collection)
(isa? java.util.HashMap ::collection)
-> true
isa?はクラス関係もテストします
(isa? String Object)
-> true
parents / ancestors(ただしdescendants は、クラスの子孫はオープンセットであるため)と同様です。
(ancestors java.util.ArrayList)
-> #{java.lang.Cloneable java.lang.Object java.util.List
java.util.Collection java.io.Serializable
java.util.AbstractCollection
java.util.RandomAccess java.util.AbstractList}
(isa? [::square ::rect] [::shape ::shape])
-> true
マルチメソッドは、ディスパッチ値の Übereinstimmung をテストするときに、=ではなくisa?を使用します。isa?の最初のテストは=であるため、完全一致が機能することに注意してください。
(defmulti foo class)
(defmethod foo ::collection [c] :a-collection)
(defmethod foo String [s] :a-string)
(foo [])
:a-collection
(foo (java.util.HashMap.))
:a-collection
(foo "bar")
:a-string
prefer-methodは、どちらも支配的でない複数の一致の場合のあいまいさを解消するために使用されます。マルチメソッドごとに、あるディスパッチ値が別のディスパッチ値よりも優先されることを宣言できます。
(derive ::rect ::shape)
(defmulti bar (fn [x y] [x y]))
(defmethod bar [::rect ::shape] [x y] :rect-shape)
(defmethod bar [::shape ::rect] [x y] :shape-rect)
(bar ::rect ::rect)
-> Execution error (IllegalArgumentException) at user/eval152 (REPL:1).
Multiple methods in multimethod 'bar' match dispatch value:
[:user/rect :user/rect] -> [:user/shape :user/rect]
and [:user/rect :user/shape], and neither is preferred
(prefer-method bar [::rect ::shape] [::shape ::rect])
(bar ::rect ::rect)
-> :rect-shape
上記のすべての例では、マルチメソッドシステムで使用されるグローバル階層を使用していますが、make-hierarchyを使用して完全に独立した階層を作成することもでき、上記のすべての関数はオプションの階層を最初の引数として取ることができます。
この単純なシステムは非常に強力です。Clojureマルチメソッドと従来のJavaスタイルの単一ディスパッチの関係を理解する1つの方法は、単一ディスパッチは、ディスパッチ関数が最初の引数でgetClassを呼び出し、メソッドがそれらのクラスに関連付けられているClojureマルチメソッドのようなものであるということです。 Clojureマルチメソッドはクラス/型にハードワイヤードされておらず、引数の任意の属性、複数の引数に基づくことができ、引数の検証、エラー処理メソッドへのルーティングなどを行うことができます。
注:この例では、データ構造セクションで説明されているように、キーワードはマップの関数であるため、キーワード:Shapeがディスパッチ関数として使用されています。
(defmulti area :Shape)
(defn rect [wd ht] {:Shape :Rect :wd wd :ht ht})
(defn circle [radius] {:Shape :Circle :radius radius})
(defmethod area :Rect [r]
(* (:wd r) (:ht r)))
(defmethod area :Circle [c]
(* (. Math PI) (* (:radius c) (:radius c))))
(defmethod area :default [x] :oops)
(def r (rect 4 13))
(def c (circle 12))
(area r)
-> 52
(area c)
-> 452.3893421169302
(area {})
-> :oops