There are some superb things about re-frame, including:
It’s well thought-out, with excellent documentation
It works fine in React Native and CLJC
It makes larger scale Reagent apps tractable by bringing structure to their binding and firing paradigms.
However, is also does some things I would consider un-clojurey:
The event and subscription store is essentially defining global scope (that’s its whole point); usually we don’t abide global scope
by implementing its reg-sub and reg-event DSL, it circumvents standard tooling by treating keywords as deffed symbols
So I’m experiencing some cognitive dissonance reconciling re-frame and the usual Clojure philosophies. Is it a compromise, a matter of necessary evils being constrained to make SPAs more tractable? Is it a direct question of whether these things are really evil? Or is there a more nuanced way of thinking about this?
In Re-frame, Re-frame is the definition of virtue. When you are in the Re-frame world, accessing norms from outside Re-frame is decidedly non-virtuous. (Those norms would have to have been injected by an interceptor for you, and they weren’t.)
In the “pro” column, Re-frame aspires to a familiar, yet elusive, ideal of simplicity. In the “con” column, whether Re-frame’s tangible manifestation is an abomination, or not, or whether it’s an abomination that might as well be regarded as consistent with virtue, should be considered in light of the alternatives. That is because virtue and truth are closely tied. Re-frame may be bizarre and repressive (and vaguely Spring-Framework-like in the most negative sense), but Re-frame focuses the programmer’s discipline and leads to effective, crisply functional solutions. In a word, Re-frame is true. Without going into details here, let me just assert that all other known techniques are false. Therefore, Re-frame, however repugnant it may seem on the surface, wins the virtue battle by default.
I know it is exciting and sometimes useful to explore subjects with open questions designed to provoke debate. But IMO you have to be careful about the question you ask, or the answers that follow are … uninteresting.
So my question is: “Have you asked the right question?” Have you asked a question which leads to interesting answers?
To me, “Is it virtuous?” is not a useful question. As the author, I am acutely aware of how I could have made re-frame more functionally virtuous. So why didn’t I?
Perhaps a more useful question might be: “Does re-frame embody a good set of tradeoffs and in what circumstances?”
Every solution is “a point in a design space”. Tradeoffs are everywhere. And necessary.
Finally, to actually answer your question … there’s an FAQ entry which might help:
I never used re-frame, so I can’t speak directly to it. But of the virtue of Clojure in general. If you look at it, Clojure doesn’t frown upon mutation, as long as it is thread safe, and encapsulated within a context.
That means that components shouldn’t be coupled to their environment, and thus they shouldn’t depend on finding things in external locations not under their control. It also means you should be able to reason independently of the rest of the application about the behavior of a component. That could extend therefore to testing it.
Shared state and mutation often fail at the above “virtue” of having independent components that do not depend on their environment or other components to have put things in the right place at the right time.
But you always need something to tie together such components, and orchestrate the data exchanged between them and their order of execution. A dataflow/workflow of some sort, describing the structure and the timings of the components together, and their interaction with the user of the application.
This application framework, Clojure tends to be okay with it performing side effects and using global state. This is often where Clojure distinguishes itself as an impure functional language. It allows the application framework to break purity, while strongly pushing for the components to maintain it.
Since I know nothing of re-frame, I don’t know where it stands within this view. If it acts as such an application framework for independent components, then I’d say it would follow along. If it doesn’t, it is probably breaking with the Clojure mentra somewhat, but hey, if it makes you productive, results in low defect code, remains open to painfree and quick future evolution and extension (adding new features to your app), and makes things understandable and easy to debug, maybe it doesn’t matter.
@Webdev_Tory, you mention reg-sub and reg-event in particular - in my opinion the use of a registry is really the only “problem” with re-frame. Scare quotes because it’s really not an important problem. A year or two ago I experimented with removing the registry from re-frame, to see what the implications would be (code here), and I found that you can retain just about everything that makes re-frame good and get back jump-to-definition and other nice things (better DCE/minification, easier to pick names for subs+handlers) that come from using vars instead of fns-named-by-keywords-in-a-map. I think the biggest pain-point with re-frame’s approach is that you end up needing to grep for handlers and subs (because you don’t need to require anything to use them), whereas with regular defns the name tells you the location. You could get that with re-frame with more discipline in your naming conventions than I’m capable of.
I ultimately didn’t pursue this any further because: 1) it’s a big breaking change to re-frame and 2) It doesn’t seem like a big improvement. I do think it is a small improvement though.
Excellent answer, including the FAQ link. Thanks! In addition to highlighting the fact that holding a registry is not all that different from what Clojure is doing anyway, I’d answer “is this the right question?” with “yes.” It was from an honest place of cognitive dissonance and the discussion that has come has definitely served to address that. Thanks, everyone!