Functional Core, Imperative Shell in Clojure(Script) Frontends

What’s the state of the art for implementing the Functional Core, Imperative Shell approach in ClojureScript (or Squint / Cherry) frontends?

In React, it’s hard. Functional components seem like pure functions, but as soon as you introduce state (via useState or a state management library), they’re not. Their result is no longer the result of their props (arguments); you have to peek at the implementation to see what state it might be subscribing to.

You can separate things out into stateful components that delegate to pure presentational components. However state can be infectious. Someone introduces a wee bit of state to primitive component and boom, all previously pure components that delegate to that component directly or indirectly are now impure.

Plus, separating into pure vs stateful components is more boiler plate that developers tend to avoid. Hooks are so easy to use that they tend to encourage throwing a little state directly into a previously pure component. Maybe that’s wrong, but keeping things pure feels like swimming upstream.

UIx and similar libraries that embrace React are great, but would seem to have the same issue.

I’ve been peeking at Reagent, and while it seems very elegant, as soon as you use a stateful atom in a component, it’s no longer pure; similarly if subscribing to a query in Reframe. I guess this is not surprising since Reagent is React under the hood. The fundamental building blocks of React are components, and React components are stateful with a lifecycle. They’re not pure functions (appearances to the contrary).

Prop drilling is a solution for small clusters of components, but that doesn’t scale very well.

Is there a solution for this problem? Is React the problem, SPAs in general, or something else?

The best I’ve been able to come up with so far is to aggressively extract out pure functions wherever, using TDD to guide: if mocking is hard maybe that’s an opportunity to pull stuff out into more easily testable pure functions. But it’s a slog.

1 Like

What exactly is “the problem” you’re referring to here? Mutable state is always going to be there in some form or other, especially when you are dealing with front-end development but also elsewhere, and as such it is impossible to completely eliminate it. Whatever any given framework gives you, you have to ask yourself if it’s good enough, easy enough, simple enough and so on for you to use it for any given task. Even if it isn’t “purely functional” or doesn’t conform to a given desirable pattern.

When you pick up React, Vue etc you end up working in the way they prescribe - lots of local state and imperative code in components by default. In that world I think extracting pure functions is the only thing you can do. You still need a lot of a framework level things though.

Mike Thompson (re-frame creator) gave a great critique of hooks here: Possibility to use without Reagent? · Issue #590 · day8/re-frame · GitHub

I am using Helix for performance and convenience and accept that fighting the hooks battle is not worth it for me

Having used Vue and Svelte I do think that modern React gives the best ‘functional’ feel even if it is largely doing side effects

1 Like

Helpful reply thanks.

In that world I think extracting pure functions is the only thing you can do.

I’m coming to the same conclusion.

accept that fighting the hooks battle is not worth it for me

That makes sense.

For context, I have just a little Clojure(script) experience, and tons of React experience. Too much actually; I’m ready for a change of pace.

My impression is that the React world has finally roughly caught up with where Reagent was at in 2014. Combine hooks and a good state management library like Jotai with Helix or Ulx and you end up in roughly the same place: a reasonably concise, pretty decent React based solution in Clojurescript. Use Cherry and you can even hook into state of the art JS build systems and frameworks like Next if you want.

In my experience scaling React apps, even with good frameworks, gets hard past a certain certain point because of “lots of local state and imperative code in components” (or hooks) as you put it. On large apps it gets hard to reason about what components might change when state changes.

That’s an interesting critique of hooks from Mike Thompson; thanks for sharing that. For me the takeaway there is that components should just be views.

It bugs me a little that in Re-frame you have to peek at a component’s implementation to see what subscriptions it depends on. A macro or something that made that explicit might help me, or maybe just the convention that subscriptions are used in let block at the very top of the component is good enough. In any case I really like Re-frame’s emphasis on components just being views.

I also need to look at Electric. Maybe something that fully embraces FRP is what I’m looking for. React + a good state management framework handles the “R” part well enough, but I’m looking for something that also emphasizes the “F” part. React falls down there - it looks “functional-ish”, but looks can be deceiving.

I try to keep the components as dumb as possible and only have subscriptions/effects/etc at the top level of a page. The rest could be expressed as pure data structures, and a library like dumdom, where events can be expressed as pure data, makes everything a lot nicer.

Example from the the readme:

(require '[dumdom.core :as dd])

(def el (document.getElementById "app"))

(dd/set-event-handler!
 (fn [e actions]
   (doseq [action actions]
     (prn "Triggered action" action (.-target e)))))

(dd/render
 [:div
  [:h1 "Fruits"]
  [:ul
   [:li {:on-click [[:fruit/select :apple]]} "Apple"]
   [:li {:on-click [[:fruit/select :banana]]} "Banana"]
   [:li {:on-click [[:fruit/select :kiwi]]} "Kiwi"]]]
 app)

Note that the library is completely agnostic as to what data structure the events are, that’s up to you. And pair this with clojure.walk for interpolating event data into placeholders in the top level event handler, something along the lines of [:my-event :event/target-value], and you’ll be able to build up a pretty expressive system with very few building blocks.

2 Likes

There’s a great talk about dumdom and a functional approach to UI:

1 Like

Hadn’t seen that before. That’s a great presentation!

Oh wow that is a great talk!

Dumdom seems like exactly what I’m looking for. UI Components are truly pure and take just data in explicitly declared props. “Business logic” and state management functions are neatly separated from the pure UI components. It does indeed seem like “Function Core Imperative Shell” applied to web apps.

Thanks @greinseth and @rtb!

It’s like the good parts of React, but without stateful class components or “functional” components that use stateful hooks.

I’ll check out dumdom more. Thanks

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.