Skip to content

Instantly share code, notes, and snippets.

@Solaxun
Last active July 19, 2020 23:54
Show Gist options
  • Save Solaxun/5db71e31f6dd7e2719f95ed86b2734b5 to your computer and use it in GitHub Desktop.
Save Solaxun/5db71e31f6dd7e2719f95ed86b2734b5 to your computer and use it in GitHub Desktop.
Toy implementation of reify, doesn't check the methods are actually part of the protocol, doesn't do namespacing, etc. Just mimicks the interface.
;;;
;;; implementation
;;;
(defn build-fn [[mname args body]]
{(keyword mname) (list 'fn mname args body)})
(defn make-generic-fn [[mname args body]]
`(defn ~mname ~args
(let [f# (get ~(first args) ~(keyword mname))]
(f# ~@args))))
(defmacro reifoo [proto-name & fns]
`(do ~@(map make-generic-fn fns)
~(apply merge (map build-fn fns)))) ; returns map of fns which
;;; ; close over locals at runtime
;;; usage
;;;
(defn foo1 [y]
(reifoo Foo
(foo [_ x] (+ x y))
(boo [_ x] (* x y))))
(def foo1-impl (foo1 85))
(foo foo1-impl 10)
(boo foo1-impl 10)
(defn foo2 [y]
(reifoo Foo
(foo [_ x] (- x y))
(boo [_ x] (/ x y))))
(def foo2-impl (foo2 85)) ; foo2-impl is a map of kw->funcs
; since the map and funcs were created
(foo foo2-impl 10) ; when the call to foo2 occured, the
(boo foo2-impl 10) ; funcs close over that environment
(def foo3-impl
(let [y 200]
(reifoo Foo
(foo [this x] (str x y))
(boo [this x] (vector x y)))))
(foo foo3-impl "foo")
(boo foo3-impl :boo)
(comment Example macroexpansion:
calling:
(reifoo Foo
(foo [_ x] (+ x y))
(boo [_ x] (* x y)))
yields:
(do
(clojure.core/defn
foo
[_ x]
(clojure.core/let
[f__1625__auto__ (clojure.core/get _ :foo)]
(f__1625__auto__ _ x)))
(clojure.core/defn
boo
[_ x]
(clojure.core/let
[f__1625__auto__ (clojure.core/get _ :boo)]
(f__1625__auto__ _ x)))
{:foo (fn foo [_ x] (+ x y)), :boo (fn boo [_ x] (* x y))}))
@Solaxun
Copy link
Author

Solaxun commented Jul 19, 2020

The generic functions created as part of the macroexpansion will end up receiving the map of functions returned from reifoo on line 70 as their first argument. Then, they look up the keyword corresponding to their function name inside this map, and call the resulting function with the map and any arguments. The functions inside the map on line 70 are created where and when the macro is called from, e.g. globally, inside a let, inside a defn, etc. and so they will capture any bindings in the context of that environment (they are lexical closures).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment