clojurescript

ClojureScript, a dialect of Clojure that compiles to JavaScript http://clojurescript.org | Currently at 1.10.879
2021-05-02T01:23:45.064400Z

Hi all, I’m a beginner doing some home automation stuff with cljs and some npm Javascript libraries for hue and tradfri. What’s the appropriate way to have a function on a regular timer that reads a sensor and does something based on the last sensor read and this sensor read? Is there a sensible way to pass the last sensor state to the next function call as an argument? Or is this where your use an atom within the closure instead of it being an argument to the function?

2021-05-02T12:34:45.070600Z

I ended up using setInterval for now and that's working well! I ran into an issue though. I have a collection of lights which are js objects. Within a go block I call my "(switch-light+ [light on?])" function on each light which calls a method on a light which returns a promise. When I do the following: (<p! (every (map #(switch-light+ % true) kitchen-lights)))) it's as if the switch-light function doesn't get called at all. However, getting the nth element out of the collection of lights and passing it to switch-light does work. Any thoughts on why? I've pasted a extract of the code below with the bulk of the logic omitted

(defn switch-light+ [^js light on?]
  (println "-- switch-lights")
  (. light (switch on?)))

(defn every [& args]
  (js/Promise.all (into-array args)))

(defn update-kitchen-lights [^js presence-sensor ^js kitchen-lights]
  (go
    (let [is-present? (. presence-sensor -presence)
          some-lights-on? (some #(. ^js % -isOn) kitchen-lights)
          someone-entered? (and is-present?
                                (not (:was-present @kitchen-state)))
          timed-out? (and (not is-present?)
                          (> (:timeout @kitchen-state) 5))]

     ...

      ;; Someone just entered the room => lights on
      (when (and someone-entered?
                 (not some-lights-on?))
        ;; Lights on (ToDo: Wait for this and check response)
        (println "Turning lights on")

        ;; LINE THAT DOES NOT WORK
        (<p! (every (map #(switch-light+ % true) kitchen-lights))))

        ;; LINES THAT DO WORK
        (<p! (switch-light+ (nth kitchen-lights 0) true))
        (<p! (switch-light+ (nth kitchen-lights 1) true))
        (<p! (switch-light+ (nth kitchen-lights 2) true))
        (<p! (switch-light+ (nth kitchen-lights 3) true)))

      ...


      (swap! kitchen-state assoc :was-present is-present?)
      (swap! kitchen-state update :seconds + 1)
      (println "kitchen-state-after: " @kitchen-state)
      (println ""))))

raspasov 2021-05-02T13:51:20.071900Z

They way you’re calling your every function is wrong, I believe. map returns a sequence (one argument).

raspasov 2021-05-02T13:54:00.072200Z

Try changing every to take one argument. (I haven’t tested this suggestion, just from skimming the code, could be wrong).

2021-05-03T12:13:14.100300Z

That was it! Thank you.

👍 1
raspasov 2021-05-02T01:55:17.064500Z

If you need something basic that doesn’t care about millisecond accuracy, js/setInterval works just fine https://www.w3schools.com/jsref/met_win_setinterval.asp

raspasov 2021-05-02T02:04:48.064900Z

If you want something more precise, and for longer intervals/periods (e.g. tomorrow at 9AM, tonight at midnight, or every Monday at 7AM), I recommend a library like https://github.com/juxt/tick and using the values that the library provides to “trigger” when your automation functions will run;

raspasov 2021-05-02T02:06:00.065300Z

Second option definitely adds complexity but it is more robust and will give you greater power and flexibility;

sova-soars-the-sora 2021-05-02T02:58:51.065500Z

yes, recommend using an atom to save sensor state. or some number of previous states

william 2021-05-02T12:14:32.066600Z

Hey, I would like to understand why I have a certain behavior with atoms and promises. Consider this snippet:

william 2021-05-02T12:15:09.066800Z

william 2021-05-02T12:17:51.068200Z

If I do an action that triggers the add case, I would expect to memorize the layout in the current-layout atom, but in subsequent interactions, it always comes back null

william 2021-05-02T12:19:38.068600Z

and same for the other cases. Am I missing some obvious scoping issue here?

p-himik 2021-05-02T12:27:12.068700Z

What's the context of that code?

william 2021-05-02T12:28:19.068900Z

It's in a function that passes down props to a react component

william 2021-05-02T12:29:10.069100Z

:cy is the callback with which the component refers to itself, and the .on fields are promises on certain events

p-himik 2021-05-02T12:29:11.069300Z

On each call of :cy, the atom is re-created. It's not stored anywhere. This is Reagent, right? You need a form-2 component or reagent.core/with-let.

william 2021-05-02T12:29:41.069500Z

wait, but shouldn't :cy be called only once?

william 2021-05-02T12:30:07.069700Z

at startup? That might very well be my misunderstanding!

p-himik 2021-05-02T12:30:27.069900Z

I have no idea how cytoscape works. It's easy to imagine that it will be called again when @app-state/app-state changes or the parent component is re-rendered.

p-himik 2021-05-02T12:30:40.070100Z

Just add a logging statement there and check. :)

william 2021-05-02T12:30:52.070400Z

very good idea, thank you!

william 2021-05-02T12:49:47.070800Z

now, I can get that working with a with-let block that wraps all the function, but I really would like to just wrap the content of :cy, so that I can initialize the atom with an initial layout instead of null. Would there be a way to do that?

p-himik 2021-05-02T12:57:06.071Z

No.

p-himik 2021-05-02T13:00:15.071200Z

Wait, but you don't need the atom at all, do you? Seems like you should be able to call (.on cy "drag" ...) right inside that (.on cy "add" (fn [_] ...)).

p-himik 2021-05-02T13:00:31.071400Z

Again, I have no idea how cytoscape works, so that might not be the case at all.

Sam Ritchie 2021-05-02T14:13:06.076800Z

Hey all! I’ve got a JS interop issue that is stumping me. The #sicmutils library has a numeric tower built on the “Fraction” type from Fraction.js, which I require with ["fraction.js/bigfraction.js" :as Fraction] (in https://github.com/sicmutils/sicmutils/blob/master/src/sicmutils/ratio.cljc). My issue is that if I use the same require form in a different namespace, then I seem to get a different copy of the library. I extend a bunch of protocols in ratio.cljc, and if I make an instance with (Fraction. 1 2) in a different namespace, it’s a different type. More simply, (instance? Fraction x) fails in namespace b if x was created with the Fraction constructor in namespace a , and I can’t type-hint instances from a in any other namespace. Is there some way to do the require above in a way that will work across multiple namespaces?

Sam Ritchie 2021-05-02T14:15:31.079200Z

All I REALLY need is a way to (instance? Fraction x) in two separate namespaces, where b depends on a. I would just put (def ratiotype Fraction) in namespace a… but the problem is that b needs to type hint function arguments with Fraction, and I don’t THINK I can do ^ratiotype as a type hint (right??)

Sophie 2021-05-02T14:28:12.082200Z

Hey Guys, is there anybody who can tell me why this is working: (defn rooms-size [value title] [:div [:input {:type "text" :value (:Size @value) :on-change #(swap! value assoc :Size (.. % -target -value)) :on-mouse-leave #(rf/dispatch [:change-room-size [title (int (.. % -target -value))]])} ]] ) and this not (let [t-room-size (atom {:qm (get-in @liste [:rooms @(rf/subscribe [:room-flag]) :qm])})] [:div [:input {:type "text" :value (:qm @t-room-size) :on-change #(swap! t-room-size assoc :qm (.. % -target -value)))} ]]) I can't change the value of the input field in the second version:face_with_rolling_eyes:

p-himik 2021-05-02T14:35:02.082300Z

It's probably more apt for #reagent Two reasons: - Regular built-in atom doesn't trigger Reagent re-renders. Only reagent.core/atom does, so you have to use that - You re-create the atom on each re-render. You have to use a form-2 component or reagent.core/with-let.

Sophie 2021-05-02T14:38:43.082500Z

Ok thank you so much. I will give it a try 😊

👍 1
p-himik 2021-05-02T14:47:09.082800Z

I guess it depends on your build setup. I cannot reproduce it with shadow-cljs:

;; app/a.cljs
(ns app.a
  (:require ["fraction.js" :as Fraction]))

(def fraction Fraction)

;; app/b.cljs
(ns app.b
  (:require ["fraction.js" :as Fraction]))

(def fraction Fraction)

;; app/core.cljs
(ns app.core
  (:require [app.a :as a]
            [app.b :as b]))

(defn ^:export main []
  (js/console.log (= a/fraction b/fraction)))
The code above outputs true.

p-himik 2021-05-02T14:47:52.083Z

And by "it" I meant specifically "I seem to get a different copy of the library".

p-himik 2021-05-02T14:48:55.083200Z

You're talking about type hints, but type hints don't work in ClojureScript. The only thing that they do is to enable externs autoinference. The actual types are never checked.

p-himik 2021-05-02T14:49:50.083400Z

A common practice to enable such autoinference is to just put ^js in front of the relevant symbol - that's enough. But in your case, I think it's a built problem and not an actual type problem.

p-himik 2021-05-02T14:55:33.083600Z

I took a deeper look at how sicmutils is structured, and to me it doesn't make much sense, to be honest. You're using both lein-cljsbuild and shadow-cljs, but the latter is only used for testing, it seems. You're using both fraction.js via npm and cljsjs/bigfraction - that might be the culprit. I would just switch to a single build tool for everything. And I would completely remove any cljsjs packages and replace them with their NPM originals. Regardless of whether it actually fixes the problem or not.

Sam Ritchie 2021-05-02T15:11:41.087100Z

Okay, excellent, that is almost certainly it. Test running was tough enough to set up last year that I’ve avoided migrating off of “doo”, my old test runner, and moved to shadow for detecting missing hints etc... but you’re right that getting shadow to do everything cljs related is the right move. I’ll set some time aside here. Thank you!

👍 1
Sam Ritchie 2021-05-02T15:12:16.088Z

(I had built a bunch of cljsjs packages before discovering shadow)

p-himik 2021-05-02T15:13:22.088300Z

AFAIK you can use NPM packages [almost] just as well in regular CLJS, without shadow-cljs. But I might be wrong since I only use shadow-cljs. :)

Sam Ritchie 2021-05-02T15:13:46.088800Z

I'm ready to become a cool kid!!

Sam Ritchie 2021-05-02T15:16:16.090200Z

One issue I remember with shadow as my main tool was that if I migrate off cljsjs, any consumer has to know and specifically include the fraction NPM dep vs getting it transitively

Sam Ritchie 2021-05-02T15:16:45.091300Z

Maybe there is some way to include an NPM dependency note or something in the published jar now?

Sam Ritchie 2021-05-02T15:17:03.092Z

Or alternately are folks publishing cljs libraries to NPM to get around this?

p-himik 2021-05-02T15:24:20.092200Z

I've never made any libraries, but FWIW I would: - Put regular CLJS sources out there with deps.edn, so that they could be used with Git SHA coordinates in other tools.deps projects - Remove all traces of Leiningen - Build and publish a jar with just the sources - Make a note in the README describing how to use this library, including installing the NPM packages But I have no clue how that would work if e.g. you use shadow-cljs-specific :require. It probably wouldn't so you'd have to find a common ground between shadow-cljs imports and regular CLJS imports.

p-himik 2021-05-02T15:25:15.092400Z

Ah yes, there's a documentation section about that: https://shadow-cljs.github.io/docs/UsersGuide.html#publish-deps-cljs

❤️ 1
Sam Ritchie 2021-05-02T20:11:46.093Z

@p-himik unfortunately I’m finding it deeply confusing, getting a node REPL setup going with shadow-cljs… this is what had blocked me before! If you have any advice…

Sam Ritchie 2021-05-02T20:16:53.093200Z

wait, progress… error seems tohave resolved…

p-himik 2021-05-02T21:38:05.093400Z

Good to hear. :) But for any shadow-cljs questions, it's better to ask in #shadow-cljs directly - I'm not an expect here.