Fork me on GitHub
#clojurescript
<
2022-01-12
>
Mateusz Mazurczak12:01:17

Hi, I was using fabricjs with clojure for some time, but it is bringing so many state and is written in a object oriented principals in mind. Is there anything like this library but done in more clojure/functional way? It doesn't have to be in clojure (although it would be great). Has someone here also use or used fabricjs with cljs?

Chase15:01:44

I'm trying to wrap my head around using `this` in CLJS (if I should ever need that) by recreating this exercise:

Chase15:01:09

const makeRandColor = () => {
    const r = Math.floor(Math.random() * 255);
    const g = Math.floor(Math.random() * 255);
    const b = Math.floor(Math.random() * 255);
    return `rgb(${r}, ${g}, ${b})`;
}

const buttons = document.querySelectorAll('button');

for (let button of buttons) {
    button.addEventListener('click', colorize)
}

const h1s = document.querySelectorAll('h1');
for (let h1 of h1s) {
    h1.addEventListener('click', colorize)
}

function colorize() {
    this.style.backgroundColor = makeRandColor();
    this.style.color = makeRandColor();
}

Chase15:01:15

I have been trying it like this:

Chase15:01:24

(ns weather.scratch 
  (:require [goog.dom :as gdom]))

(defn rand-rgb []
  (rand-int 256))

(defn rand-color []
  (let [[r g b] [(rand-rgb) (rand-rgb) (rand-rgb)]]
    (str "rgb(" r ", " g ", " b ")")))

(defn change-background []
  (let [new-color (rand-color)]
    (set! (.. js/document -body -style -backgroundColor) new-color)))

(defn colorize []
  (this-as this
    (set! (.. this -style -color) (rand-color))
    (set! (.. this -style -backgroundColor) (rand-color))))

(defn app []
  [:div
    [:h1 {:on-click colorize} "Hello"]
    [:button {:on-click colorize} "Click Me"]])

Chase15:01:30

I have also tried to have my `colorize` function take an element parameter like "button" and then access that but it doesn't work. My `change-background` function works to change the whole document but not sure how I can generically change elements as they are passed to the `colorize` function.

Chase15:01:37

I am getting a "Cannot set properties undefined (setting 'color')" error when clicking.

jkxyz16:01:22

The two examples are different in that the first is attaching the event handler directly to the element, where this is bound to the event target (the element). In the second case, I guess you're using Reagent or some React wrapper, where the onClick prop is passed through React's machinery and this is bound to undefined. In both cases, a better way to get the element is through the event object which gets passed to the handler. :on-click (fn [event] (js/console.log (.-target event))) (.addEventListener el (fn [event] (js/console.log (.-target event))))

Chase16:01:32

This was my other attempt that isn't working either:

(defn colorize [element]
  (let [el (gdom/getElement element)]
    (set! (.. el -style -color) (rand-color))
    (set! (.. el -style -backgroundColor) (rand-color))))

Chase16:01:25

Ok, thanks @UE72GJS7J! I'll check this out.

jkxyz16:01:59

I wouldn't use this in this scenario 🙂 In general it's only used for object-oriented stuff. For event handlers, the event object contains everything you need

Chase16:01:38

Got it. I'm still learning web dev in general (not just how to do cljs) and all these tutorials have this all over the place. haha

Chase16:01:48

This did work:

(defn colorize [event]
  (let [el (.-target event)]
    (set! (.. el -style -color) (rand-color))
    (set! (.. el -style -backgroundColor) (rand-color))))
ty!

jkxyz16:01:46

It's kind of an old jQuery style to do things like <button onclick="$(this).foo()">, but totally different in the React world 🙂 I recommend starting with React-specific tutorials if that's the way you want to go. There's a lot out there

Sam Ritchie19:01:15

hey all - I am looking to write a macro that includes a call to (Math/abs x). it is currently macroexpanding to (.abs java.lang.Math x)… does anyone have advice on how I can have this appropriate expand for use in Clojurescript, to a call to (.abs js/Math x)?

Sam Ritchie19:01:27

actually that is probably just what to do, write it like that vs trusting Math/abs to expand…

p-himik19:01:30

In CLJ, (Math/abs x) becomes (. Math abs x). In CLJS, (Math/abs x) remains that, but the compiler has a special case for Math, so in the end it works just like (.abs js/Math x). Given that macros expand during compile time, in CLJ, I don't think you can have plain (Math/abs x) there - you have to make your macro conditional on whether it expands into CLJ or CLJS code.

p-himik19:01:08

As an alternative, I think, you can write your own abs, put one copy into the CLJ file with the macros, put one in the corresponding CLJS file, and in the macro just use FQN for abs.

Sam Ritchie19:01:36

it worked to use fork from macrovich to do just what you suggested:

