rewrite-clj

https://github.com/clj-commons/rewrite-clj
lread 2020-11-23T19:12:25.093900Z

Hey @borkdude, I got a chance to slim down namespaced elements design notes for rewrite-cljc. Some details are sure to change as I move to implementation, but would love to have a critical eye, when you have a moment, to review for problems/opportunities. Content is on same branch but moved to a https://github.com/lread/rewrite-cljc-playground/blob/lread-ns-kw-map/doc/design/namespaced-elements.adoc for, hopefully, a less overwhelming review experience.

lread 2020-11-23T19:13:04.094700Z

Am happy to get feedback from any others with an interest too! ❤️

borkdude 2020-11-23T19:15:25.095700Z

@lee with: > Same as 3 but also ensure backward compatibility with current rewrite-clj implementation you should keep an eye on binary size with GraalVM binaries as this relies on *ns*. I'm not a big fan of using that at runtime in GraalVM binaries as this can result in bloat.

borkdude 2020-11-23T19:17:05.096300Z

> but automatically parsing and returning a technically correct sexpr for auto-resolved namespaced elements is a rabbit hole that we’ll reject for now. agreed, I'll skip over the rabbit hole part

lread 2020-11-23T19:17:38.097100Z

yeah rabbit hole section is summary of you and I discussing this ages ago.

borkdude 2020-11-23T19:17:40.097200Z

> fall back to ns if the current namespace is not specified by caller. same comment as above, should be careful about this

lread 2020-11-23T19:18:56.098300Z

Noted, thanks. Maybe I’ll do some sort of http://grep.app to try to learn if any public repos that use rewrite-clj are even using this.

lread 2020-11-23T19:20:09.099600Z

Or… it could just be a documented breaking change.

borkdude 2020-11-23T19:21:11.100200Z

About the *ctx*: have you also considered just passing an extra opts map to sexpr? Dynamic vars do not work well with laziness. Passing in explicit opts is often preferred:

(sexpr foo opts)
e.g. see https://github.com/clojure/tools.reader/blob/master/src/main/clojure/clojure/tools/reader/edn.clj#L379-L380

borkdude 2020-11-23T19:21:38.100900Z

I once did a massive refactor on a commercial project rewriting dynamic vars to explicit opts because of laziness problems

lread 2020-11-23T19:22:54.102200Z

Hmm… interesting. I did consider it but sexpr is also used internally. I could have a look at impact.

borkdude 2020-11-23T19:23:15.102700Z

you can make an extra arity of course, opts being optional

lread 2020-11-23T19:24:05.104Z

ya, makes sense. I knew that passing over dynamic vars was preferred but did not know about laziness problems. I’ll take a gander.

borkdude 2020-11-23T19:24:40.105Z

About creating map nodes: the interface looks fine to me, but I'd also like to see the same record type backing both types of maps, so writing predicates for map? nodes becomes trivial

borkdude 2020-11-23T19:24:46.105200Z

This is my feedback.

lread 2020-11-23T19:26:14.106300Z

Yep, plan is to have MapNode now also cover namespaced maps.

👍 1
borkdude 2020-11-23T19:27:51.108100Z

A breaking change seems fine with respect to sexpr and namespaced maps. I think this was one of the more recent features in rewrite-clj and it feels more like it got bolted on (with all respect)

lread 2020-11-23T19:29:08.109400Z

Yeah I’m cool with totally breaking namespaced maps, was more concerned about breaking namespaced keywords.

lread 2020-11-23T19:29:35.109700Z

Do you have any comments on https://github.com/lread/rewrite-cljc-playground/blob/lread-ns-kw-map/doc/design/namespaced-elements.adoc#sexpr-behaviour? I’m wishy washy on last 2 behaviours.

borkdude 2020-11-23T19:30:56.111100Z

One problem I ran into with clj-kondo and *ns* was, when sexpr-ing namespaced maps from completely unrelated code (clj-kondo lints any code) you have to create namespaces to be able to get those. Which is crazy.

lread 2020-11-23T19:31:52.111500Z

Yeah, understood.

borkdude 2020-11-23T19:33:55.112300Z

the behavior of sexpr based on your config map seems ok to me. I do a similar thing here in edamame:

