Merge pull request #532 from dubek/vhdl-fix-defmacro
[jackhill/mal.git] / impls / lib / protocols.mal
CommitLineData
dcdb6c02
NB
1;; A sketch of Clojure-like protocols, implemented in Mal
2
3;; By chouser (Chris Houser)
4;; Original: https://gist.github.com/Chouser/6081ea66d144d13e56fc
5
83665b4f
NB
6;; This function maps a MAL value to a keyword representing its type.
7;; Most applications will override the default with an explicit value
8;; for the `:type` key in the metadata.
9(def! find-type (fn* [obj]
dcdb6c02 10 (cond
dcdb6c02
NB
11 (symbol? obj) :mal/symbol
12 (keyword? obj) :mal/keyword
13 (atom? obj) :mal/atom
83665b4f
NB
14 (nil? obj) :mal/nil
15 (true? obj) :mal/boolean
16 (false? obj) :mal/boolean
17 (number? obj) :mal/number
18 (string? obj) :mal/string
19 (macro? obj) :mal/macro
20 true
21 (let* [metadata (meta obj)
22 type (if (map? metadata) (get metadata :type))]
23 (cond
24 (keyword? type) type
25 (list? obj) :mal/list
26 (vector? obj) :mal/vector
27 (map? obj) :mal/map
28 (fn? obj) :mal/function
29 true (throw "unknown MAL value in protocols"))))))
dcdb6c02 30
83665b4f
NB
31;; A protocol (abstract class, interface..) is represented by a symbol.
32;; It describes methods (abstract functions, contracts, signals..).
33;; Each method is described by a sequence of two elements.
34;; First, a symbol setting the name of the method.
35;; Second, a vector setting its formal parameters.
36;; The first parameter is required, plays a special role.
37;; It is usually named `this` (`self`..).
38;; For example,
39;; (defprotocol protocol
40;; (method1 [this])
41;; (method2 [this argument]))
42;; can be thought as:
43;; (def! method1 (fn* [this]) ..)
44;; (def! method2 (fn* [this argument]) ..)
45;; (def! protocol ..)
46;; The return value is the new protocol.
dcdb6c02 47(defmacro! defprotocol (fn* [proto-name & methods]
83665b4f
NB
48 ;; A protocol is an atom mapping a type extending the protocol to
49 ;; another map from method names as keywords to implementations.
50 (let* [
51 drop2 (fn* [args]
52 (if (= 2 (count args))
53 ()
54 (cons (first args) (drop2 (rest args)))))
55 rewrite (fn* [method]
56 (let* [
57 name (first method)
58 args (nth method 1)
59 argc (count args)
60 varargs? (if (<= 2 argc) (= '& (nth args (- argc 2))))
61 dispatch `(get (get @~proto-name
62 (find-type ~(first args)))
63 ~(keyword (str name)))
64 body (if varargs?
65 `(apply ~dispatch ~@(drop2 args) ~(nth args (- argc 1)))
66 (cons dispatch args))
67 ]
68 (list 'def! name (list 'fn* args body))))
69 ]
70 `(do
71 ~@(map rewrite methods)
72 (def! ~proto-name (atom {}))))))
dcdb6c02 73
83665b4f
NB
74;; A type (concrete class..) extends (is a subclass of, implements..)
75;; a protocol when it provides implementations for the required methods.
76;; (extend type protocol {
77;; :method1 (fn* [this] ..)
78;; :method2 (fn* [this arg1 arg2])})
79;; Additionnal protocol/methods pairs are equivalent to successive
80;; calls with the same type.
81;; The return value is `nil`.
dcdb6c02
NB
82(def! extend (fn* [type proto methods & more]
83 (do
84 (swap! proto assoc type methods)
85 (if (first more)
86 (apply extend type more)))))
87
83665b4f
NB
88;; An object satisfies a protocol when its type extends the protocol,
89;; that is if the required methods can be applied to the object.
90(def! satisfies? (fn* [protocol obj]
91 (contains? @protocol (find-type obj))))
92;; If `(satisfies protocol obj)` with the protocol below
93;; then `(method1 obj)` and `(method2 obj 1 2)`
94;; dispatch to the concrete implementation provided by the exact type.
95;; Should the type evolve, the calling code needs not change.