(defn- klein-term [acc delta]
  `[sum# (+ ~acc ~delta)
    ~delta (if (ud/fork
                :clj (>= (Math/abs ~(with-meta acc {:tag 'double}))
                         (Math/abs ~(with-meta delta {:tag 'double})))
                :cljs (>= (.abs js/Math ~acc)
                          (.abs js/Math ~delta)))
             (+ (- ~acc sum#) ~delta)
             (+ (- ~delta sum#) ~acc))
    ~acc sum#])

Sam Ritchie19:01:44

ud/fork is the macrovich “fork”

p-himik19:01:11

Just in case - assuming the macro can accept something that's not just a number but rather a form, you definitely need to add (let [acc# ~acc] ...) at the top and replace all usages of ~acc with acc# so that the same thing is not recomputed multiple times.

p-himik19:01:31

As a small example of that, check out (source and).

Sam Ritchie19:01:33

ah! good call, in this case it is actually just accepting a symbol

👍 1
Sam Ritchie19:01:06

it’s private, and participating in generating entries for a big let binding (that’s enforced by the ~acc sum# which will explode in the generated macro if acc is not a symbol

Sam Ritchie19:01:23

but yes that is a great tip and I am glad to have it refreshed…

Alex Miller (Clojure team)20:01:30

as an aside... Clojure 1.11 will add a fast polymorphic clojure.core/abs in the next alpha (and I expect CLJS will follow as well), kind of a fallout from https://clojure.atlassian.net/browse/CLJ-2673

🎉 2
Alex Miller (Clojure team)20:01:26

and in general @U017QJZ9M7W you'll get best results for your stuff using the fns in the new clojure.math lib (which will also be cljs portable thanks to @U051N6TTC!)

❤️ 2
Sam Ritchie00:01:35

One other item on my wishlist that I can't resist sharing is “transductions”, analogous to reductions. Stream of results of transducing successively longer prefixes of the input @U064X3EF3 . Or maybe something exists already?

Sam Ritchie00:01:02

I can build it out of reductions and sequence of course

Alex Miller (Clojure team)01:01:47

yeah, it has a transducer reductions

👍 1
quoll03:01:02

Thanks for the reminder @U064X3EF3. There were some inlining issues that I wanted to fix. Done now 🙂

thheller06:01:50

as a general tip for cljc macro cases I would recommend writing the macro to just call a function (eg. my-abs) and then have that function handle the #?(:cljs ... :clj ...) cases there. IMHO you should avoid host specific conditionals as much as possible in macros.

Sam Ritchie21:01:50

@U05224H0W I was groveling at the feet of the “Performance” gods, wanting that particular call in-lined…

Alex Miller (Clojure team)00:01:46

given how much boxed math is happening here (100x slower), inlining a call to abs is the least of your worries :)

Alex Miller (Clojure team)00:01:44

oh wait, this is cljs, was referring to what I'd expect from this in jvm

Sam Ritchie00:01:06

Yeah, i need a major tutorial on detecting and eliminating boxing

Sam Ritchie00:01:14

In hot loop code…

Alex Miller (Clojure team)00:01:06

well if you're using anything other than let and loop, you're probably using boxed math (any seq function, reduce, etc)

Alex Miller (Clojure team)00:01:25

which is sad, because the code is usually a lot uglier

Sam Ritchie05:01:45

@U064X3EF3 of course you nerd-sniped me into writing a macro that pushed all of this into a loop/recur

Sam Ritchie05:01:08

(defn- klein-term
  "Takes symbolic variables for

  - `acc`, the accumulating term we're compensating for
  - `delta`, the shared symbol used for deltas

  and generates let-binding entries updating `acc` to `(+ acc delta)` and
  `delta` to the new compensation amount in `(+ acc delta)`."
  [acc delta]
  `[sum# (+ ~acc ~delta)
    ~delta (if (ud/fork
                :clj (>= (Math/abs ~acc)
                         (Math/abs ~acc))
                :cljs (>= (.abs js/Math ~acc)
                          (.abs js/Math ~delta)))
             (+ (- ~acc sum#) ~delta)
             (+ (- ~delta sum#) ~acc))
    ~acc sum#])

(defmacro ^:no-doc kbk-n-sum
  "Given some order `n`, generates a function implementing fast `n`-th order
  Kahan-Babushka-Klein summation.

  See [[kbk-n]] for more detail."
  [n]
  (let [syms   (into [] (repeatedly (inc n) gensym))
        zeros  (map (fn [i] `(~'double ~i)) (repeat 0.0))
        prefix (pop syms)
        final  (peek syms)
        delta  (gensym)]
    `(fn [xs#]
       (loop [i# (long 0)
              ~@(interleave syms zeros)]
         (let [~delta (nth xs# i# nil)]
           (if (not ~delta)
             (+ ~@syms)
             (let [~@(mapcat #(klein-term % delta) prefix)]
               (recur (inc i#) ~@prefix (+ ~final ~delta)))))))))

Sam Ritchie05:01:20

4x faster than using the fold version

👍 1