This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-01-08
Channels
- # announcements (10)
- # beginners (53)
- # calva (22)
- # clj-kondo (21)
- # cljs-dev (1)
- # clojure (18)
- # clojure-europe (2)
- # clojure-filipino (1)
- # clojure-indonesia (1)
- # clojure-my (1)
- # clojure-seattle (6)
- # clojure-sg (1)
- # clojurescript (60)
- # code-reviews (3)
- # conjure (1)
- # cursive (1)
- # datomic (2)
- # figwheel-main (1)
- # fulcro (2)
- # gratitude (1)
- # honeysql (35)
- # lsp (6)
- # malli (5)
- # meander (11)
- # off-topic (27)
- # other-languages (3)
- # play-clj (1)
- # portal (31)
- # sql (13)
- # xtdb (5)
What is going on underneath here that makes such a difference?
(defn fn1 [f xs]
(loop [[x & ?more] xs acc 0]
(if x
(recur ?more (f acc x))
acc)))
(defn fn2 [xs]
(loop [[x & ?more] xs acc 0]
(if x
(recur ?more (+ acc x))
acc)))
(let [xs (vec (range 1e5))]
(enc/qb 1e2
(fn1 + xs)
(fn2 xs)))
;; => [879 541] ; milliseconds
functions are basically the same, except that one takes +
as arg and the other uses it directly
thanks to http://app.klipse.tech/, it can be seen that f.call
is used in the first case, but how does reduce
not have a problem with it?
In fn1
, the function is used. In fn2
, the macro, which in JS becomes just plain acc + x
.
(let [xs (vec (range 1e5))]
(enc/qb 1e2
(fn1 (fn [x y] (+ x y)) xs)
(fn2 xs)))
;; => [637 553]
In this case, it may be the fact that (fn [x y] ...)
is a function with a fixed arity of 2 whereas +
is a function with arbitrary arity which in JS becomes (at least when I check) a switch
on the number of its arguments.
Also, if you really care about performance you should try switching from destructuring to indexing, at least when you know for sure that your argument is a vector and not a list/seq/something else.
And I'm not sure what exactly you're trying to do here, but you can definitely just (apply + ...)
or maybe use reduce
.
You also asked:
> how does `reduce` not have a problem with it?
But you didn't use reduce
above. What is its timing?
Ah, and yeah - it still has the problem with +
being a function with arbitrary artity. But it's probably faster just because it doesn't use destructuring.
I am interested in whether it is possible to somehow get rid of the overhead of passing f
as an argument in CLJS
. In CLJ
this problem does not exist.
CLJ
(let [xs (vec (range 1e5))]
(enc/qb 1e2
(fn1 + xs)
(fn2 xs)))
;; => [144.97 144.17] ;ms
I discovered this "problem" by accident, because my fast functions after moving to CLJS were no longer fast 😛
Oh maybe it's your REPL, whatever you're using for it.
For your initial example, in a debug build of an app running in Chrome, I got [945 926]
- barely any difference.
Interesting. As far as I'm concern, it should work the same, unless we have versions of Chrome that are drastically different. There's a chance something might've happened in CLJS itself, but I doubt that. But also, performance measurements can be finicky. Different browsers, different flags, different platforms, slightly different JS code,...
Some additional measurements from my end:
[:destructure+fn 922]
[:destructure+macro 864]
[:index-loop+fn 699]
[:reduce 195]
[:apply 202]
So maybe something strange is going on with shadow-cljs' browser REPL. I haven't tried that - I just chucked that code into an existing app and ran it from there.
I will check in firefox, and if there are identical anomalies, I will ask on shadow-cljs
channel
Also, if your loops are really tight and you're operating on some data that previously was JS arrays, it might make sense to keep using arrays and maybe even JS's reduce
. There won't be anything faster than that.
https://ask.clojure.org/index.php/10303/interop-clojure-pattern-clojure-consider-adding-iter-clojure
quite a few libraries use iter
, because although it is not documented and is supposedly hidden, it is much faster
I understand what you mean, but every clojure collection is iterable
, so you can use this without any problem
generally there is no need to use it in 99.9% of cases, but in this 0.1% it may be useful
Depends on what you mean by "without any problem".
My whole point is that cljs.core/iter
might not be public, even though it doesn't use defn-
- simply because it has no docstring for some reason.
non-public functions can also be used, but I don't think that's what this is all about
not sure yet how to attach chrome dev tools to a node REPL but that's where I'd start I see you're using a browser REPL so it should be easy to use the Profile tab in Chrome dev tools
you might also find that things that are slow from a REPL are fast when used in your application, because most CLJS apps are released using advanced optimizations which can inline things, remove IIFEs and other optimizations
hey, I'd like to understand better why js->clj
sometimes doesn't work, and what I can do about it.
Consider this example, when there is some selected text
(js->clj (js/document.getSelection))
;; => #object[Selection voluptate]
(type (js->clj (js/document.getSelection)))
;; => #object[Selection]
(type (js/document.getSelection))
;; => #object[Selection]
I want the actual clj
map corresponding to that object instead.
I think one reason here could be that the selection is not just a js Object, but a Selection, and if that's the case I'd like to know why that's a problemBecause js->clj
is intended for plain JS objects, not for instances of particular classes. I don't know for sure but my guess is that it's done to prevent people from overusing it, like you're trying to do in your scenario - if you want some particular fields, just access those fields via interop.
Here's why I care: the fact that I can't visualize the converted value in my editor breaks my repl workflow; maybe there's a better way of doing that. Is this restriction added for performance reasons?
So all you want is to output the value in a readable way in a plain-text REPL?
If so, you definitely should look into tap>
and how to use it along with Portal or Reveal. Alternatively, as an easy way of doing similar thing once, you can simply (js/console.log ...)
that value and check it out in full in your browser's JS console.
It's not for performance reasons. js->clj
is for plain data, that's it. It was never intended to work with instances of classes. (at least, that's my personal take on it)
If you really do prefer to use plain text REPL over much more rich tools like Portal and Reveal, then you can look into cljs-bean and use it instead of js->clj
.
FWIW, cljs-bean
was written with plain data in mind as well, but it might work to expose regular objects in a suitable way. (There have certainly been a few bug reports related to that use when people hit corner cases that aren't handled cleanly.)
$ nbb -e "(require '[cljs-bean.core :as b]) (def c (b/bean js/console)) ((:log c) \"hello\")"
hello
similarly:
$ nbb -e "(require '[applied-science.js-interop :as j]) (def c (j/lookup js/console)) ((:log c) \"hello\")"
hello