(parse-string "[::foo ::str/foo]" {:auto-resolve '{:current user str clojure.string}})
;;=> [:user/foo :clojure.string/foo]
but informationally I think it's pretty much the same

lread 2020-11-23T19:35:33.112900Z

cool, what does edamame do when alias str is not provided?

borkdude 2020-11-23T19:35:58.113500Z

I think some breaking changes are justified and if you are going to make them, it will be best to make the in the first release of your new lib (and document them somewhere).

borkdude 2020-11-23T19:36:12.113700Z

> cool, what does edamame do when alias str is not provided? exception

borkdude 2020-11-23T19:36:46.114300Z

but it also supports a default I think

borkdude 2020-11-23T19:36:50.114500Z

let me check

borkdude 2020-11-23T19:37:58.114900Z

user=> (e/parse-string "::foo" {:auto-resolve (constantly 'foo)})
:foo/foo

borkdude 2020-11-23T19:38:09.115300Z

user=> (e/parse-string "::s/foo" {:auto-resolve (constantly 'foo)})
:foo/foo

borkdude 2020-11-23T19:38:34.115700Z

user=> (e/parse-string "::s/foo" {:auto-resolve (constantly nil)})
Execution error (ExceptionInfo) at edamame.impl.parser/throw-reader (parser.cljc:92).
Alias `s` not found in `:auto-resolve
`

lread 2020-11-23T19:39:01.115900Z

Interesting.

lread 2020-11-23T19:39:51.117200Z

So I’d likely setup something similar for rewrite-cljc but default it to something that never throws.

borkdude 2020-11-23T19:39:57.117400Z

It was designed with a map of options in mind first, but later on this was made more flexible, so you can just pass in a function of one arg

lread 2020-11-23T19:41:51.118200Z

I found my initial stab underwhelming here - at alias not found behaviour:

(binding [*ctx* {:ns-aliases {'a1 'another.ns.a1
                             'str 'clojure.string}]
  (sexpr (parser/parse-string "::nope/foo"))
  ;; => :_<?namespace-not-found?>_/foo
  (sexpr (parser/parse-string "#::nope{:a 1 :b 2}"))
  ;; => {:_<?namespace-not-found?>_/a 1 :_<?namespace?>_/b 2}
)

borkdude 2020-11-23T19:42:04.118500Z

@lee You could do this:

user=> (e/parse-string "::s/foo" {:auto-resolve (fn [alias] (get {:current 'foo 'str 'clojure.string} alias 'unresolved))})
:unresolved/foo

borkdude 2020-11-23T19:43:24.119100Z

yeah that could work too:

user=> (e/parse-string "::s/foo" {:auto-resolve (fn [alias] (or (get {:current 'foo 'str 'clojure.string} alias) (symbol (str alias "-unresolved"))))})
:s-unresolved/foo

borkdude 2020-11-23T19:43:41.119300Z

not a bad idea

lread 2020-11-23T19:44:31.119500Z

ah… interesting.

lread 2020-11-23T19:46:08.120900Z

and probably not a terrible default (I think the majority of folks will not provide any namespace options).

lread 2020-11-23T19:52:40.123800Z

Should I be more obscure with my default unresolved? Instead of :s-unresolved/foo would :<?s-unresolved?>/foo be silly?

lread 2020-11-23T20:07:32.128Z

Anyway, thanks @borkdude, in summary to recap your major points: 1. consider not supporting *ns* for backward compatibility. Not only is is a bloater for GraalVM, it is also an awkward mechanism for, at least this, use case. 2. consider passing an optional opts over using dynamic vars for namespace info. Dynamic vars can cause issues with lazyness. 3. in general, consider breaking compatibility where things are obviously amiss.

borkdude 2020-11-23T20:08:15.128300Z

well summarized!

lread 2020-11-23T20:10:08.129400Z

Thanks! I’ll take another stab tomorrow and maybe start implementing to feel out issues.

lread 2020-11-23T20:10:50.129900Z

And, as always @borkdude, thanks for your beautiful big brain and kind heart!

borkdude 2020-11-23T20:11:08.130100Z

Likewise!

❤️ 1