Fork me on GitHub
#clojurescript
<
2021-04-28
>
nivekuil00:04:57

what accounts for the large perf difference here?

cljs.user> (simple-benchmark [] (reduce + (rseq (vec (range 1000)))) 1000)
[], (reduce + (rseq (vec (range 1000)))), 1000 runs, 315 msecs
nil
cljs.user> (simple-benchmark [] (reduce + (vec (range 1000))) 1000)
[], (reduce + (vec (range 1000))), 1000 runs, 57 msecs
nil
in clojure the reverse reduce is only 2x slower

p-himik06:04:07

Too few details. What's your ClojureScript version? What engine is running the code? Here's my result for Node:

ClojureScript 1.10.844
cljs.user=> (simple-benchmark [] (reduce + (rseq (vec (range 1000)))) 1000)
[], (reduce + (rseq (vec (range 1000)))), 1000 runs, 83 msecs
nil
cljs.user=> (simple-benchmark [] (reduce + (vec (range 1000))) 1000)
[], (reduce + (vec (range 1000))), 1000 runs, 38 msecs
nil

nivekuil22:04:39

good point, it was firefox, same cljs version.

Aron02:04:36

I am rewriting a callback hell to core async channels, and I have a dilemma: not sure how to solve situations where there is a need to fork based on state after a remote call What needs to happen is that there is a form filled out where parts of it need to be saved before than others (don't ask why, it's how the backend works) For user convenience pressing the last save button actually means that I check if anything that needs to be saved gets saved first, then I call the last save. My issue is that if there is any error, validation or otherwise, then I don't want to proceed with the save, just show feedback. And I don't know where to put the conditional that decides it. I would like to do it in the event handler with the rest of the relevant code, but that leads me to the idea that I should use a callback channel that is used for only one message. Seems like I would be back from where I started?

Franco Gasperino03:04:33

im very new, but it would reason that you could have a shared submission channel and a per-save-action reply channel which is provided to the submission channel with the save request. Once all of the save tasks and their reply channels are submitted, map over the reply channels with a take. Correlate the responses and display errors associated with the individual save requests.

Franco Gasperino03:04:05

for the most part, it could be elegant and fit nicely into a threading macro

Aron03:04:16

the 'per-save-action reply channel' is what I referred to as a 'callback channel'

Aron03:04:46

It really doesn't seem any more elegant than callback hell. What if I need to add another fork? I create another channel? How is that different from passing callbacks?

Franco Gasperino03:04:01

do you see any benefit at the response correlation point, compared to callbacks?

Aron03:04:31

response correlation point?

Aron03:04:49

is that the event handler?

Franco Gasperino03:04:54

