Fork me on GitHub

two additional approach ideas for dealing with sexpr


1) same as earlier idea of a new protocol, but name its method something like sexpr2, and don't remove sexpr from the Node protocol -- this is a change, but it doesn't break existing code?


2) add a sexprable? method to the Node protocol -- all it does is tell you if it's safe to call sexpr


in approach 1, may be existing sexpr implementations can be moved to external functions and those functions can be called from the protocol methods. both the Node protocol sexpr and the new protocol sexpr2 can call these externalized functions. newer code can use sexpr2 and other code can migrate to sexpr2 gradually. satisfies? can be used to check whether it's safe to call sexpr2 before use.


the earlier idea of just having sexprable? has the downside of maintainers having to remember to update it appropriately if node implementation details change in certain ways over time.


here is some scratch work for sexprable?

(require '[rewrite-clj.node :as rn])

(defn uneval?
  "Check whether a node represents an uneval."
  (= (rn/tag node) :uneval))

;; following things will throw when sexpr is called:
;;   comma,
;;   newline,
;;   whitespace
;;   comment
;;   uneval
;; this could throw:
;;   some reader nodes
;; however, afaict, all currently implemented reader nodes (:var and :eval) don't throw by default
(defn sexprable?
  "Check whether sexpr can be safely called on node."
  (not (or (uneval? node)
           (rn/whitespace? node)
           (rn/comment? node))))


so, I don’t know @sogaiu. There are other reasons to stay away from sexpr. 😬 For example, let’s say your cljs app is sexpr-ing clj code that is not cljs compatible. Like a ratio for example. Looking at these kind of sexpr language incompatibilities is on my todo list.


I would not go so far a deprecating sexpr but I’m thinking I’ll give strong guidance in docs on only using it in specific cases where you have a very good idea of what you are sexpr-ing. For that reason, I am wondering if further work on sexpr is prudent. Whadya think?


but the extra predicates seem fine to me.


the case you described about a cljs app working with clj code is understandable, but it seems like the kind of thing where a warning would do. not sure yet -- need to digest it more fully. i'm interested in hearing about other reasons to stay away from sexpr. please share any further thoughts. i need to go through all the places i used sexpr in more detail -- perhaps i can manage that in the next day or so 🙂 may be you know this already, but for reference, all of the following rewrite-clj-using projects use sexpr (some much more than others): i haven't looked in detail how it's used though.


thanks for the list!


@lee regarding extra predicates, borkdude has the following in clj-kondo's impl/utils.clj:

(defn boolean-token? [node]
  (boolean? (:value node)))

(defn char-token? [node]
  (char? (:value node)))

(defn string-token? [node]
  (boolean (:lines node)))

(defn number-token? [node]
  (number? (:value node)))

(defn symbol-token? [node]
  (symbol? (:value node)))
i'm not sure about the naming (e.g. in some rewrite-clj/node/*.cljc there are comment?, comma?, etc.), but it'd be nice to have at least the string and symbol predicates -- may be having "-token" helps prevent collisions w/ clojure's built-in predicates? having those would help with some of the usages of sexpr i'm finding. also, there are zip versions of list?, vector?, set?, map?, whitespace?, comment?, ... -- any thoughts on these and/or more of these types? i know i use list?, vector?, and map?. it would be nice to have something for string? and symbol? too -- though i also wonder about appropriate names for these.


yeah, I’m not sure about the naming either, but since these are not part of my API I didn’t worry about it 🙂


also these predicates already assume that the node is a token. for efficiency


thanks for the clarifications 🙂


one other usage of sexpr i appear to have repeatedly is for getting at values of things, e.g. string, symbol, first item in list. i noticed in clj-kondo's impl/util.clj the following:

(defn symbol-call
  "Returns symbol of call"
  (when (= :list (node/tag expr))
    (let [first-child (-> expr :children first)
          ?sym (:value first-child)]
      (when (symbol? ?sym)
for string and symbol, i guess the guts of the -token predicates (e.g. (:value token)) above might work. may be those guts, after being externalized into functions, can be called from the predicates?