Skip to content

Instantly share code, notes, and snippets.

@micha
Last active April 18, 2021 15:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save micha/c9d8175d80e8a7d378adb6047e850c50 to your computer and use it in GitHub Desktop.
Save micha/c9d8175d80e8a7d378adb6047e850c50 to your computer and use it in GitHub Desktop.
Implementation of `clojure.core/binding` and `clojure.core/bound-fn` in ClojureScript.
(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)))
(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))))))
@micha
Copy link
Author

micha commented Sep 5, 2016

The code in this gist is distributed under the Eclipse Public License v1.0 or later, at your discretion.

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