(def sink (chan 10)
(def saves [{} {}]

(defn publish-to-sink [s]
  (go
    (let [reply (chan)]
      (>! sink [reply s]))))

(defn wait-for-response [[c s]]
  (go
    [s (<! c)]))

(defn correlate-responses [coll]
  ... sort saves and responses from chan here ...
  {:success true/false :message "..."})

->> saves
  (map publish-to-sink)
  (map wait-for-response)
  correlate-responses


  

Franco Gasperino03:04:17

then a handler to read from the sink channel, perform your real save action

Franco Gasperino03:04:12

the forking additional channels im unsure

raspasov05:04:05

You have a form, it requires two (remote) API calls (I presume, HTTP, but not important) - is that about right? core.async is not some magic dust that you’ll sprinkle and complexity will go away. Like you said, you have some complexity already baked in, unfortunately, in that you need to make two separate API calls… I would just write a long (go …) that does everything in order and has the if/else statements in order with takes like (go (<! my-resp-1-ch) ;do something (<! my-resp-2-ch) ;do something else ) At least it will look sequential in code.

raspasov05:04:53

The power of core.async is that it allows you to build nice abstractions, but it is much better in somewhat of a “greenfield” environment where you can design things from the ground-up.

raspasov05:04:18

Let me know if I can help with anything else.

raspasov05:04:43

If you post some code, I would be happy to give feedback.

Aron06:04:04

I am not really asking for code, rather, I would like to know how response channels are different from callback functions

raspasov06:04:31

Understood 🙂 All I said is that if you have example code, I’d be happy to discuss.

Aron06:04:01

i did the same thing you said, have a long go that does everything, but just to replace a single callback, now i have to create a channel pass the channel push to the channel and close the channel. Sure, its a queue, not a callback, which in cljs matters more than in clj, but there has to be a more idiomatic approach

raspasov06:04:20

Otherwise often I’m not sure if I’m falling into abstract discussion that might be misunderstanding the problem.

Aron06:04:44

left out reading from a channel but I did that too in code:)

raspasov06:04:45

Btw, you shouldn’t be needing to “close” channels. In my experience, closing channels is pretty rare.

raspasov06:04:25

(been using core.async since ~2014 or so, if that’s any reference 🙂 )

raspasov06:04:09

This is an old video by David Nolen, but I believe still very much relevant (and he does the presentation in CLJS/JS, so it will probably apply to what you’re doing) https://www.youtube.com/watch?v=AhxcGGeh5ho

raspasov06:04:38

Generally, to bring some more “sanity” into your HTTP requests/responses, you can have a global channel that all HTTP responses arrive on; But we’re back to the abstraction building, you need to build that yourself, core.async is just the toolbox with which you can build a vehicle 🙂

phronmophobic06:04:20

> i did the same thing you said, have a long go that does everything, but just to replace a single callback, now i have to create a channel pass the channel push to the channel and close the channel. A few differences from an architectural perspective: • Using channels gives you the opportunity to deal with backpressure, overflow, rate limiting, etc. • Using channels gives you more flexibility for deciding when an event should be processed. It doesn't have to be processed immediately when the event is produced • Channels help decouple parts of your system which can make it easier to test. As more of the system becomes decoupled, it's more feasible to test parts in isolation Sure, replacing a single callback isn't going to help that much. It's like untangling one thread of a hairball. Hopefully, once enough parts become disentangled, the system will be easier to change, update, test, and reason about.

💯 3
raspasov06:04:25

It depends on what you care about. Can your UI take responses in any order? Ideally, yes, that would avoid a lot of complexity. But there’s definitely cases where you might want to ensure at least certain events are ordered (say, you want to save the user to your database before reporting it to analytics); A number of cases, and a number of design choices to make. CSP/core.async can help guide you to a small degree, but will not make sane choices for you.

phronmophobic06:04:57

One of the huge wins I found when using core async was that it greatly simplified dealing with async errors, especially when combined with asynchronous processes that require time outs.

raspasov06:04:36

Yes… Timeouts are a huge win… With regular promises/callbacks how do you even handle timeouts? (I’ve actually never dealt with JS at that level of sophistication, most of my frontend work has been CLJS) Do you use some mutable state? :man-shrugging:

raspasov06:04:42

I guess there’s Promise.race … Ok. 🙂

phronmophobic06:04:14

Before core.async, I used Deferreds (in JS, Python, and c++): • https://twistedmatrix.com/documents/16.2.0/core/howto/defer.htmlhttps://api.jquery.com/jquery.deferred/ • We wrote our own deferred library for c++ It's not as clean as core.async, but it was an improvement over raw callbacks.

phronmophobic06:04:25

Many promise libraries do much of the same stuff.

raspasov06:04:19

jQuery.Deferred looks like an OOP mutable API over callbacks 🙂

raspasov06:04:25

(I have never used it)

Aron08:04:24

I watched that talk and about 2 dozen other CSP talks, I used csp in javascript a lot.

👍 2
Aron08:04:37

I just never liked callback channels, that's all.

Aron08:04:43

I was hoping there is an alternative

Aron08:04:43

backpressure might be relevant, but overflow? rate limiting? we are talking about a submit button on a form...

macrobartfast04:04:14

I understand I should post code with this question, but please indulge a shot in the dark, as the code can’t be posted and I am unable to duplicate the problem. I have a create-cljs-app Reagent project running via cider/emacs. When evaluating a function that updates an atom while in the containing fns.cljs buffer via cider, everything works and the UI updates in the browser. However, when that function is imported and called via an :on-click handler in another file containing a button, the function only partially runs. The function attached to the button’s :on-click with an #(fns/thefunction) style ‘anonymous’ function. I’m only asking in case this is a frequent gotcha or anything for which a troubleshooting tip pops out for anyone.

macrobartfast04:04:37

That same function attached to the :on-click runs fine in the other file, btw, if evaluated alone with cider as (fns/thefunction)

macrobartfast04:04:16

So this has something to do with cider evaluation or with button attachment, probably.

macrobartfast04:04:43

I just created a test function and imported it and added it to a test button and that’s not working either…

;; in fns.cljs
(defn test-function []
  (prn "this ran")
  (prn "this ran too"))

;; in ui.cljs
(defn test-button []
  [:div
   [:input {:type "button" :value "test"
            :on-click #(fns/test-function)}]])

macrobartfast04:04:31

(fns/test-function) runs in ui.cljs when evaluated with cider.

macrobartfast04:04:44

the state is kept in state.cljs… maybe there’s a problem with my keeping all these things in different files.

macrobartfast04:04:01

I’m importing them into the other files left and right.

macrobartfast05:04:02

but in the case of the test-function, it just has two prn’s, so seems as simple as can be.

macrobartfast05:04:46

this works:

(defn test-button []
    [:div
     [:input {:type "button" :value "test"
              :on-click #(js/alert "test")}]])

macrobartfast05:04:20

but not

(defn test-function []
  (js/alert "yoyo"))
defined in fns.cljs, and called in ui.cljs via the button. This is mysterious. Alerts work, but not prn’s.

macrobartfast05:04:08

further thoughts in this thread… I’ve posted faaar too much already.

macrobartfast05:04:05

I found the prn’s in the browser console… that’s figured out… cider called functions print to the repl, of course, and the function when called in the browser prns to the console. Doh.

macrobartfast05:04:27

So it’s starting to look like the function I’m calling is the problem when attached to :on-click… calling it via cider standalone in ui.cljs works as mentioned, but not when the button it is attached to is clicked in the ui. The function modifies an atom with a somewhat lengthy map function.

lassemaatta05:04:45

map is lazy, calling it from the repl will force its evaluation so that the result may be printed but I'm not sure if that happens with :on-click handlers

lassemaatta05:04:36

also, mixing side-effects (e.g. modifying an atom) with lazy sequences (`map`) is usually not encouraged

lassemaatta05:04:23

your immediate problem might be solved with doall, see https://clojuredocs.org/clojure.core/doall (well, actually dorun might be better for your use case if you don't need the return value)

macrobartfast05:04:31

you’re psychic… I’m staring at the doall in the called function. I had put it in, but I’ve zeroed in on it as a likely problem. I’ll try dorun instead now.

macrobartfast05:04:43

hmm… doall hadn’t worked, and dorundoesn’t either, but I think we’re in the zone.

macrobartfast05:04:19

something to do with lazy evaluation when called by the :on-click or something, not sure.

macrobartfast05:04:56

the horrible sloppy convoluted modification of state by a long tedious inefficient map might be something for me to think about, too.

macrobartfast06:04:29

my main UI element is driven by an atom; clicking this button resets! another atom with data and maps over it to update the first atom.

macrobartfast06:04:10

as in, maps over the second atom, and searches the first atom for matching things to update.

macrobartfast06:04:01

My messy function uses walk/postwalk… I wonder if there’s a lazy evaluation issue in there.

macrobartfast06:04:03

I just wrapped the whole for wrapped walk/postwalk business in a dorun and it works!

macrobartfast06:04:31

you led me to the solution @https://app.slack.com/team/UC506CB5W… thank you!

macrobartfast06:04:01

issue solved thanks to @lasse.maatta… it was a lack of a doall/dorun in the function called by the button.

👍 5
😀 2
flowthing07:04:33

Something caused some of my ClojureScript tests to start failing like this:

Uncaught exception, not in assertion.
expected: nil
  actual: #object[ReferenceError ReferenceError: $f is not defined]
Is there any way to get a hold of the stack trace for the error, or any other additional information that might help figure out the root cause? Frustratingly, the exception is only thrown when I run my tests from the command line, not when I run them from the REPL with (cljs.test/run-tests ,,,).

flowthing07:04:11

I know there's https://clojure.atlassian.net/browse/CLJS-3162, but looks like that was never merged.

flowthing07:04:05

Never mind. Looks like good old rm -rf target && .shadow-cljs fixed the issue. :man-shrugging::skin-tone-2:

danieroux12:04:27

Upgrading to the latest ClojureScript and friends. I don’t yet know how to narrow this down, has anyone seen something like this? Note the ! in the varname. JavaScript does not like that.

var file_$Users$danie$_m2$repository$org$clojure$google_closure_library$0_0_20201211_3e6c510d$google_closure_library_0_0_20201211_3e6c510d_jar!$goog$log$log$classdecl$var0

Alex Miller (Clojure team)12:04:39

Looks like a munged url for a resource in a jar

danieroux16:04:07

In this case, removing this solved it /cc @U050B88UR

:language-out :ecmascript5

danieroux16:04:28

(I do not have a small repro for this, just a heads up)

Aron13:04:56

;; this is inside a subcription, only relevant effects appear based on :type from effect, and inside a go loop too
  (let [{:keys [payload success-channel error-channel] :as effect } (<! effect-chan)
         :keys [stuff needed] payload
         response (!< (http/post ,,,))]
    (case (:status response) 
      201 (do (set-status-and-result)
              (>! state successful-put)
              (>! success-channel response))
      200 (do (set-status-and-result)
              (>! state successful-post)
              (>! success-channel response))
      ;otherwise errors
      (do (>! state error)
          (>! error-channel response))))

;; and in the event handlers (onClick, onLoad, onResize etc.)

(let [succ-chan (chan)
      err-chan (chan)
      (go (>! effects-chan {:type above-effect 
                            :payload payload 
                            :success-channel succ-chan 
                            :error-channel err-chan})
          (alt! succ-chan handle-success err-chan handle-error)
          (close! succ-chan)
          (close! err-chan)))
     
So this is code, I kinda expect that there are many better ways to achieve this. I also wish to be able to define succ/err effect reactions globally and locally too. Above would be the local example, and global I would write it inside the effect handler, but I am not sure how, because I think it's bad to intertwine the basic logic that I wrote above with specific business stuff that might change if the design (i.e. graphics, visuals, language, client interface) changes. Somehow I would like to be able to just write a tree of actions and effects that are defined elsewhere, is that possible?

pmooser13:04:07

Is it possible to send a byte array using transit-cljs (without me manually encoding the byte array to base64 or something) ?

pmooser13:04:28

I guess I should just try and see what happens!

dnolen16:04:45

@danie hard to say w/o a reproducer, this looks like macro stuff from somewhere