Last active
April 18, 2021 15:20
-
-
Save micha/c9d8175d80e8a7d378adb6047e850c50 to your computer and use it in GitHub Desktop.
Implementation of `clojure.core/binding` and `clojure.core/bound-fn` in ClojureScript.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns hoplon.binding | |
(:refer-clojure :exclude [binding bound-fn]) | |
(:require [clojure.core :as clj] | |
[cljs.analyzer :as a])) | |
(defmacro binding | |
"See clojure.core/binding." | |
[bindings & body] | |
(let [env (assoc &env :ns (a/get-namespace a/*cljs-ns*)) | |
value-exprs (take-nth 2 (rest bindings)) | |
bind-syms (->> (take-nth 2 bindings) | |
(map #(:name (a/resolve-existing-var env %)))) | |
bind-syms' (map (partial list 'quote) bind-syms) | |
restore-syms (take (count bind-syms) (repeatedly gensym)) | |
set-syms (take (count bind-syms) (repeatedly gensym)) | |
setfn (fn [x y] `(fn [] (set! ~x ~y))) | |
push-pop (fn [x y z] {:push! (setfn x y) :pop! (setfn x z)}) | |
thunkmaps (map push-pop bind-syms set-syms restore-syms)] | |
(a/confirm-bindings env bind-syms) | |
`(let [~@(interleave restore-syms bind-syms) | |
~@(interleave set-syms value-exprs)] | |
(hoplon.binding/push-thread-bindings ~(zipmap bind-syms' thunkmaps)) | |
(try ~@body (finally (hoplon.binding/pop-thread-bindings)))))) | |
(defmacro bound-fn | |
"See clojure.core/bound-fn." | |
[args & body] | |
`(hoplon.binding/bound-fn* (fn [~@args] ~@body))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns hoplon.binding | |
(:refer-clojure :exclude [binding]) | |
(:require-macros [hoplon.binding :as b])) | |
(def thread-bindings | |
"Stack of binding maps like this: | |
{ | |
my.namespace.core {:push! (fn [] ...) :pop! (fn [] ...)} | |
other.namespace.foo {:push! (fn [] ...) :pop! (fn [] ...)} | |
... | |
where the keys of the map are the Javascript variables (as symbols) and | |
the values are maps with :push! and :pop! keys, each associated with a | |
zero arity procedure that pushes or pops the thread binding for that var." | |
(atom [])) | |
(defn push-thread-bindings | |
"Pushes binding-map onto the thread-bindings stack and establishes the | |
associated bindings." | |
[binding-map] | |
(swap! thread-bindings conj binding-map) | |
(doseq [{:keys [push!]} (vals binding-map)] (push!))) | |
(defn pop-thread-bindings | |
"Pops the topmost binding map from thread-bindings stack and restores the | |
associated bindings to their previous, saved values." | |
[] | |
(let [popped (peek @thread-bindings)] | |
(swap! thread-bindings pop) | |
(doseq [{:keys [pop!]} (vals popped)] (pop!)))) | |
(defn bound-fn* | |
"Given a function f, returns a new function capturing the current bindings | |
in its closure. When the returned function is invoked the saved bindings | |
are pushed and set, f is applied to the arguments, and bindings are restored | |
to their previous, saved values." | |
[f] | |
(let [binding-map (apply merge @thread-bindings)] | |
(fn [& args] | |
(push-thread-bindings binding-map) | |
(try (apply f args) (finally (pop-thread-bindings)))))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The code in this gist is distributed under the Eclipse Public License v1.0 or later, at your discretion.