Szerkesztő:Marcell1202/próbalap
Clojure Multimetódus és Hierarchia
[szerkesztés]A Clojure megkerüli a hagyományos objektum orientált megközelítést, azaz hogy minden új szituációhoz új adattípust hozzon létre, helyette felépít egy nagy függvénykönyvtárat egy kis típusra. A Clojure azonban teljes mértékben elismeri a futás időben történő polimorfizmust, ezáltal flexibilis és bővíthető rendszer architektúrát tesz lehetővé. Támogatja a kifinomult futás idejű polimorfizmust egy multimethod rendszeren keresztül, ami támogatja az egy vagy több argumentumok típusainak, értékeinek, attribútumainak, metaadatainak, és kapcsolatainak továbbküldését (dispatch).
A Clojure multimethod kombinációja a dispatching funkciónak, és egy vagy több metódusnak. Amikor a multimetódust definiálják a defmulti kulcsszóval, akkor a dispatching funkciót kötelező megadni. Ez a funkció lesz használva a multimetódus argumentumain, hogy előállítsa a dispatching értéket. A multimetódus ezután megpróbálja megkeresni a dispatching értékhez tartozó metódust, vagy azt az értéket ahonnan a dispatching érték származott. Ha a metódus definiálva lett (defmethod kulcsszóval), akkor meg lesz hívva az argumentumokkal, és az lesz az értéke a multimetódus hívásnak. Ha nincs olyan metódus ami a dispatching értékhez tartozik, akkor a multimetódus az alap dispatching értékhez tartozó metódust keres (:default), és azt használja, ha létezik. Különben a hívás hibát dob.
A multimethod rendszer a következő API-t valósítja meg: defmulti egy új multimetódust készít, defmethod egy új metódust készít és telepít, ami összefüggésben van a dispatch értékkel, remove-method eltávolítja a dispatch értékkel összefüggő metódust, a prefer-method pedig rendezést készít a metódusok között amikor kétértelműek lehetnek.
A származtatás meghatározott Java öröklődés (osztály értékekre), vagy a Clojure ad-hoc hierarchia rendszer kombinációja alapján. A hierarchia rendszer támogatja a származtatását kapcsolatoknak nevek között (szimbólumok vagy kulcsszavak), és kapcsolatoknak osztályok és nevek között. A derive funkció készíti el ezeket a kapcsolatokat, és az isa? funkciót tesztelni a létezésüket. (Az isa? nem instance?)
Megadhatóak hierarchikus kapcsolatok a következővel: (derice child parent). A gyerek (child) és szülő (parent) lehet egyaránt szimbólum vagy kulcsszó, és kötelezően névtér meghatározott (namespace-qualified):
Megjegyzés: a ::reader syntax, ::keywords névtereket valósítanak meg.
::rect
-> :user/rect
derive az alapvető kapcsolatépítő
(derive ::rect ::shape)
(derive ::square ::rect)
parents (szülők) / ancestors (ősök) / descendants (leszármazottak) és isa? a hierarchia lekérdezésére szolgál
(parents ::rect)
-> #{:user/shape}
(ancestors ::square)
-> #{:user/rect :user/shape}
(descendants ::shape)
-> #{:user/rect :user/square}
(= x y) implementálja (isa? x y)
(isa? 42 42)
-> true
isa? a hierarchia rendszert használja
(isa? ::square ::shape)
-> true
Használható egy osztály gyerekként is (de nem a szülő, az egyetlen lehetőség hogy valamit gyerekké tegyünk Java öröklődéssel lehetséges) Ez lehetővé teszi új rendszerek ráhelyezését már létező Java osztályhierarchiára.
(derive java.util.Map ::collection)
(derive java.util.Collection ::collection)
(isa? java.util.HashMap ::collection)
-> true
isa? teszteli az osztály kapcsolatait
(isa? String Object)
-> true
szülők (parents) / ősök (ancestors) is szintén (de leszármazottak (descendants) nem, mivel ezek nyílt halmazok)
(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? vektorokkal dolgozik, úgy hogy meghívja az isa?-t a megfelelő elemekre:
(isa? [::square ::rect] [::shape ::shape])
-> true
isa? alapú küldés (dispatch)
[szerkesztés]A multimetódusok isa?-t használnak = helyett amikor tesztelik a dispatch értékek egyezését. Fontos, hogy az isa? első tesztje =, tehát a pontos egyezések működnek.
(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 egyértelműsítésre szolgál, abban az esetben ha több egyezés van ahol egyik sem dominánsabb mint a másik. Egyszerűen deklarálható multimetódusonként, hogy melyik dispatch érték részesüljön előnyben.
(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
A fenti példák mindegyike a globális hierarchiát használja, ami a multimetódus rendszer által van használva, de teljes különálló hierarchiák is kiépíthetőek make-hierarchy-val, és a fenti funkciók felvehetnek opcionális hierarchiát első argumentumként.
Ez a rendszer extrém hatékony. Egy út hogy megértsük a kapcsolatokat a Clojure multimetódusok és a tradicionális Java stílusú egyszeres dispatch között, hogy az egyszeres dispatch olyan mint a Clojure multimethod, aminek a dispatch funkciója meghívja a getClass-t az első argumentumon, amelynek a metódusai kapcsolatban állnak azokkal az osztályokkal. A Clojure multimetódusok nem erősen kötöttek osztály/típusra nézve, bármilyen argumentum tulajdonságon alapulhatnak, több argumentumon, csinálhatnak validációt rajtuk, és eljuthatnak hibakezelő metódusokig.
Megjegyzés: Ebben a példában a :Shape kulcsszó dispatch funkcióként van használva, mivel a kulcsszavak a map-ek funkciói, ahogy az az adatstruktúrák szekcióban áll.
(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