code-reviews

adam 2020-07-01T20:22:19.087Z

Which one of these two functions is considered better:

(defn navbar-toggle []
  (when-let [burger-elem (gdom/getElementByClass "navbar-burger")]
    (let [target-name (gdataset/get burger-elem "target")
          target-elem (gdom/getElement target-name)]
      (gevents/listen
        burger-elem
        EventType.CLICK
        (fn [e]
          (gclass/toggle burger-elem "is-active")
          (gclass/toggle target-elem "is-active"))))))
(defn navbar-toggle []
  (when-let [burger-elem (gdom/getElementByClass "navbar-burger")]
    (gevents/listen burger-elem EventType.CLICK (fn [_]
                                                 (gclass/toggle burger-elem "is-active")
                                                 (gclass/toggle (gdom/getElement (gdataset/get burger-elem "target")) "is-active")))))

2020-07-01T20:23:20.087500Z

I like the first, though I'd use _ instead of e since e isn't used

➕ 1
2020-07-01T20:23:53.088Z

the second one is character and line count but in a way that makes it harder to read

2020-07-01T20:24:17.088600Z

their performance will be identical at runtime if I am not misreading

adam 2020-07-01T20:27:39.089600Z

Ok thanks, I needed a confirmation that I am not abusing let 🙂

2020-07-01T20:28:55.090Z

no, that's a perfect usage of let

2020-07-01T20:29:38.090500Z

one thing though - how often would gdataset/get return a different result if called later in the callback?

2020-07-01T20:29:54.091Z

if that answer isn't "never", you should move the let into the fn

2020-07-01T20:30:54.091800Z

that goes for gdom/getElement too of course - to be safe I'd probably want to move the let into the fn, and view hoisting it out as a potential optimization

adam 2020-07-01T20:34:18.093100Z

Wouldn’t that be theoretically slower? I assume the binding will be recreated on every click on the element

2020-07-01T20:35:13.093400Z

right - my concern is whether it would be correct

2020-07-01T20:35:35.093900Z

for example, with react you can see the dom nodes destroyed entirely and replaced

2020-07-01T20:36:19.094700Z

but maybe your knowledge of your app lets you know that the element remains valid in all contexts where that click can happen - that's why I referred to it as an optimization

phronmophobic 2020-07-01T20:38:50.095700Z

even if it's theoretically slower, it's unlikely to make a practical difference unless (gdataset/get burger-elem "target") is really slow

2020-07-01T20:39:30.096300Z

right, "root of all evil" and all that, only do optimizations when you know they will matter because they carry a cost

walterl 2020-07-01T20:42:08.098300Z

A colleague follows the rule of thumb that let-bindings that are only used once, are superfluous, as it is in the first implementation. I've come around to defaulting to that too. The second could be made a bit easier to read, though:

(defn navbar-toggle []
  (when-let [burger-elem (gdom/getElementByClass "navbar-burger")]
    (gevents/listen burger-elem
                    EventType.CLICK
                    (fn [_]
                      (gclass/toggle burger-elem "is-active")
                      (-> (gdataset/get burger-elem "target")
                          gdom/getElement
                          (gclass/toggle "is-active"))))))

2020-07-01T20:42:52.098800Z

> let-bindings that are only used once, are superfluous

2020-07-01T20:43:32.099500Z

hard disagree, a let binding is free in terms of performance / implementation complexity, and increases clarity

practicalli-john 2020-07-02T10:10:43.102300Z

In my opinion, inverting the rule of thumb would be more useful. "If something is used more than once, consider a let binding" Defining practices in the negative are less likely to be adhered to, as they block rather than guide. I also find let bindings offer more clarity to code, which is vital if there is a production issue that needs to be fixed quickly.

💯 1
2020-07-02T14:29:27.102600Z

I like that way of formalating it, and will be adopting the "guide rather than block" criterion 😄 thanks

practicalli-john 2020-07-02T15:15:47.102800Z

consider it a small contribution compared to everything I have learnt from you here.. thank you 🙂

adam 2020-07-01T20:44:19.100300Z

I tried to use this version of when-let to group things, but still struggling with compiling macros for CLJS.

(defmacro when-let*
  "Multiple binding version of when-let"
  [bindings & body]
  (if (seq bindings)
    `(when-let [~(first bindings) ~(second bindings)]
       (when-let* ~(vec (drop 2 bindings)) ~@body))
    `(do ~@body)))

walterl 2020-07-01T20:56:29.100500Z

Isn't "hard" a bit of a... hard position to take? 😝 let-bindings add to the cognative load of the reader, and, names being hard, will often not increase clarity. There's also something to be said for allowing the lispy structure of the (helpfully indented) code to aid in its visual parsing. A part of my colleague's argument that also made sense to me is that, if the inline invocation isn't easy/easier to understand, the problem may lie with the interface(s) of the called function(s). As a concrete example, something like (let [full-name (get-full-name first-name last-name)] ...) would be worse than using (full-name first-name last-name) in the body, in most cases. I think @somedude314's function is also a great (and more practical) example for this. I'd rather go for the let-free version, for readability's sake.

2020-07-01T21:06:36.100700Z

in my experience, code where I replaced let with inline forms has been harder to maintain, let bindings don't add to cognitive load because I know precisely where to look for them and the formatting (if done idiomatically) makes it immediately clear where they are

2020-07-01T21:08:19.100900Z

note that I wasn't making a sweeping declaration that let bindings always increase clarity, I was objecting to a sweeping declaration that let bindings that are only used once are superfluous

2020-07-01T21:09:04.101100Z

let bindings that are used once are especially useful to break the job of reading carefully into distinct and trackable steps

walterl 2020-07-01T21:09:07.101300Z

> rule of thumb

2020-07-01T21:09:45.101500Z

fair, I missed that

walterl 2020-07-01T21:10:21.101700Z

There are many exceptions to that rule.

walterl 2020-07-01T21:10:36.101900Z

Trackable steps being a good one

adam 2020-07-01T21:28:24.102100Z

Ended up with this:

(defn navbar-toggle []
  (when-let* [burger-elem (gdom/getElementByClass "navbar-burger")
                 target-name (gdataset/get burger-elem "target")
                 target-elem (gdom/getElement target-name)]
      (gevents/listen burger-elem EventType.CLICK (fn [_]
                                                    (gclass/toggle burger-elem "is-active")
                                                    (gclass/toggle target-elem "is-active")))))