HN user

pinchhit

64 karma
Posts0
Comments11
View on HN
No posts found.

A couple of things here:

- Intellij IDEA with the Cursive extension is very popular outside of emacs (I've met more clojure developers who use IDEs than those who don't.)

- Clojure uses the error handling mechanisms of the target runtime. You have try/catch statements and side effects are often used. It's not a no-side-effect language.

- Parentheses are almost always managed with parinfer/paredit and python-style indentation rules in production code I've seen.

You're quite right that performance will be tied to the JVM or V8/SpiderMonkey/etc.

I think Rich's point may be specific to Java switch statements and their limitations. (I know he's familiar with MLs, but I would imagine his frame of reference is mostly colored by Java's control structures.) The idiomatic clojure for your color example would leverage names in the same way:

    (defn color-function [{:keys [r g b]}]
      (* r g b))

I have noticed that our developers coming from javascript pretty much exclusively feel that way (and tend to write sharper UIs.) The ones who come from the JVM and feel more emboldened to write more frontend code thanks to reagent write UIs that don't satisfy product requirements, but are very happy with reagent.

Not that devs coming from the JVM can't contribute! But there is some eyerolling from the devs coming from javascript when the primarily backend devs throw together a too-simple UI and wonder why everybody doesn't use reagent exclusively.

As a person who has and currently does work professionally in clojure (at multiple all-clojure shops): The smart engineer effect is extremely overblown.

You will hire smarter-than-average engineers, but with a caveat that no one talks about: they are all self-selected for being people who enjoy tinkering on computer science problems to a fault, and not necessarily your business. So the breakdown is that you get 10 super-smart people, but 9 of them are focused on myopic bits of code plumbing or reinventing APIs or something, and in practice you only have 1 smart person focused on your business.

As the other 9 continue to reinvent wheels, the nature of clojure being small relative to other open-source communities means that eventually your shiny new clojure tools get replaced by better community offerings, except you don't have the bandwidth to switch over and start using them.

Overall, velocity would be improved if you had hired 1 super smart engineer and 9 mediocre ones.

It's important to note here that while technically clojure can interoperate with java or javascript, in practice everyone expects you to use poorly-maintained clojure-ish libraries to paper over the java/javascript bits, which get out of date quickly. This effect is 10 times worse for clojurescript in the browser, although it's pretty bad for clojure targeting the JVM as well.

This is not what I meant by maintainable. The companies I've programmed clojure in were sizable and bet on it for their entire stack, and hired very sharp senior people to work on them.

Even there, the projects suffered so much to hit release dates and stay understandable as the system grew that velocity suffered relative to peers doing experiments in traditional OO languages. In fact, the maintenance burden (for code composed by smart senior people!) is so high that it's actually convinced me on the virtue of regular OO/procedural languages over lisp.

This absolutely matches my experience as a full time Clojure dev at multiple companies. Leadership is desperate to replace the code with something more maintainable. It's gotten bad enough that I will switch languages for my next job, the headaches are not worth it.

I'll try re-frame-test, thanks! If it doesn't do something like `(with-redefs [re-frame.db/app-db (reagent/atom {})] ...)` it is still open to this bug, though. Because the call to `dispatch` happens inside of a callback the test library has no way to signal that future dispatches coming from a different function context should not be put on the same queue.

It'd be hard to change that aspect of the API now, and I wouldn't recommend you do it; but right now, editor completion and new-user understanding is just better on the reagent side. They do this:

    (defn assoc-foo [db bar]
      (assoc db :foo bar))
    
    (defn fetch-new-foo [db bar]
      (go
        (let [bar (<! (get-bar)]
          (swap! db assoc-foo bar))))
    
    (swap! db assoc-foo bar)
    (fetch-new-foo db bar)
whereas re-frame would make you do this:
    (re-frame/reg-event-db
      ::assoc-foo
      (fn [db [_ bar]]
        (assoc db :foo bar)))
    
    (re-frame/reg-fx
      ::pull-from-channel
      (fn [{:keys [f event]}]
        (go
          (let [resp (<! (f))]
            (re-frame/dispatch (conj event resp))))))
    
    (re-frame/reg-event-fx
      ::fetch-new-foo
      (fn [_ _]
        {:pull-from-channel {:f get-bar
                             :event [::assoc-foo]}}))
    
    (dispatch [::assoc-foo bar])
    (dispatch [::fetch-new-foo])
The first one has a lot going for it; swap! is part of the standard library, has a bunch of docs for free, etc.; any clojure editor will pick up on the use of assoc-foo as a function and give you arity warnings if you pass too many parameters, etc.

I've looked at the larger apps - the way I've seen people be most successful if if they have a child namespace with events, subs, & views laid out on a per namespace basis (`my-ns.events, my-ns.subs, my-other-ns.events, my-other-ns.subs`.) Partially due to the tone the docs take, there's always some pushback as to whether that's the re-frame way to do things, and I'd love to just be able to avoid that whole conversation in the future.

Three things that would make re-frame better, if you want them:

- A supported/blessed way to switch out the entire app-db entirely for the purposes of testing

- Use of symbols rather than namespaced keywords for function dispatch

- Docs that give alternate strategies for code organization (fine to have the events.cljs convention, but would be nice to see some viewpoints like having one per child ns similar to angular's code organization model.)

I've seen developers remark how turned off they got by reading stuff like this on its front page, which is frankly just noise and undermines the goals of a serious project:

This was exactly my experience. Professional clojurescript developer using re-frame, but not typically one to chat on forums. I wouldn't have shared this opinion until prompted to by this forum, but it's an opinion I've held privately for longer.

I've run a team with re-frame and a team with reagent (and a convention of a single state atom.) Reagent by itself scaled far better. I use re-frame if it's a company convention, but I'd far rather just ditch it.

Plain reagent has no unnecessary function registry; mutations are done with `(swap! my-cursor foo/update-foo bar)` rather than the extra overhead of `(dispatch [::foo/update-foo bar])`. This also helps new people who rely on cursive to jump to definitions, and don't have a preferred emacs setup.

Reagent testing can be scoped to a single DB. In re-frame, you can clear the subscription queue (which will not handle callbacks from network calls putting events on to the queue when the callback fires, leading to flaky tests that receive unexpected events.) In reagent, you have more power, since you can call `(reagent/atom {})` with your test state and any callbacks specfic to the test will see their DB, regardless of when they resolve.

Re-frame's single events/subscriptions files scaled horribly; once we got beyond a few hundred business-logic-filled events, velocity on changes involving that file slowed down a lot. We broke convention and moved events into namespaces closer to the code.