Fork me on GitHub

cljs.core/test doesn’t seem to find the :test function in metadata:

$ clj
Clojure 1.9.0
user=> (defn ^{:test #(assert false)} foo [] nil)
user=> (test #'foo)
AssertionError Assert failed: false  user/fn--145 (NO_SOURCE_FILE:1)
user=> ^D

$ clj -m cljs.main -r
ClojureScript 1.10.439
cljs.user=> (defn ^{:test #(assert false)} foo [] nil)
cljs.user=> (test #'foo)
cljs.user=> ^D

$ planck
ClojureScript 1.10.439
cljs.user=> (defn ^{:test #(assert false)} foo [] nil)
cljs.user=> (test #'foo)


@shaunlebron My guess would be that because it existed before the rudimentary support for vars was added, so instead you pass the function value


Maybe things could be revised


@mfikes ah, you’re right, thanks:

$ clj -m cljs.main -r
ClojureScript 1.10.439
cljs.user=> (defn ^{:test #(assert false)} foo [] nil)
cljs.user=> (test foo)
Error: Assert failed: false


Maybe worth a JIRA to see if we can clean it up a bit


i’ll file and post here


I hope people don't mind another question about analyzer behavior. When analyzing specter the analyzer is throwing an Unable to resolve var: coll? in this context at line 1449. coll? being a built in I'm unsure how this is possible? (link to code in thread)


Found a discrepancy between clj + cljs:

(subs "" 1) ;; throws in clj
(subs "" 1) => "" ;; in cljs
Is this something that should be fixed, or is it “let the host platform deal with it”. There is code that depends on this behavior (at least re-seq does)


(subs "" :a :b)
also works in cljs. it seems you can pass whatever you want and it returns an empty string if it doesn’t make sense


I want to say that all are undefined behavior. (Also, any attempt to do anything about it messes up the “optimized for correct programs” theme.)


@mfikes I think I’ll need to make a special case for the subs spec in cljs then, because currently re-seq runs into trouble with it


Maybe re-seq could be revisited if it is relying on undefined behavior?


I’ll make an issue for it


Really, if we absolutely need to, re-seq could call .substring directly, which is a little gross. But, arguably if it is relying on JavaScript semantics for some reason, maybe it shouldn’t be calling subs


Huh, looking at the source for re-seq I think I also see an opportunity for improving performance: it's searching the string twice by running .search to find the index after running re-find. re-find already uses re.exec to find the match, it's just discarding the index you get from .exec. It could skip having to search the string twice by calling .exec directly.


Interesting. I’m trying this in a Planck REPL:

(let [regex #"a", s "aaaa"] (while (let [res (.exec regex s)] (println (.-lastIndex regex)) res)))
but the loop never terminates.


@jesse.wertheim .exec only returns the match right, not the index?


it sticks the index onto the array via an .index property


which is admittedly kinda weird, but that's javascript for you


is this supported on all platforms, browser, node etc?


yep, it's part of the spec


do you have a link to the spec? I’m reading Ecma 2018 and it says: > returns an Array object containing the results of the match, or null if string did not match.


poking through it now 😉


It's possible I'm conflating it being in spec with the lastIndex property of (stateful) global regular expressions - if I can't find it here I'll bug someone in TC39 about it


@U04V15CAJ ahh it's more explicit going back to ECMA 5.1's spec version - there's more indirection in the latest spec since it refers to RegExpBuiltinExec.


(see item 15 in that list)


so this is 2011? how far back does cljs want to support standards?


I dunno. I didn't refer back further - it could go back to ES 5 or even ES3

jaawerth20:11:02 talks about the index stuff and at the bottom there are various spec links - I just opened ES3 to see if I can find it there


(that's MDN - the earlier devdocs link just scrapes it - I use devdocs since it's faster to search than MDN itself)


> 13. Return a new array with the following properties: • The index property is set to the position of the matched substring within the complete string


so yeah it dates back to at least 1999


what’s the maximum ecmascript standard that cljs wants to support? specifically, is it ok to rely on the .-index property in the array resulting from a call with (.exec #"a" "a").


@jesse.wertheim points out it dates back to at least 1999 (link:,%203rd%20edition,%20December%201999.pdf). if we could use this in re-seq, it could be further optimized.


should be good then


@jesse.wertheim it seems to help a little bit:

cljs.user=> (simple-benchmark [re #"\d+" s "a1b22c333d"] (re-seq re s) 100000000)
[re #"\d+" s "a1b22c333d"], (re-seq re s), 100000000 runs, 50975 msecs
cljs.user=> (simple-benchmark [re #"\d+" s "a1b22c333d"] (cljs.core/re-seq re s) 100000000)
[re #"\d+" s "a1b22c333d"], (cljs.core/re-seq re s), 100000000 runs, 53529 msecs


for longer strings, the difference may become more significant


yeah - I imagine with direct benchmarking the JS jit engine might be able to optimize things a bit to reduce the difference, but that might vary in the context of a full application as it prioritizes what to optimize


but to your point it could make for a big difference for longer strings, which are going to be a good usecase for re-seq


I imagine that a global JS regex might be faster since you can just keep calling exec and look at the lastIndex property of the global regex, but it probably would lose out since you have to do something nasty under the hood like (RegExp. (.-source original-re) "g") and dynamically rebuilding the regex using the constructor would be expensive


correction: converting to a JS global RegExp would prevent needing to use subs at all (unless you wanted to include it in the result), since each exec gives you the matches, but it would probably only really win for long strings because of the cost of using the RegExp constructor


could that still be implemented lazily?


anyway feel free to try. the latest version I had was:

(defn re-seq
  "Returns a lazy sequence of successive matches of re in s."
  [re s]
  (when-let [matches (.exec re s)]
    (let [match-idx (.-index matches)
          match-str (first matches)
          post-idx (+ match-idx (max 1 (count match-str)))
          matches (if (== (count matches) 1)
                    (first matches)
                    (vec matches))]
      (lazy-seq (cons matches
                      (when (<= post-idx (count s))
                        (re-seq re (subs s post-idx))))))))


it can be, but it's kinda gross and I think it's probably best to leave it using non-global regular expressions (since they don't exist in clj/cljs anyway). But this is what I mean - you would convert the existing regex into a global one and then just keep calling .exec until it returns null. Should be fine since the mutable global regex only exists within the lazy-seq, but doing this conversion dynamically is expensive compared to using a regex literal:

(require 'clojure.string)

(defn- re-seq-g* [global-reg s]
      (.exec global-reg s)
      (cons (re-seq-g* global-reg s)))))

(defn re-seq-g [pattern s]
  (let [[src flags]
        (if (string? pattern)
          [pattern "g"]
          [(.-source pattern) (clojure.string/join (into #{"g"} (.-flags pattern)))])
        global-reg (js/RegExp. src flags)]
    (re-seq-g* global-reg s)))


to be clear that's just a way it COULD be done - I'm not seriously suggesting this be the implementation 😉


I haven’t bothered. The most recent one is in the -2 patch:


1.6-1.8x speedup, seems nice


nice! yeah I just wrote the above to show what i was talking about - I'm vaguely curious whether it would have better perf in some usecases but even if it did, I'm guessing it wouldn't be worth the dynamism of RegExp.


oh and with the above I didn't turn it into a string rather than vector for the case of no subgroup matches


so more like:

(require 'clojure.string)
(defn- re-seq-g* [global-reg s]
      (.exec global-reg s)
      (as-> match-data
        (if (= 1 (.-length match-data))
          (aget match-data 0)
          (vec match-data)))
      (cons (re-seq-g* global-reg s)))))

(defn re-seq-g [pattern s]
  (let [[src flags]
        (if (string? pattern)
          [pattern "g"]
          [(.-source pattern) (clojure.string/join (into #{"g"} (.-flags pattern)))])
        global-reg (js/RegExp. src flags)]
    (re-seq-g* global-reg s)))