Fork me on GitHub
#clojurescript
<
2017-11-05
>
Kevin Lynagh01:11:31

I'd like to swap out a few functions between a "development" build and a "production" build of a cljs program. Think using (foo-with-instrumentation x y z) in dev and (foo x y z) in prod. The only way I can think of doing this is to use goog-define vars:

(def foo (if DEV? dev-impl/foo-with-instumentation impl/foo))

Kevin Lynagh01:11:24

where the value corresponding to DEV? can be changed w/ the :closure-defines cljs compiler option.

Kevin Lynagh01:11:53

However, since the instrumented functions live in a different namespace than the uninstrumented one, does this mean I need to pull in those namespaces with toplevel require rather than in the ns form?

Kevin Lynagh01:11:54

Not sure if this is the best way to go about things, or if there's a neater solution.

thheller10:11:29

@kevin.lynagh top level require is basically the same as ns. it can’t be conditional either. you add a :preload in dev that just uses set! to change the impl of foo.

Kevin Lynagh06:11:17

Got it, thanks for the reply. I'm nervous about the idea of using set! lest I forget to override every var. I'm going to try having identical namespaces and choosing between them via source-paths.

pesterhazy10:11:08

@kevin.lynagh i wouldn't call it neat but you could have two directories which each define the same namespace, dev/my/foo.cljs and prod/my/foo.cljs, then set the source paths in the dev/prod profiles

Kevin Lynagh06:11:27

Yeah, this sounds like it might be the easiest way forward. I've done this hack in the past. Thanks!

roti11:11:30

I'm having a weird bug, and I have no idea how to track it down. I have a SPA where I use hash urls (/index.html#/foo), and some radio inputs in a form. well for some reason, when executing an event (which only updates an atom) my whole page gets reloaded as /index.html?bar=baz#/foo, where bar is the name of the radio group, and baz the value of chosen radio input. Any idea how to track down the code that is reloading my page? (breakpoints on global events like onbeforeunload will not show me a relevant stack trace)

pesterhazy11:11:48

@roti, the form gets submitted?

roti11:11:13

its a simple onlick event with a function that just swaps an atom (after which the dom gets rerendered)

pesterhazy11:11:45

try adding onsubmit="return false" to your form, just for shits and giggles

roti11:11:10

@pesterhazy now it works fine 🙂 great idea, thanks

roti11:11:21

so the form got submitted, by why?

roti11:11:29

I meant, how could I find out why the form is submitted?

orestis12:11:36

How do I pass a clojurescript function to a JS function that expects a callback? Do I have to convert it somehow or is it toll-free bridged?

pesterhazy12:11:55

try planck -v and enter (defn foo [n] (* n n)) - you'll see what it compiles down to

mfikes12:11:57

For an even simpler (Var-free) view of things see what you get without the def. Evaluate (fn [n] (* n n))

pesterhazy14:11:23

👍 for planck -v - very useful to explain stuff to beginners with solid JS knowledge

pesterhazy14:11:35

thanks for adding this!

mfikes12:11:27

(FWIW, def forms only evaluate to Vars while in the REPL.)

roti12:11:38

is there a collection function (or combination of) with which I can search in a vector (using a predicate) and get the index? but without going through the whole array. I need something like some which returns the index

rauh12:11:08

@roti reduce-kv and then use reduced

mfikes12:11:42

AFAIK, there is no core fn for this. Andre is right, and there are others at https://stackoverflow.com/questions/8087115/clojure-index-of-a-value-in-a-list-or-other-collection

roti13:11:10

reduce-kv traverses the whole collection, doesn't it?

mfikes13:11:31

@roti The use of reduced should prevent traversal of the whole collection. A concrete example

(reduce-kv 
  (fn [_ k v] 
    (prn k) 
    (when (= v :c) 
     (reduced k))) 
  nil 
  [:a :b :c :d :e])
^ Edited to show steps via prn

ajs13:11:41

I like the example of (count (take-while (partial not= item) coll))) for this.

