Hacker News new | ask | show | jobs
by dustingetz 342 days ago
a problem with a repl-centric approach to crud apps is that the repl is not reactive. It is a good fit for request/response pure function backend programming. But UIs are not pure functions, they have a deeply effectful nature, and being reactive all those effects are highly “situated” if you will (to use Rich Hickey’s word). IMO this is a severe impedance mismatch at the core of Clojure’s design. We simultaneously want to write small functions that can be explored in the REPL and we also want to write in-situ functions with complex dependencies in scope. ClojureScript users are running in circles for a decade trying to turn the latter into the former to make the REPL work again but it’s a mirage, the REPL is just not a fit for a deeply situated problem domain. An example of this impedance surfacing in backend programming is a nontrivial map/reduce pipeline. What is the shape of the document at stage six? How do I manifest one to play at the REPL? Clojure doesn’t have great answers to this.
3 comments

> But UIs are not pure functions

Most of us (developers using ClojureScript) are using React or similar "view-as-a-function" libraries, where the UI is essentially built from pure(-ish) functions.

I'm using the REPL as effectively with ClojureScript + Reagent as I would with any Clojure program. The only thing that matters is how you structure your application, and it's certainly possible to iteratively develop frontend applications with ClojureScript just like what we do with Clojure.

UI is not just DOM it is also network connections, events from the user, and local state. React only datafies the DOM orchestration. The UI=f(state) composition model fails at all of these points, most vividly at the network boundary, but does not include answers for the other domains I listed as well.
Right, but when actually working on UIs, do you need to replay the actual side-effects of those things, or just whatever happened before/after of that?

Personally I split those things up into two parts, and only iterate on the UI with "static data" that either exists before, during or after. So testing/iterating on things like "Click button, loading animation plays while network request is in flight, show success/failure" is essentially 4-5 "static" states, and you don't really have to care about what happens in-between much except for corner-cases.

in my opinion it is the events and state transitions that are where everything interesting and challenging happens, like server IO in mount/unmount/didUpdate lifecycle methods or event callbacks. For example, a typeahead component. You focus the input and start typing. The picklist unfolds and loads records from the server, we need to stream subsequent keystrokes to the server into the query for server filtering, we need to maintain the DOM gracefully as the query changes stream in, we need virtual scroll if the collection is large, the user then uses the arrow keys to select an item in the picklist and presses enter to submit, closing the picklist and returning the selection to the caller. It is a living, reactive process. Now "use the REPL" to get, like, any value at all?
> But UIs are not pure functions, they have a deeply effectful nature, and being reactive all those effects are highly “situated” if you will (to use Rich Hickey’s word).

Oh, but we do this in ClojureScript the whole time.

You can express events and actions in pure functions as data and then take care of the effects outside of the core of your application.

A great example of how to do this is the "re-frame" framework [1] [2]. The documentation is also a joy to read.

> An example of this impedance surfacing in backend programming is a nontrivial map/reduce pipeline. What is the shape of the document at stage six?

There's many ways to find that out in Clojure/Script. There's the built-in `tap>` function [3] which can be used for debugging. You can use that to visualize complex data with Portal. [4]

    (defn inspect
      [coll]
      (doto coll tap>))
    
    (->> coll
         (map this)
         (inspect)
         (filter that))

[1] The framework: https://github.com/day8/re-frame

[2] A library with HTTP-Effects for your re-frame applications: https://github.com/Day8/re-frame-http-fx

[3] https://clojuredocs.org/clojure.core/tap%3E

[4] https://github.com/djblue/portal

what great answers could look like here?

A one way to address that is via tooling (e.g. to visualize state across time) but maybe there’s something more fundamental to be solved here? Or is it more about current tooling capabilities?