This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-03-20
Channels
- # announcements (5)
- # aws (15)
- # babashka (12)
- # beginners (87)
- # calva (13)
- # cider (16)
- # clj-kondo (4)
- # clojure (22)
- # clojure-argentina (1)
- # clojure-europe (9)
- # clojure-houston (1)
- # clojure-nl (2)
- # clojure-norway (25)
- # clojure-uk (5)
- # clojurescript (12)
- # core-typed (37)
- # cursive (15)
- # datomic (40)
- # editors (8)
- # emacs (4)
- # events (1)
- # hyperfiddle (29)
- # keechma (8)
- # leiningen (6)
- # lsp (7)
- # malli (25)
- # off-topic (26)
- # pathom (10)
- # portal (3)
- # re-frame (22)
- # reitit (1)
- # releases (1)
- # ring (2)
- # shadow-cljs (18)
- # yamlscript (1)
I need to transform a form for saving into EDN format and faithfully restoring it (context: Babashka pods).
Transformation is to consist of replacing objects, which cannot be serialized, by proxy values.
Later, a transformed form needs to have its object proxies replaced by the original objects.
The proxies are represented as {:myns/proxy "some-uuid-string"}
. The code maintains maps of objects->UUID and UUID->objects.
Part of the process is to walk through the form and apply the transformation to all of its leaf nodes, which need it.
However I found that the functions in namespace clojure.walk
are not suitable, because (for example) when transforming back into the original objects, the walk should stop at the proxy representations, without going into them. The proxy representations are, after all, maps with a specific key (namely :myns/proxy
).
Is there any Clojure library which already implements a conditional walk function which uses a predicate to avoid walking into some elements of a nested form?
(walk/postwalk #(if (pred %) (f %) %) form)
?
If the objects being proxied are not walkable themselves, then you can do it with prewalk
:
(let [proxy->obj {:obj1 inc
:obj2 dec}]
(clojure.walk/prewalk (fn [v]
(if (and (map? v)
(contains? v :myns/proxy))
(proxy->obj (:myns/proxy v))
v))
{:ms [{:id 1
:obj {:myns/proxy :obj1}}
{:id 2
:children [{:id 3
:obj {:myns/proxy :obj2}}]}]}))
=> {:ms [{:id 1, :obj #object[clojure.core$inc 0x34cea4c1 "clojure.core$inc@34cea4c1"]}
{:id 2, :children [{:id 3, :obj #object[clojure.core$dec 0x1714587c "clojure.core$dec@1714587c"]}]}]}
If they are walkable, you indeed need something else (unless you can just let the walker walk them, if they are small and have no proxies that need to be replaced). I'm pretty sure that Specter is that something.This?
(defn proxy-kvp? [form]
(and (map-entry? form)
(= :proxy (key form))))
(defn walk-if-not-proxy [form]
(if (proxy-kvp? form)
;; handle how you want
{:ser form}
(clojure.walk/walk
walk-if-not-proxy
identity
form)))
(def data {:id 10
:name "car"
:models
[[{:proxy "some-uuid-string"}]
1
"s"]})
(walk-if-not-proxy data)
;; {:id 10, :name "car", :models [[{:ser [:proxy "some-uuid-string"]}] 1 "s"]}
walk log:
Walking form = {:id 10, :name car, :models [[{:proxy some-uuid-string}] 1 s]}
Walking form = [:id 10]
Walking form = :id
Walking form = 10
Walking form = [:name car]
Walking form = :name
Walking form = car
Walking form = [:models [[{:proxy some-uuid-string}] 1 s]]
Walking form = :models
Walking form = [[{:proxy some-uuid-string}] 1 s]
Walking form = [{:proxy some-uuid-string}]
Walking form = {:proxy some-uuid-string}
Walking form = [:proxy some-uuid-string]
Walking form = 1
Walking form = s
@U2FRKM4TW I wasn't familiar with prewalk
. I tried your solution, and it's good unless the proxy itself is walkable as you mentioned. Example:
(def data {:id 10
:name "car"
:models
[[{:proxy "some-uuid-string"}]
1
"s"]})
(defn proxy-kvp? [form]
(and (map-entry? form)
(= :proxy (key form))))
(clojure.walk/prewalk
(fn [form]
(if (proxy-kvp? form)
{:ser form}
form))
data)
results in stack overflow.The proxy form (`{:myns/proxy "my uuid string"}`) itself is walkable. I want the walk function (whatever it is) to be able to identify the proxy form (`#(and (map? %) (:myns/proxy %))`) and not walk into it.
A general way would be for the walk function to accept, as another parameter, a predicate which will test each form before trying to walk into it, and return true
to allow the form to be walked into; false
in order not to walk into the form.
@U06QERFJXA5 Right, I'm dumb - your solution is not exactly prewalk
since it doesn't continue the walking.
However, there's another problem with it - checking for map entries and not maps requires for the replacement value to be something conj
'able onto a map. But that's easily fixable.
@U06BDSLBVC6 Have you tried Duminda's solution?
@U06BDSLBVC6 what would that predicate look like for the inner forms?
Nevermind I misread AGAIN. Time for another ☕
Looks like I'll have to implement my own walk version, starting from the output of (source clojure.walk/walk)
.
Is there a convention for naming a function, which does dostuff-augmented based upon another function doing dostuff?
Unless there is a better suggestion, I'll call it walk-cond
whose definition is a copy-paste-modify of walk
.
Then I'll have postwalk-cond
which is the same as postwalk
except for invocation of walk-cond
.
Is this a problem you can avoid? E.g. if all the inner forms are maps you could put the :myns/proxy
everywhere in a preprocessing step.
@U06BDSLBVC6 I learned about walk by trying to fix your problem. So thanks!
This is my understanding from this exercise:
the "conditional walk" that you need is already there in clojure.walk/walk
itself. The inner
function is the filter in this case. It's already there and no need to reimplement imo.
https://clojurians.slack.com/archives/C03S1KBA2/p1710929895524319?thread_ts=1710903781.294179&cid=C03S1KBA2 There is a pathological case, in which @U06QERFJXA5’s solution can cause stack overflow. I cannot guarantee 100% that such a pathological case can never occur.
https://clojurians.slack.com/archives/C03S1KBA2/p1710930022788909?thread_ts=1710903781.294179&cid=C03S1KBA2
Inner forms can be just anything. I need a way to match a form even if it is a collection and conditionally prevent walk
from descending into it.
https://clojurians.slack.com/archives/C03S1KBA2/p1710936280596239?thread_ts=1710903781.294179&cid=C03S1KBA2
The "inner" function does not act as a filter (at least from my understanding of clojure.walk/walk
's source code).
@U2FRKM4TW, See the note at end of https://clojurians.slack.com/archives/C03S1KBA2/p1710924947457629?thread_ts=1710903781.294179&cid=C03S1KBA2 I did not check it myself, as I am relying upon his words.