A let binds in sequence, so binding a method's names pairwise from the generic's reads a name it has just bound. A generic [a b] with a method [b a] -- a swap, which is what renaming parameters most often is -- was handed its first argument twice and could not reach its second at all; [b c] is the same bug one step shorter. Every argument is now copied into a temp in the unspellable ~ namespace first and every method name bound from a temp, uniformly rather than only for the pairs that collide, because a rule that fires on the tangled case alone is one nobody exercises. Both shapes are in dyn-class.flan, where the values are what is wrong rather than the types, and across all three rows. With it, two things the descriptor fix left behind. descriptors_asm wrote the descriptors into .rodata and a descriptor holds the address of its own offset table, so every one of them was a relocation in a read-only section -- a DT_TEXTREL, which ld warns about in a PIE and refuses in a shared object, and which was warning in the new daemon case's own output. They go in .data.rel.ro now, in both the executable and the reload module; readelf -d on a reload module from each backend shows no TEXTREL. And FIX.org: the stale held line for item 6, the fourth read site of the shape tag (say_render, not just print), the warning that a class's qualifier is the importer's alias so a hand-written :a/point is coupled to one import's name, and the gap flagged for the next sweep -- marking through a descriptor an x86 reload module emitted is still unexercised.
138 lines
5.9 KiB
Plaintext
138 lines
5.9 KiB
Plaintext
;;;; Classes and generic functions, the dyn side's two dispatch styles.
|
|
;;;;
|
|
;;;; A defclass is a named dyn map with a shape tag. The constructor is the
|
|
;;;; class's own name, positional over the slots; the slots are ordinary map
|
|
;;;; keys, so get and put are how one is read and written and nothing new was
|
|
;;;; needed for either. What the class adds is the tag, which lives in the
|
|
;;;; object's header and not in the entries: (len p) is the slot count, no key
|
|
;;;; a program can write collides with it, and it shows up in exactly three
|
|
;;;; places -- class-of, equality, and the printed form #point{ :x 1 :y 2}.
|
|
;;;;
|
|
;;;; The two dispatch styles are one mechanism. A defgeneric dispatches on the
|
|
;;;; class of its first argument, which is Common Lisp's; a defmulti's body IS
|
|
;;;; the dispatch, which is Clojure's. A class dispatcher is the shape tag of
|
|
;;;; the first argument as the dispatch function, so the second spells the
|
|
;;;; first, and a method is a branch either way.
|
|
|
|
(defclass point [x y])
|
|
(defclass circle [r])
|
|
|
|
;; CLOS's half: dispatch on the class of the first argument. The generic
|
|
;; declares the parameters and the return type once; a method states neither.
|
|
(defgeneric area [self] dyn)
|
|
|
|
(defmethod area point [p] (* (get p :x) (get p :y)))
|
|
;; A method may name its parameter whatever it likes -- the generic's name is
|
|
;; bound to it on the way in.
|
|
(defmethod area circle [c] (* 3 (* (get c :r) (get c :r))))
|
|
|
|
;; Clojure's half: the dispatch is a body, over the same parameter list every
|
|
;; method has, answering the value the methods are keyed by.
|
|
(defmulti describe [thing] dyn (get thing :kind))
|
|
|
|
(defmethod describe :square [s] (get s :side))
|
|
(defmethod describe "round" [s] "a round thing, keyed by a string")
|
|
(defmethod describe 7 [s] "the one keyed by a number")
|
|
;; The method that answers when no other does. It is :else, which is the word
|
|
;; match already uses, and it is the last arm whatever order it is written in.
|
|
(defmethod describe :else [s] "something else")
|
|
|
|
;; A generic with no :else: a miss signals, and the program answers it.
|
|
(defgeneric name-of [self] dyn)
|
|
(defmethod name-of point [p] "a point")
|
|
|
|
;; A method's parameter names are its own, and the rebinding that gives it
|
|
;; them is parallel. [reorder] names them in the generic's order reversed, which
|
|
;; a sequential binding would get wrong in the worst possible way -- it would
|
|
;; read the name it had just bound and hand the method its first argument
|
|
;; twice. [shift] is the same bug one step shorter: [b] there is the
|
|
;; generic's second parameter and must not become the first.
|
|
(defmulti reorder [a b] dyn (class-of a))
|
|
(defmethod reorder point [b a] [b a])
|
|
(defmulti shift [a b] dyn (class-of a))
|
|
(defmethod shift point [b c] [b c])
|
|
|
|
(defn main [] i32
|
|
(let [p (point 3 4)
|
|
c (circle 2)]
|
|
;; The instance is a map, and prints as one with its tag in front.
|
|
(println p)
|
|
(println (len p))
|
|
(println (get p :x))
|
|
(put p :x 10)
|
|
(println (get p :x))
|
|
(println (has-key? p :x))
|
|
(println (has-key? p :nothing))
|
|
(println (get p :nothing))
|
|
|
|
;; The shape tag, as a value. Every value can be asked; only an instance
|
|
;; answers with a name.
|
|
(println (class-of p))
|
|
(println (class-of c))
|
|
(println (class-of {:x 3 :y 4}))
|
|
(println (class-of 1))
|
|
(println (class-of nil))
|
|
|
|
;; Equality takes the tag into account: two instances of one class compare
|
|
;; by their slots, an instance and a plain map with the same entries do
|
|
;; not, and two classes with the same slots are two classes.
|
|
(println (= (point 1 2) (point 1 2)))
|
|
(println (= (point 1 2) (point 1 3)))
|
|
(println (= (point 1 2) {:x 1 :y 2}))
|
|
(println (= (circle 2) (circle 2)))
|
|
|
|
;; Class dispatch. Same call site, two classes, two methods.
|
|
(println (area p))
|
|
(println (area c))
|
|
|
|
;; Arbitrary dispatch, over three kinds of dispatch value and the
|
|
;; fallback. The dispatch runs on every call, so the value is whatever
|
|
;; the map holds at the time.
|
|
(println (describe {:kind :square :side 5}))
|
|
(println (describe {:kind "round"}))
|
|
(println (describe {:kind 7}))
|
|
(println (describe {:kind :hexagon}))
|
|
;; An empty map on its own is the zero-field struct literal, so it is
|
|
;; bound first -- the dispatch answers nil for a map with no :kind, and
|
|
;; nil finds no method either.
|
|
(let [empty {:no :kind}]
|
|
(println (describe empty)))
|
|
|
|
;; An instance is an ordinary dyn value: it goes in a vec, keys a map,
|
|
;; and is collected like anything else.
|
|
(let [v [p c]]
|
|
(println (len v))
|
|
(println (class-of (at v 1))))
|
|
|
|
;; The renamed parameters, in the generic's own order: the first element
|
|
;; of each answer is the first argument. A sequential rebinding would
|
|
;; print the point twice in the first and lose the 99 in the second.
|
|
(println (reorder p 99))
|
|
(println (shift p 99))
|
|
|
|
;; The miss. No method and no :else, so the generic signals NoMethod, and
|
|
;; handler-case answers the whole form with a value -- the condition
|
|
;; carries the generic's name and the dispatch value that found nothing.
|
|
(println (name-of p))
|
|
(println
|
|
(handler-case (name-of c)
|
|
[(NoMethod [e] (.generic e))]))
|
|
;; The condition carries the dispatch value that found nothing, which for
|
|
;; a defgeneric over a value that is no instance at all is nil.
|
|
(println
|
|
(handler-case (name-of 42)
|
|
[(NoMethod [e] (.value e))])))
|
|
|
|
;; Instances under the collector: enough of them to pass the 1 MiB floor
|
|
;; many times over, with one live instance in a rooted global. A marker that
|
|
;; lost an instance's slots would free something live and the sum would come
|
|
;; out wrong -- the tag itself is an interned keyword and immortal, which is
|
|
;; why it needs no tracing.
|
|
(let [total (point 0 0)]
|
|
(dotimes [i 50000]
|
|
(let [q (point i "forty-seven bytes of text to fatten each row")]
|
|
(put total :x (+ (get total :x) (len q)))))
|
|
(println (get total :x))
|
|
(println (class-of total)))
|
|
0)
|