mfikes13:11:21

@ajs Yeah, I suppose the reduce-kv variant might be faster if you are dealing with vectors, while your example is generic for sequences. I think you can get a backtick by holding down the tick key.

ajs13:11:10

The long press worked, thanks!

ajs13:11:25

reduce is explicit iteration which I try to avoid as much as possible if there are other functions available

risto13:11:56

does anyone know of a starter repo for leiningen + es6 modules

risto13:11:52

i know it's alpha but I wanted to play around with it a bit I can't get the basic example working from the blog post, i don't know what i'm doing differently

mfikes13:11:39

@ajs @roti I was curious about the perf of reduce-kv on vectors vs. generic seq fns. Here is a result: https://gist.github.com/mfikes/eee355193cb6b8a3c1124c8514da8432

ajs13:11:47

Very cool. How did you do the benches?

roti14:11:05

yes, indeed. very interesting.

mfikes14:11:49

@ajs If you set up the ClojureScript compiler for running tests (see https://clojurescript.org/community/running-tests), then you can run ./script/benchmark. With that, it is an easy matter to modify this file https://github.com/clojure/clojurescript/blob/master/benchmark/cljs/benchmark_runner.cljs to include new tests that you might be interested in, and it will run them across all of the JavaScript VMs you've set up. You can also delete everything from line 11 down, and replace it with just the benchmarks you want to run.

mfikes14:11:08

One bit of advice is to watch out for benchmarking expressions that Closure can completely optimize away. (Sometimes you need to "let" a fn containing the expression of interest in the simple-benchmark binding portion and then call it in the body.

ajs14:11:11

I just discovered simple-benchmark in cljs, so for less aggressive benchmarking, that will probably help me test little functions like this in the future

roti13:11:03

@mfikes @rauh thanks, I was planning to join the values with their indexes and then use some, but I think reduce-kv is better

mfikes13:11:38

Yeah, it is all just tradeoffs. If you concretely know you are operating on vectors, you can play games like this. 🙂

risto13:11:31

nvm git it to work with figwheel

gfredericks14:11:58

does clojurescript have any mechanisms similar to user.clj that would let me monkeypatch things before the primary code runs?

orestis15:11:02

Thanks! I thought there was some weird thing going on but in the end the clojure syntax was obscuring (to my untrained eye) my misuse of the JS API :)

roti16:11:13

I use try/catch in a go block and it doesn't seem to catch. am I doing something wrong?

(defn testex  []
  (go
    (try
      (let [response (<! (http/get "" {:with-credentials? false}))]
        (println response))
      (catch :default e
        (println "error" e)))))

noisesmith16:11:00

@roti any channel op breaks out of the enclosing try

roti16:11:35

erm, try is inside go

noisesmith16:11:41

core.async is turning what looks like sequential imperative code into a bunch of callbacks - the exception is inside your callback

noisesmith16:11:00

@roti it is, but your <! contains an http/get that is not inside your try 😄

noisesmith16:11:21

it's actually a callback, for readability sake core.async makes it look like it is

roti16:11:39

so I should try inside the <! calls?

noisesmith16:11:52

if that's where you expect the exception, yes

roti16:11:11

ok, thanks

noisesmith16:11:14

or, another way to put it is that if http/get is the thing throwing, it should have a try around it

noisesmith16:11:39

I think... but http/get is giving you a channel which makes me think things might be trickier than that...

noisesmith16:11:54

I would need to read the code to help more at this point I think

roti16:11:16

let me try putting it around http/get

noisesmith16:11:51

@pontus.colliander that's very helpful advice, and if the authors of the http lib roti was using had followed it, roti would not have this problem

roti16:11:11

@noisesmith nope, doesn't work. here's the code:

(defn testex []
  (go
    (let [response (<! (try (http/get "" {:with-credentials? false})
                            (catch :default e
                              (println "error" e))))]
      (println response))))

noisesmith16:11:17

the answer here might be using a different lib, or a PR to fix the one you are using

roti16:11:06

so, http/get returns a channel, which apparently is not happening when an error occured (in my case the response is of type json, but the content is not so http can't parse it, so it throws an error)

roti16:11:36

it looks like a bug in http to me

noisesmith16:11:44

which http lib is it?

roti16:11:51

actually I'm not so sure where the error is thrown, because if the channel is not returned because of an exception, it should be caught.

roti16:11:57

Uncaught SyntaxError: Unexpected token S in JSON at position 0 at JSON.parse (<anonymous>) at cljs_http$util$json_decode (util.cljs?rel=1509894674732:63) at core.cljs:4829 at Function.cljs.core.update_in.cljs$core$IFn$_invoke$arity$3 (core.cljs:4829) at cljs$core$update_in (core.cljs:4820) at cljs_http$client$decode_body (client.cljs?rel=1509894676467:83) at Function.<anonymous> (client.cljs?rel=1509894676467:181) at Function.cljs.core.apply.cljs$core$IFn$_invoke$arity$2 (core.cljs:3685) at cljs$core$apply (core.cljs:3676) at async.cljs?rel=1509880110795:712 cljs_http$util$json_decode @ util.cljs?rel=1509894674732:63 (anonymous) @ core.cljs:4829 cljs.core.update_in.cljs$core$IFn$_invoke$arity$3 @ core.cljs:4829 cljs$core$update_in @ core.cljs:4820 cljs_http$client$decode_body @ client.cljs?rel=1509894676467:83 (anonymous) @ client.cljs?rel=1509894676467:181 cljs.core.apply.cljs$core$IFn$_invoke$arity$2 @ core.cljs:3685 cljs$core$apply @ core.cljs:3676 (anonymous) @ async.cljs?rel=1509880110795:712 (anonymous) @ async.cljs?rel=1509880110795:702 cljs$core$async$state_machine__29062__auto____1 @ async.cljs?rel=1509880110795:702 cljs$core$async$state_machine__29062__auto__ @ async.cljs?rel=1509880110795:702 cljs$core$async$impl$ioc_helpers$run_state_machine @ ioc_helpers.cljs?rel=1509880107319:35 cljs$core$async$impl$ioc_helpers$run_state_machine_wrapped @ ioc_helpers.cljs?rel=1509880107319:39 (anonymous) @ ioc_helpers.cljs?rel=1509880107319:48 (anonymous) @ channels.cljs?rel=1509880106192:61 cljs$core$async$impl$dispatch$process_messages @ dispatch.cljs?rel=1509880105746:19 channel.port1.onmessage @ nexttick.js:211

noisesmith16:11:56

> actually I'm not so sure where the error is thrown, because if the channel is not returned because of an exception, it should be caught. if the error is thrown inside someone else's go block, your try/catch cannot catch it

noisesmith16:11:28

and if core.async runs the code that throws the error before the code that uses the channel, you never see the evidence the channel got returned

pesterhazy17:11:33

IMO there's little reason to use a library for HTTP requests in CLJS. Just use js/fetch

pesterhazy17:11:48

I'd also recommend against introducing core.async unless you have a serious need for its features

tatut17:11:47

cljs-ajax has convenient support for transit

athomasoriginal17:11:07

@pesterhazy RE core.async > I'd also recommend against introducing core.async unless you have a serious need for its features Would this include using it to create functionality like that produced by setInterval?

phronmophobic18:11:47

@tkjone, I can’t imagine creating an clojurescript application in the browser without core.async. I think it’s an easier way to deal with the event handling and callbacks that are apart of most js apis

phronmophobic18:11:05

including creating functionality similar to setInterval (probably using timeout from core async)

athomasoriginal18:11:56

That was also my impression, but I wanted to see under which cases core.async is "too much"

pesterhazy18:11:06

@tkjone yes of course, why would using setInterval necessitate the introduction of macros and channels?

phronmophobic18:11:23

that’s just the way i’m most comfortable. I’m not saying it’s the right thing everyone

phronmophobic18:11:55

I’ve already gone through learning some of the quirks of core async

phronmophobic18:11:11

so I just start with core async at the beginning

pesterhazy18:11:13

by the way, often using https://google.github.io/closure-library/api/goog.Timer.html would be a good alternative to using setTimeout directly

phronmophobic18:11:26

if you’re working on a project with a team, there is somewhat of a barrier to entry with picking up core async, so using goog.Timer or setTimeout directly is definitely more straightforward

phronmophobic18:11:24

the macrology involved with core async can also make debugging more difficult.

pesterhazy18:11:25

I'd argue core.async has a high cost even in a team that knows it well

phronmophobic18:11:42

yea, i prefer using core async, but I totally get that many would avoid core async unless it’s doing a lot of heavy lifting

athomasoriginal18:11:01

This is one of those interesting things about learning CLJ/S: There are a lot of mechanisms that are capable of doing the same things, but not all of them should be used in all scenarios. I think this might present itself a little more in CLJS because, it feels to me, that the interop is used more often in CLJS.

athomasoriginal18:11:31

Earlier today I was prompted to write something like this:

(go
      (loop []
        (async/<! (async/timeout 16))
        (do-something)
Because I figured it was more idiomatic than the simple js/setInterval (had no idea about goog/timer) This is not to say that other languages do not have this issue

pesterhazy18:11:28

core.async has pros and cons, but i wouldn't use it just because it's idiomatic (I'd argue it isn't, not on the server and especially not in the browser)

athomasoriginal18:11:45

This is interesting because I feel that I have read quite a bit about the language and still find it challenging to find out what the right tool for the job is. My example was idiomatic setInterval, but this also applies to other areas like .. v. -> macro etc

pesterhazy18:11:28

I agree, a lot of this is tribal knowledge

pesterhazy18:11:38

.. being deprecated-ish in favor of ->

pesterhazy18:11:27

from the outside it may look like everyone in clojure(script)land is using core.aysnc, but that's far from true

athomasoriginal18:11:51

See, I did not know this. .. is often passed on as an okay practice, which I am sure it is, but as you mention -> is likely better practice. And yes, core.async does seem to be widely used

athomasoriginal18:11:17

The question is, how can the tribal knowledge be accessed?

pesterhazy18:11:46

people blog, or give talks

pesterhazy18:11:04

but I agree that this knowledge, or experience, is not easily accessible

pesterhazy18:11:21

would you say other communities do a better job at this?

pesterhazy18:11:33

I find that it's worse in JavaScript, because some of the material you find online is written by novice programmers, so you need to watch out for signs that the author doesn't really know what they're talking about

athomasoriginal18:11:49

haha True and I accept that answer because it is true. With this in mind, if all developers are working on different teams, the tribes split and evolve and grow. How do we keep track of the evolution? Yes, I believe that the Python, Go and JS communities do an excellent job of this this. Emphasis on JS. JS has other issues, but it is very easy to find what all the tools are for one task, and find write ups on when to use them.

athomasoriginal18:11:30

I also agree about writeups from novices

athomasoriginal18:11:49

I have been steered in the wrong direction several times because of this. This is both in JS and Clojure, but with JS you know the 10 most reputable developers and you can use their work to vet others writings

athomasoriginal18:11:01

In Clojure, I know the reputable developers, but their writings, valuable as they are, are sparse

pesterhazy18:11:58

agree with that

pesterhazy18:11:39

but you need to understand that python, js etc. are orders of magnitude larger than we are

athomasoriginal18:11:00

Definitely true. This is understood.

pesterhazy18:11:19

I'd like to blog more, but it's hard work and you don't immediately see the effects

athomasoriginal18:11:00

I hear you. What would you say is a good approach to asking the clojure community to support efforts that attempt to share the tribal knowledge. Essentially asking people to peer review your writings? I generally just ask whichever channel I believe would be able to contribute, but there is a line and I want to be sure its not being abused

athomasoriginal18:11:41

I know we have the community driven API docs, but I find that those make assumptions about learning styles and also context is not given so understand things like when to use one tool over another

pesterhazy19:11:39

personally I'm always happy for people to ask for feedback on the writing

pesterhazy19:11:45

I think most clojurians feels the same

athomasoriginal19:11:16

Yes, I have only had positive responses thus far. Whatever I have said about CLJ/S at large, this forum is likely one of the best I have ever experienced. So CLJ/S is a friendly tribe

athomasoriginal22:11:24

Here is a fun one. I am working with an experimental browser feature: webkitSpeechRecognition. When you speak, you are going to get back a SpeechRecognitionResultList which is distinct because it is not an Array. Here is some sample code:

(set! js/SpeechRecognition (.-webkitSpeechRecognition js/window))

(def recognition (js/SpeechRecognition.))

(set! (.-interimResults recognition) true)

(defn handle-speech
  [results]
  (map (fn [results] (get results 0)) clj-results)

(.addEventListener recognition "result" handle-speech)
This will error out when you try to speak into your speaker with Uncaught Error: [object SpeechRecognitionResultList] is not ISeqable. I ran into something similar with nodeList and I was able to resolve it with a protocol like:
(extend-type js/NodeList
   ISeqable
   (-seq [node-list] (array-seq node-list)))
However, this won't work for the above, because there is no JS/SpeechRecognitionResultList object.

pesterhazy22:11:46

I don't understand the snippet. Where's e used?

pesterhazy22:11:26

Anyway can't you just use array-seq directly?

pesterhazy22:11:56

also it looks like you're using map for side effects. Don't do that. Use doseq or run!

athomasoriginal22:11:51

Sorry, e was a mistake. It should be results

athomasoriginal22:11:05

(get results 0) would be a side effect?

pesterhazy22:11:43

no but... handle-speech is an event listener, so its return value is presumably ignored?

athomasoriginal22:11:07

Oh, yes, 100%. Sorry, I will be storing the value returned by Map and doing some more processing in the future. But right now, I was just running some tests and wanted to illustrate the problem. Good point though!

pesterhazy22:11:54

anyway try (->> results array-seq (map ...) prn)

raheel23:11:10

Hello, a question about advanced optimization: I am using D3.js in CLJS, and have a D3 callback function (the anonymous function below) that receives a previously bounded data as input from D3. Without advanced optimization, I get the expected numeric value in minutes, but with optimization, I get undefined. Any ideas?

(.attr "transform"
                  (fn [d i]
                    (let [minutes (.-minutes d)
                          y (y-scale minutes)]
                      (str "translate(0, " y ")")))))

coderofhonor00:11:18

I am completely new here, but I have some experience with the Closure Compiler. Perhaps the optimizations are renaming the minutes property during minification?

raheel00:11:05

ah. is there a way I can avoid the renaming? Also, wondering if I can avoid writing problematic code like this

raheel00:11:07

hmm, doesn't look like it AFAICT. just logging with _ (js/console.log (.-minutes d)) gives me undefined

coderofhonor00:11:39

In JS world, using brackets to access a value eliminates it as a candidate for renaming. So d['minutes'] would be safe.

coderofhonor00:11:04

I think in that case it is still possibly minifying the '.-minutes' part

coderofhonor00:11:17

I would inspect the js output maybe to see if that's the case

coderofhonor00:11:16

In terms of a Clojure-y way to represent bracket syntax, maybe using (:minutes d) would work? I'm a newb here coming from JS land, so this is just a guess.

coderofhonor00:11:47

Hmm, that doesn't seem to work. Maybe using aget? But that might just be equivalent to .-

coderofhonor00:11:12

Looks like this page outlines a solution you can use. Simply put: (aget obj "propertyName")

raheel02:11:09

@U7W5MHG1L that does work, thanks much!

raheel23:11:42

- if I log d above it gets printed as {minutes: 20} etc. - I am using D3.js from CLJSJS