defprotocol

macrosince v0.0-927 clojure.core/defprotocolEdit
(defprotocol psym & doc+methods)

Source docstring:
A protocol is a named set of named methods and their signatures:

(defprotocol AProtocolName
  ;optional doc string
  "A doc string for AProtocol abstraction"

;method signatures
  (bar [this a b] "bar docs")
  (baz [this a] [this a b] [this a b c] "baz docs"))

No implementations are provided. Docs can be specified for the
protocol overall and for each method. The above yields a set of
polymorphic functions and a protocol object. All are
namespace-qualified by the ns enclosing the definition The resulting
functions dispatch on the type of their first argument, which is
required and corresponds to the implicit target object ('this' in
JavaScript parlance). defprotocol is dynamic, has no special compile-time
effect, and defines no new types.

(defprotocol P
  (foo [this])
  (bar-me [this] [this y]))

(deftype Foo [a b c]
  P
  (foo [this] a)
  (bar-me [this] b)
  (bar-me [this y] (+ c y)))

(bar-me (Foo. 1 2 3) 42)
=> 45

(foo
  (let [x 42]
    (reify P
      (foo [this] 17)
      (bar-me [this] x)
      (bar-me [this y] x))))
=> 17
Source code @ clojurescript:src/main/clojure/cljs/core.cljc
(core/defmacro defprotocol
  [psym & doc+methods]
  (core/let [p (:name (cljs.analyzer/resolve-var (dissoc &env :locals) psym))
             [doc methods] (if (core/string? (first doc+methods))
                             [(first doc+methods) (next doc+methods)]
                             [nil doc+methods])
             psym (vary-meta psym assoc
                    :doc doc
                    :protocol-symbol true)
             ns-name (core/-> &env :ns :name)
             fqn (core/fn [n] (symbol (core/str ns-name "." n)))
             prefix (protocol-prefix p)
             _ (core/doseq [[mname & arities] methods]
                 (core/when (some #{0} (map count (filter vector? arities)))
                   (throw
                     #?(:clj (Exception.
                               (core/str "Invalid protocol, " psym
                                 " defines method " mname " with arity 0"))
                        :cljs (js/Error.
                                (core/str "Invalid protocol, " psym
                                  " defines method " mname " with arity 0"))))))
             expand-sig (core/fn [fname slot sig]
                          (core/let [sig (core/if-not (every? core/symbol? sig)
                                           (mapv (core/fn [arg]
                                                   (core/cond
                                                     (core/symbol? arg) arg
                                                     (core/and (map? arg) (core/some? (:as arg))) (:as arg)
                                                     :else (gensym))) sig)
                                           sig)]
                            `(~sig
                              (if (and (not (nil? ~(first sig)))
                                    (not (nil? (. ~(first sig) ~(symbol (core/str "-" slot)))))) ;; Property access needed here.
                                (. ~(first sig) ~slot ~@sig)
                                (let [x# (if (nil? ~(first sig)) nil ~(first sig))
                                      m# (unchecked-get ~(fqn fname) (goog/typeOf x#))]
                                  (if-not (nil? m#)
                                    (m# ~@sig)
                                    (let [m# (unchecked-get ~(fqn fname) "_")]
                                      (if-not (nil? m#)
                                        (m# ~@sig)
                                        (throw
                                          (missing-protocol
                                            ~(core/str psym "." fname) ~(first sig)))))))))))
             psym (core/-> psym
                    (vary-meta update-in [:jsdoc] conj
                      "@interface")
                    (vary-meta assoc-in [:protocol-info :methods]
                      (into {}
                        (map
                          (core/fn [[fname & sigs]]
                            (core/let [doc (core/as-> (last sigs) doc
                                             (core/when (core/string? doc) doc))
                                       sigs (take-while vector? sigs)]
                              [(vary-meta fname assoc :doc doc)
                               (vec sigs)]))
                          methods))))
             method (core/fn [[fname & sigs]]
                      (core/let [doc (core/as-> (last sigs) doc
                                       (core/when (core/string? doc) doc))
                                 sigs (take-while vector? sigs)
                                 amp (core/when (some #{'&} (apply concat sigs))
                                       (cljs.analyzer/warning
                                        :protocol-with-variadic-method
                                        &env {:protocol psym :name fname}))
                                 slot (symbol (core/str prefix (munge (name fname))))
                                 fname (vary-meta fname assoc
                                         :protocol p
                                         :doc doc)]
                        `(defn ~fname
                           ~@(map (core/fn [sig]
                                    (expand-sig fname
                                      (symbol (core/str slot "$arity$" (count sig)))
                                      sig))
                               sigs))))]
    `(do
       (set! ~'*unchecked-if* true)
       (def ~psym (~'js* "function(){}"))
       ~@(map method methods)
       (set! ~'*unchecked-if* false))))