clojurescript

ClojureScript, a dialect of Clojure that compiles to JavaScript http://clojurescript.org | Currently at 1.10.879
nivekuil 2021-04-28T00:45:57.485Z

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

nivekuil 2021-04-28T22:25:39.042300Z

good point, it was firefox, same cljs version.

Aron 2021-04-28T02:42:36.490300Z

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?

Aron 2021-04-28T08:57:24.020400Z

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

šŸ‘ 1
Aron 2021-04-28T08:57:37.020600Z

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

Aron 2021-04-28T08:57:43.020800Z

I was hoping there is an alternative

Aron 2021-04-28T08:58:43.021Z

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

Franco Gasperino 2021-04-28T03:19:33.490500Z

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 Gasperino 2021-04-28T03:20:05.490700Z

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

Aron 2021-04-28T03:23:16.490900Z

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

Aron 2021-04-28T03:24:46.491100Z

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 Gasperino 2021-04-28T03:26:01.491300Z

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

Aron 2021-04-28T03:28:31.491500Z

response correlation point?

Aron 2021-04-28T03:28:49.491700Z

is that the event handler?

Franco Gasperino 2021-04-28T03:35:54.491900Z

(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 Gasperino 2021-04-28T03:37:17.492100Z

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

Franco Gasperino 2021-04-28T03:39:12.492300Z

the forking additional channels im unsure

macrobartfast 2021-04-28T04:32:14.499600Z

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.

macrobartfast 2021-04-28T04:34:37.000700Z

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

macrobartfast 2021-04-28T04:35:16.001300Z

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

macrobartfast 2021-04-28T04:50:43.002600Z

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)}]])

macrobartfast 2021-04-28T04:53:31.003300Z

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

macrobartfast 2021-04-28T04:58:44.003900Z

the state is kept in state.cljsā€¦ maybe thereā€™s a problem with my keeping all these things in different files.

macrobartfast 2021-04-28T04:59:01.004200Z

Iā€™m importing them into the other files left and right.

macrobartfast 2021-04-28T05:00:02.004900Z

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

macrobartfast 2021-04-28T05:16:46.005200Z

this works:

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

raspasov 2021-04-28T05:19:05.005900Z

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.

macrobartfast 2021-04-28T05:19:20.006400Z

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.

raspasov 2021-04-28T05:20:53.007Z

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.

macrobartfast 2021-04-28T05:22:08.007600Z

further thoughts in this threadā€¦ Iā€™ve posted faaar too much already.

raspasov 2021-04-28T05:22:18.007700Z

Let me know if I can help with anything else.

raspasov 2021-04-28T05:22:43.007900Z

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

macrobartfast 2021-04-28T05:23:05.008100Z

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.

macrobartfast 2021-04-28T05:24:27.008400Z

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.

lassemaatta 2021-04-28T05:39:45.008800Z

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

lassemaatta 2021-04-28T05:40:36.009Z

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

lassemaatta 2021-04-28T05:41:23.009200Z

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)

macrobartfast 2021-04-28T05:57:31.009600Z

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.

macrobartfast 2021-04-28T05:58:43.009800Z

hmmā€¦ doall hadnā€™t worked, and dorundoesnā€™t either, but I think weā€™re in the zone.

macrobartfast 2021-04-28T05:59:19.010Z

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

macrobartfast 2021-04-28T05:59:56.010200Z

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

macrobartfast 2021-04-28T06:01:29.010400Z

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.

macrobartfast 2021-04-28T06:02:10.010600Z

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

p-himik 2021-04-28T06:05:07.011Z

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=&gt; (simple-benchmark [] (reduce + (rseq (vec (range 1000)))) 1000)
[], (reduce + (rseq (vec (range 1000)))), 1000 runs, 83 msecs
nil
cljs.user=&gt; (simple-benchmark [] (reduce + (vec (range 1000))) 1000)
[], (reduce + (vec (range 1000))), 1000 runs, 38 msecs
nil

Aron 2021-04-28T06:06:04.011200Z

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

raspasov 2021-04-28T06:08:31.011400Z

Understood šŸ™‚ All I said is that if you have example code, Iā€™d be happy to discuss.

raspasov 2021-04-28T06:09:20.011900Z

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

Aron 2021-04-28T06:09:44.012100Z

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

raspasov 2021-04-28T06:10:45.012300Z

Btw, you shouldnā€™t be needing to ā€œcloseā€ channels. In my experience, closing channels is pretty rare.

raspasov 2021-04-28T06:11:25.012500Z

(been using core.async since ~2014 or so, if thatā€™s any reference šŸ™‚ )

raspasov 2021-04-28T06:17:09.012700Z

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

raspasov 2021-04-28T06:21:38.013Z

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 šŸ™‚

phronmophobic 2021-04-28T06:25:20.013200Z

> 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.

šŸ’Æ 1
raspasov 2021-04-28T06:26:25.013400Z

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.

phronmophobic 2021-04-28T06:27:57.013700Z

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.

raspasov 2021-04-28T06:29:36.013900Z

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:

macrobartfast 2021-04-28T06:30:01.014100Z

My messy function uses walk/postwalkā€¦ I wonder if thereā€™s a lazy evaluation issue in there.

macrobartfast 2021-04-28T06:30:32.014300Z

YEEEEEESSSS!!

macrobartfast 2021-04-28T06:30:35.014500Z

that was it.

raspasov 2021-04-28T06:31:42.014800Z

I guess thereā€™s Promise.race ā€¦ Ok. šŸ™‚

macrobartfast 2021-04-28T06:32:03.015Z

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

macrobartfast 2021-04-28T06:32:31.015200Z

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

phronmophobic 2021-04-28T06:34:14.015600Z

Before core.async, I used Deferreds (in JS, Python, and c++): ā€¢ https://twistedmatrix.com/documents/16.2.0/core/howto/defer.html ā€¢ https://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.

macrobartfast 2021-04-28T06:35:01.016800Z

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

šŸ‘ 2
šŸ˜€ 1
phronmophobic 2021-04-28T06:35:25.016900Z

Many promise libraries do much of the same stuff.

raspasov 2021-04-28T06:36:19.017200Z

jQuery.Deferred looks like an OOP mutable API over callbacks šŸ™‚

raspasov 2021-04-28T06:36:25.017400Z

(I have never used it)

flowthing 2021-04-28T07:13:33.019600Z

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 ,,,).

flowthing 2021-04-28T07:14:11.019900Z

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

flowthing 2021-04-28T07:22:05.020100Z

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

danieroux 2021-04-28T12:52:27.023900Z

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

alexmiller 2021-04-28T12:56:39.027700Z

Looks like a munged url for a resource in a jar

Aron 2021-04-28T13:14:56.039Z

;; 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 } (&lt;! effect-chan)
         :keys [stuff needed] payload
         response (!&lt; (http/post ,,,))]
    (case (:status response) 
      201 (do (set-status-and-result)
              (&gt;! state successful-put)
              (&gt;! success-channel response))
      200 (do (set-status-and-result)
              (&gt;! state successful-post)
              (&gt;! success-channel response))
      ;otherwise errors
      (do (&gt;! state error)
          (&gt;! error-channel response))))

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

(let [succ-chan (chan)
      err-chan (chan)
      (go (&gt;! 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?

2021-04-28T13:37:07.040100Z

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

2021-04-28T13:38:28.040500Z

I guess I should just try and see what happens!

danieroux 2021-04-28T16:09:07.041Z

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

:language-out :ecmascript5

danieroux 2021-04-28T16:09:28.041200Z

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

dnolen 2021-04-28T16:12:45.041800Z

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