This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-02-19
Channels
- # announcements (1)
- # architecture (8)
- # babashka (8)
- # beginners (68)
- # biff (1)
- # calva (2)
- # clj-kondo (13)
- # cljs-dev (2)
- # clojure (71)
- # clojure-art (26)
- # clojure-europe (14)
- # clojure-nl (10)
- # clojure-uk (4)
- # clojurescript (96)
- # community-development (6)
- # conjure (1)
- # datalog (2)
- # emacs (6)
- # fulcro (20)
- # hugsql (7)
- # lsp (6)
- # nextjournal (13)
- # off-topic (7)
- # portal (1)
- # reagent (5)
- # reveal (8)
- # sci (50)
- # shadow-cljs (8)
- # spacemacs (2)
- # tools-deps (9)
- # xtdb (6)
Hi folks: I'm looking for suggestions, or tips, for a library. I want something like re-frame - I need to listen to events (onClick, onChange, onKeyUp, etc) and make then trigger other events/do actions.
The trouble: these need to be configurable in user-space (imagine a user changing keybindings on an editor - that's exaclty one of the cases), it needs to be fast, and it needs to allow for async and sync dispatch (for example, if the user listen to an event onChange
in an input, and decide to revert whatever was typed, I don't want the UI to flicker).
The idea is to prototype a code editor. I'm even thinking about intercepting datascript transact!
so I could have the app state in a quasi-real database, but I'm worried about subscribing to events. So, anyone have any suggestion? Even things like "yeah, re-frame is fast enough" can help! Thanks a lot! 🙂
Hi, all. Say, I am in position to add a clojuresript frontend to the existing clojure/java project. And I am also don't want to duplicate my "model" (entity maps, specs and so on) logic. Is it is acceptable approach to move model logic to the cljc files so it can be shared between frontend and backend? I am really interested in opinions here. I have seen a logic shared in cljc files, but I didn't seen (yet) a model logic shared in this way.
(defprotocol Foo)
(instance? Foo (reify Foo))
This returns true
in Clojure but false
in CLJS. What is the most performant alternative: implements?
implements?
isn't documented but reading it I think that it would be faster than satisfies?
- at least, it is nearly the same code just eliding the branch that checks native-satisfies?
@lilactown would this be a good reason to introduce a deftype
that implements a protocol rather than using reify
?
the implements?
check isn't in a very hot spot though, so using implements?
or even satisfies?
is fine here. just wonder if there are other benefits to not using reify and rather moving to a deftype
in CLJS. it still seems to be very fast with reify
hmm
cljs.user=> (defprotocol Foo)
false
cljs.user=> (deftype Bar [] Foo)
cljs.user/Bar
cljs.user=> (instance? Foo (->Bar))
false
Making a type seems to have a performance edge when invoking the protocol function on it though
@borkdude this is not a property of reify
but protocols in general. they do not add themselves as parent objects in JS prototype chains, so instance?
will always return false if you're checking if something implements a protocol
it seems weird to me that it works on the JVM tbh. I assume it's a property of how protocols are reified and interact with the Java object model
if I make a type I can just do the instance check on the type though, this is what I did before
that's true. if you know something will always be of a concrete type, it's much faster to check
would there be any reason why invoking a protocol fn on a reify would be slower than on a typed instance (deftype) in CLJS?
I'd have to see the code. I would think that dispatching would be the same, but constructing the object may be slower for reify
I did a huge refactor to go from a type that wraps a function to a protocol because it was slightly faster in Clojure, but now I'm discovering it's way slower in CLJS :/ I hope I can speed it up using some tricks still.
> from a type that wraps a function to a protocol
not sure what this means. I thought you were asking about protocols via reify
vs protocols via deftype
yes. so first I had this:
(deftype Foo [f])
(if (instance? Foo ...) ((.-f obj) ... ...) ...)
and now I went to:
(defprotocol IFoo (invoke [_]))
(invoke (reify IFoo (invoke [_])))
ok, and what about
(defprotocol IFoo (invoke [_]))
(deftype Foo [f] IFoo (invoke [_] (f)))
(let [foo (->Foo)]
(invoke foo))
?method/property invocation is quite fast in JS. hard to get faster than ((.-f obj) ,,,)
you can see what the JS code is doing for this code
(defprotocol Foo
(bar [o]))
becomes
cljs.user.Foo = function(){};
cljs.user.bar = (function cljs$user$bar(o){
if((((!((o == null)))) && ((!((o.cljs$user$Foo$bar$arity$1 == null)))))){
return o.cljs$user$Foo$bar$arity$1(o);
} else {
var x__18528__auto__ = (((o == null))?null:o);
var m__18529__auto__ = (cljs.user.bar[goog.typeOf(x__18528__auto__)]);
if((!((m__18529__auto__ == null)))){
return m__18529__auto__.call(null,o);
} else {
var m__18526__auto__ = (cljs.user.bar["_"]);
if((!((m__18526__auto__ == null)))){
return m__18526__auto__.call(null,o);
} else {
throw cljs.core.missing_protocol.call(null,"Foo.bar",o);
}
}
}
});
how clojure compiles protocol invocations isn't clear to me, I just know "clojure protocols are fast"
I have profiled yes, using advanced. Btw, I now see that reify or type doesn't really make a difference (from this simple benchmark).
cljs.user=> (defprotocol IFoo (invoke [_]))
cljs.user=> (deftype Dude [] IFoo (invoke [_] (inc 1)))
cljs.user=> (let [f (->Dude)] (simple-benchmark [f f] (invoke f) 1000000000))
[f f], (invoke f), 1000000000 runs, 21015 msecs
nil
cljs.user=> (let [f (reify IFoo (invoke [_] (inc 1)))] (simple-benchmark [f f] (invoke f) 1000000000))
[f f], (invoke f), 1000000000 runs, 21122 msecs
nil
cljs.user=> (deftype Foo [f])
cljs.user/Foo
cljs.user=> (let [f (->Foo inc)] (simple-benchmark [f f] ((.-f f) 1) 1000000000))
[f f], ((.-f f) 1), 1000000000 runs, 6447 msecs
nil
in the case you have above you ought to be a able to compile it to
o.cljs$user$IFoo$invoke$arity$1()
cljs.user=> (let [f (reify IFoo (invoke [_] (inc 1)))] ((.-cljs$user$IFoo$invoke$arity$1 f)))
2
cljs.user=> (let [f (reify IFoo (invoke [_] (inc 1)))] (simple-benchmark [f f] ((.-cljs$user$IFoo$invoke$arity$1 f)) 1000000000))
[f f], ((.-cljs$user$IFoo$invoke$arity$1 f)), 1000000000 runs, 496 msecs
nil
Using that "patch", it's close enough to the (.-f ..)
approach:
cljs.user=> (let [f (reify IFoo (invoke [_] (inc 1)))] (simple-benchmark [f f] (invoke f) 1000000000))
[f f], (invoke f), 1000000000 runs, 7400 msecs
nil
Eh, the patch being:
cljs.user=> (set! cljs.user.invoke (fn [this] ((.-cljs$user$IFoo$invoke$arity$1 this) this)))
#object[Function]
the check is there for objects that do not have this method attached to them, e.g. if you would extend the protocol to :default
and pass a number or string which does not implement the protocol
That's right, I can do something like this:
#?(:cljs
(set! sci.impl.types/eval
(fn [this ctx bindings]
(if-let [f (.-sci$impl$types$Eval$eval$arity$3 this)]
(f this ctx bindings)
this))))
that looks pretty similar to what the body of sci.impl.types.eval
ought to be. but again, this might be far more JIT friendly
Aaah!
#?(:cljs
(set! sci.impl.types/eval
(fn [expr ctx bindings]
(if-some [_ (.-sci$impl$types$Eval$eval$arity$3 expr)]
(.sci$impl$types$Eval$eval$arity$3 expr ctx bindings)
expr))))
This is what it translates into:
"function (expr,ctx,bindings){\nvar temp__5792__auto__ = expr.sci$impl$types$Eval$eval$arity$3;\nif((temp__5792__auto__ == null)){\nreturn expr;\n} else {\nvar _ = temp__5792__auto__;\nreturn expr.sci$impl$types$Eval$eval$arity$3(ctx,bindings);\n}\n}"
This now works:
cljs.user=> (sci/eval-string "[1 (+ 1 2 3)]")
[1 6]
but not everything works...anyway, stuff to figure out. I hope this will fix the perf issues, else I'll probably roll back to what it was
another fix:
#?(:cljs
(set! sci.impl.types/eval
(fn [expr ctx bindings]
(when-some [expr expr]
(if-some [_ (.-sci$impl$types$Eval$eval$arity$3 expr)]
(.sci$impl$types$Eval$eval$arity$3 expr ctx bindings)
expr)))))
This seems to work in all cases:
(def old-eval eval)
#?(:cljs
(set! sci.impl.types/eval
(fn [expr ctx bindings]
(if (satisfies? Eval expr)
(old-eval expr ctx bindings)
expr))))
If I implement everyone on a type I could replace satisfies? with instance? and then call old-val...if I do this, then it doesn't work anymore:
(def old-eval eval)
#?(:cljs
(set! sci.impl.types/eval
(fn [expr ctx bindings]
(if (satisfies? Eval expr)
(.sci$impl$types$Eval$eval$arity$3 expr ctx bindings)
expr))))
This is also interesting. This is what I get on master:
cljs.user=> (time (sci/eval-string "(loop [val 0 cnt 10000000] (if (pos? cnt) (recur (inc val) (dec cnt)) val))"))
"Elapsed time: 2560.479166 msecs"
10000000
But on my protocol branch, the first time:
cljs.user=> (time (sci/eval-string "(loop [val 0 cnt 10000000] (if (pos? cnt) (recur (inc val) (dec cnt)) val))"))
"Elapsed time: 1666.945417 msecs"
10000000
cljs.user=> (time (sci/eval-string "(loop [val 0 cnt 10000000] (if (pos? cnt) (recur (inc val) (dec cnt)) val))"))
"Elapsed time: 3654.658507 msecs"
Fascinating. On master:
$ node $(which nbb)
Welcome to nbb v0.1.8!
user=> (time (loop [val 0 cnt 10000000] (if (pos? cnt) (recur (inc val) (dec cnt)) val)))
"Elapsed time: 865.337636 msecs"
With protocols:
$ node out/nbb_main.js
Welcome to nbb v0.1.8!
user=> (time (loop [val 0 cnt 10000000] (if (pos? cnt) (recur (inc val) (dec cnt)) val)))
"Elapsed time: 571.292925 msecs"
10000000
user=> (time (loop [val 0 cnt 10000000] (if (pos? cnt) (recur (inc val) (dec cnt)) val)))
"Elapsed time: 1045.600417 msecs"
10000000
It's not that much slower in advanced as you can see, so it might be ok to keep it... although it's not an improvement in general... I might take this small hit on CLJS because in the JVM it's faster... it's a balance
After running more runs of these it could be fair to say that it's not that much of a difference in advanced
This is pretty weird. The first time it seems to be significantly faster.
user=> (time (loop [val 0 cnt 20000000] (if (pos? cnt) (recur (inc val) (dec cnt)) val)))
"Elapsed time: 1097.261099 msecs"
20000000
user=> (time (loop [val 0 cnt 20000000] (if (pos? cnt) (recur (inc val) (dec cnt)) val)))
"Elapsed time: 1994.345612 msecs"
20000000
user=> (time (loop [val 0 cnt 20000000] (if (pos? cnt) (recur (inc val) (dec cnt)) val)))
"Elapsed time: 1983.203614 msecs"
20000000
Programmed my way out using a macro which does 1 thing on Clojure and another thing on CLJS...!

@borkdude i wonder if you were hitting the same thing that this timely tweet thread is about https://twitter.com/SeaRyanC/status/1496273931120300032?s=20