I’m trying to do a z/remove on a keyword that is a key in a map eg, {:key "fred"} I know I’m looking at :key as z/sexpr returns that.
However, I’m getting “cannot remove at top” when I do z/remove
Also, if I want to return the key and value is this correct? (-> key-loc z/remove z/remove)?
It still does not add up for me, Joel. How would rewrite-clj restore to a location that does not exist?
Hiya @joel380, please hold while I fire up my REPL.
Ok, let's do some setup:
(require '[rewrite-clj.zip :as z])
(def s "{:key \"fred\"}")
(def zloc (z/of-string s))
If you want to remove :key you'd need to navigate to the :key node first, for example:
(-> zloc z/down z/string)
;; => ":key"
To remove :key node, navigate to it, then remove it:
(-> zloc z/down z/remove z/root-string)
;; => "{\"fred\"}"
I'm not sure if you made a typo, did you mean? "Also, if I want to remove the key and value is this correct?" You could do this:
(-> zloc
z/down ;; at :key
z/right ;; at "fred"
z/remove ;; at :key after removing "fred"
z/remove ;; at {} after removing :key
z/root-string)
;; => "{}"Does that all make sense and get you unstuck?
The weird thing, is my zloc says it’s at :key, but I can’t do the z/remove.
(prn "remove" (-> zloc z/sexpr)) ; => remove :key
(z/remove zloc) ; => "cannot remove at top"oh, because I’m doing it within a subedit, why is that a problem?
because presumably the z/subedit-> makes it become the top i suppose.
Yeah that makes sense to me.
that’s seems a bit odd though, it’s not really the top, although I don’t understand the significance of being at the top… I couldn’t remove the ns from the top of a file?
Oh. Yeah. I think you are right. Let me go back to my REPL.... please hold.
Oh. So. A subedit will try to preserve your location in the zipper, but you are removing that location, no?
Yes, I’m removing that key and the following value from the map starting at the location of the key.
What problem is subedit solving for you? Do you need to use it?
Actually, IDK really. I had done something similar where I was just doing z/replace of multiple locations and it wasn’t working until I told it to use subedit.
I figured I’d do the same approach for z/remove… I’m removing multiple locations.
Subediting can be useful. If you need to preserve your location in the zipper. Maybe you figured that was useful?
Yeah, for the replacements, I think the locations were no longer valid unless I used subedit.
But it looks like remove works without it which I find odd as I’m still using a find-by-position function to find the next spot to edit, and it seems to be working fine.
I thought subedit was preserving the following locations so I could still find them. But now my mental model of what’s happening is broken 😞
I’m grasping the positions and iterating through them applying edits to each position.
Have you read through the https://cljdoc.org/d/rewrite-clj/rewrite-clj/1.1.49/doc/user-guide#sub-editing yet?
Subedit tries to preserve the current location. Can totally make sense to use it.
Doesn’t it preserve all locations?
Well, a zipper wraps a tree of nodes and tracks the current location. As you move through the tree you affect the current location. Does this help?
(-> "(fn [] {:a 1 :b 2})"
z/of-string ;; location is at (fn ..)
z/down ;; location is at fn
z/right ;; location is at []
z/right ;; location is at map
z/down ;; location is at :a
z/rightmost ;; location is at 2
z/remove ;; location is at :b
z/remove ;; location is at 1
z/string)
;; => "1"
(-> "(fn [] {:a 1 :b 2})"
z/of-string ;; location is at (fn ..)
z/down ;; location is at fn
z/right ;; location is at []
z/right ;; location is at map
(z/subedit-> z/down z/rightmost z/remove z/remove)
;; location is preserved, still at map
z/string)
;; => "{:a 1}"(-> "(fn [] {:a 1 :b 2}) ()"
z/of-string
z/remove)
This doesn’t complain, why does subedit?(-> "(fn [] {:a 1 :b 2}) ()"
z/of-string
(z/subedit->
z/remove))
blows upI think because you are removing the current location? And subedit wants to preserve the current location?
I typically use subedit when it is challenging to restore my location in the tree after making changes down the tree.
hmm, that’s subtle but makes sense. I need to look at my other usage better to understand why it broke without it (subedit).
The word "sub" in "subedit" is important to remember.
Happy to help if you get stuck.
i think a key here is that z/remove moves position to the left, but there is no left.
When you parse forms into a zipper there is a :forms node that represents the top-level implicit do.
(-> ":foo :bar :baz"
z/of-string
z/up
z/tag)
;; => :forms
So even when you remove :foo :bar and :baz, you still haven't removed the top level :form:
(-> ":foo :bar :baz"
z/of-string
z/rightmost
z/remove
z/remove
z/remove
z/tag)
;; => :forms
But if we add one more remove you'll see the "remove at top" exception:
(-> ":foo :bar :baz"
z/of-string
z/rightmost
z/remove
z/remove
z/remove
z/remove)
;; => Execution error at rewrite-clj.custom-zipper.core/remove (core.cljc:273).
;; Remove at top
A subedit does not have the extra :forms node:
(defn my-silly-fn [zloc]
(try
(println "tag" (z/tag zloc))
(println "node" (z/string zloc))
(println "up" (z/up zloc))
(z/remove zloc)
(catch Exception ex
(println "ex" (ex-message ex))))
zloc)
(-> ":foo"
z/of-string
(z/subedit-> my-silly-fn))
Prints:
tag :token
node :foo
up nil
ex Remove at top
(the remove threw because we were at top within subedit)
If we repeat without subediting:
(-> ":foo"
z/of-string
my-silly-fn)
Prints:
tag :token
node :foo
up [<forms: :foo> nil]
(the remove did not throw because we were not at top)Does that help?
It explains why the behavior differs at “doc level” vs. “subedit level”, but should the behavior differ?
I feel it is acceptable, yes. An alternative that somehow supports locating to a deleted subedit root node would be more confusing than helpful.
But happy to be convinced otherwise if you think of something that makes sense for general use.
Just for consistency.