From eef404114a27a4aaf8740b8f643227f28e616d52 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 18 Nov 2025 22:40:08 -0500 Subject: [PATCH 1/5] first pass at cljs.proxy --- src/main/cljs/cljs/proxy.cljs | 132 ++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/main/cljs/cljs/proxy.cljs diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs new file mode 100644 index 000000000..e5f4ab162 --- /dev/null +++ b/src/main/cljs/cljs/proxy.cljs @@ -0,0 +1,132 @@ +(ns cljs.proxy + (:refer-global :only [Proxy isNaN])) + +(deftype SimpleCache [^:mutable obj ^:mutable cnt] + Object + (set [this k v] + (when (== cnt 1024) + (.clear this)) + (unchecked-set obj k v) + (set! cnt (inc cnt)) + v) + (get [this k] + (unchecked-get obj k)) + (clear [this] + (set! obj #js {}) + (set! cnt 0))) + +(defn write-through [f] + (let [cache (SimpleCache. #js {} 0)] + (fn [x] + (let [v (.get cache x)] + (if (some? v) + v + (.set cache x (f x))))))) + +(def desc + #js {:configurable true + :enumerable true}) + +(defn builder + ([] + (builder keyword)) + ([key-fn] + (js* "var __ctor") + (let [cache-key-fn (write-through key-fn) + vec-handler #js {:get (fn [^cljs.core/IIndexed target prop receiver] + (if (identical? prop "length") + (-count ^cljs.core/ICounted target) + (let [n (js* "+~{}" prop)] + (when (and (number? n) + (not (isNaN n))) + (js/__ctor (-nth target n nil)))))) + + :has (fn [^cljs.core/IAssociative target prop] + (if (identical? prop "length") + true + (let [n (js* "+~{}" prop)] + (and (number? n) + (not (isNaN n)) + (<= 0 n) + (< n (-count ^cljs.core/ICounted target)))))) + + :getPrototypeOf + (fn [target] nil) + + :ownKeys + (fn [target] #js ["length"]) + + :getOwnPropertyDescriptor + (fn [target prop] desc)} + map-handler #js {:get (fn [^cljs.core/ILookup target prop receiver] + (js/__ctor (-lookup target (cache-key-fn prop)))) + + :has (fn [^cljs.core/IAssociative target prop] + (-contains-key? target (cache-key-fn prop))) + + :getPrototypeOf + (fn [target] nil) + + :ownKeys + (fn [target] + (when (nil? (.-cljs$cachedOwnKeys target)) + (set! (. target -cljs$cachedOwnKeys) + (into-array (map -name (keys target))))) + (.-cljs$cachedOwnKeys target)) + + :getOwnPropertyDescriptor + (fn [target prop] desc)} + __ctor (fn [target] + (cond + (implements? IMap target) (Proxy. target map-handler) + (implements? IVector target) (Proxy. target vec-handler) + :else target))] + __ctor))) + +(comment + + (def c (SimpleCache. #js {} 0)) + (.set c "foo" :foo) + (.get c "foo") + (.-cnt c) + (.clear c) + (.get c "foo") + + (def kw (write-through keyword)) + (kw "foo") + + (time + (dotimes [i 1e6] + (kw "foo"))) + + (time + (dotimes [i 1e6] + (keyword "foo"))) + + (def proxy (builder)) + + (def proxied-map (proxy {:foo 1 :bar 2})) + + (require '[goog.object :as gobj]) + (gobj/get proxied-map "foo") + (gobj/get proxied-map "bar") + + (time + (dotimes [i 1e7] + (unchecked-get proxied-map "foo"))) + + (def proxied-vec (proxy [1 2 3 4])) + (alength proxied-vec) + (time + (dotimes [i 1e6] + (alength proxied-vec))) + + (nth [1 2 3 4] 1) + + (aget proxied-vec 1) + + (time + (dotimes [i 1e7] + (aget proxied-vec 1))) + +) From 6c1e4da129117f9166b9f1e9b90ae594a7371676 Mon Sep 17 00:00:00 2001 From: davidnolen Date: Tue, 18 Nov 2025 22:46:30 -0500 Subject: [PATCH 2/5] trivial nested access example --- src/main/cljs/cljs/proxy.cljs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs index e5f4ab162..11b99c4f9 100644 --- a/src/main/cljs/cljs/proxy.cljs +++ b/src/main/cljs/cljs/proxy.cljs @@ -128,5 +128,8 @@ (time (dotimes [i 1e7] (aget proxied-vec 1))) + + (def proxied-deep (proxy [{:foo "Hello"}])) + (-> proxied-deep (aget 0) (unchecked-get "foo")) ) From c56d961a9d52b2a756911000cfeafa2ba89f44b0 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Wed, 19 Nov 2025 09:07:20 -0500 Subject: [PATCH 3/5] hide everything except builder --- src/main/cljs/cljs/proxy.cljs | 24 ++++++------------------ src/main/cljs/cljs/proxy/impl.cljs | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 src/main/cljs/cljs/proxy/impl.cljs diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs index 11b99c4f9..ce4324ff1 100644 --- a/src/main/cljs/cljs/proxy.cljs +++ b/src/main/cljs/cljs/proxy.cljs @@ -1,21 +1,8 @@ (ns cljs.proxy - (:refer-global :only [Proxy isNaN])) - -(deftype SimpleCache [^:mutable obj ^:mutable cnt] - Object - (set [this k v] - (when (== cnt 1024) - (.clear this)) - (unchecked-set obj k v) - (set! cnt (inc cnt)) - v) - (get [this k] - (unchecked-get obj k)) - (clear [this] - (set! obj #js {}) - (set! cnt 0))) - -(defn write-through [f] + (:refer-global :only [Proxy isNaN]) + (:require [cljs.proxy.impl :refer [SimpleCache]])) + +(defn- write-through [f] (let [cache (SimpleCache. #js {} 0)] (fn [x] (let [v (.get cache x)] @@ -23,7 +10,8 @@ v (.set cache x (f x))))))) -(def desc +(def ^{:private true} + desc #js {:configurable true :enumerable true}) diff --git a/src/main/cljs/cljs/proxy/impl.cljs b/src/main/cljs/cljs/proxy/impl.cljs new file mode 100644 index 000000000..56c99430d --- /dev/null +++ b/src/main/cljs/cljs/proxy/impl.cljs @@ -0,0 +1,15 @@ +(ns cljs.proxy.impl) + +(deftype SimpleCache [^:mutable obj ^:mutable cnt] + Object + (set [this k v] + (when (== cnt 1024) + (.clear this)) + (unchecked-set obj k v) + (set! cnt (inc cnt)) + v) + (get [this k] + (unchecked-get obj k)) + (clear [this] + (set! obj #js {}) + (set! cnt 0))) From 5685fa765a717ee1c988b13964aee04897be84cf Mon Sep 17 00:00:00 2001 From: David Nolen Date: Wed, 19 Nov 2025 09:40:42 -0500 Subject: [PATCH 4/5] some more cases in comment --- src/main/cljs/cljs/proxy.cljs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs index ce4324ff1..b6dafabb8 100644 --- a/src/main/cljs/cljs/proxy.cljs +++ b/src/main/cljs/cljs/proxy.cljs @@ -98,6 +98,8 @@ (require '[goog.object :as gobj]) (gobj/get proxied-map "foo") (gobj/get proxied-map "bar") + (gobj/getKeys proxied-map) + (.keys js/Object proxied-map) (time (dotimes [i 1e7] From 84798d35abb349aff828ab1cddf581261bda2997 Mon Sep 17 00:00:00 2001 From: David Nolen Date: Wed, 19 Nov 2025 11:25:04 -0500 Subject: [PATCH 5/5] docstring --- src/main/cljs/cljs/proxy.cljs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs index b6dafabb8..a071868de 100644 --- a/src/main/cljs/cljs/proxy.cljs +++ b/src/main/cljs/cljs/proxy.cljs @@ -16,6 +16,12 @@ :enumerable true}) (defn builder + "EXPERIMENTAL: Return a JavaScript Proxy ctor fn with the provided key-fn. You + can proxy ClojureScript map and vectors. Access pattern from JavaScript + will lazily wrap collection values in Proxy if needed. Note key-fn + is only used for proxied ClojureScript maps. This function should map + strings to the appropriate key representation. All maps proxied from the + same ctor fn will share the same key-fn cache." ([] (builder keyword)) ([key-fn] @@ -93,6 +99,7 @@ (def proxy (builder)) + (def raw-map {:foo 1 :bar 2}) (def proxied-map (proxy {:foo 1 :bar 2})) (require '[goog.object :as gobj]) @@ -105,6 +112,11 @@ (dotimes [i 1e7] (unchecked-get proxied-map "foo"))) + (def k :foo) + (time + (dotimes [i 1e7] + (get raw-map k))) + (def proxied-vec (proxy [1 2 3 4])) (alength proxied-vec) (time