Skip to content

Latest commit

 

History

History
131 lines (100 loc) · 5.85 KB

2020-05-11-referentially-transparent-guis.md

File metadata and controls

131 lines (100 loc) · 5.85 KB

Referentially transparent GUIs

Back in late 2015 I started working on a project at a client to implement the frontend for a new internal system. To tackle the problem we developed an approach to UI programming that I wish would be more widely understood. Why? Back then I wasn't a veteran frontend developer and, honestly, had doubts about not seeing some obvious faults in our approach, which is why I was cautious about promoting it. Myself and others have since then used it on several other projects and spent a lot of time refining the approach in various ways. Working as a consultant I have now also seen many different UI implementation approaches on many different platforms and my appreciation for what we came up back then has increased. Not a single technical issue that would have invalidated our approach has turned up during the last 4.5 years and we have found gradual improvements to various aspects of the approach.

Pretty much everything else I have seen since then has suffered from one or more of typical problems:

Not reflecting the view hierarchy
In the days of React this may seem odd, but in many UI projects there are places in the UI code where views are constructed programmatically in such a way that one cannot simply take a glance at the code and see the structure of the resulting view hierarchy. We can and should do better:
it is possible to make the structure of the program match the structure of the problem being solved. — Burge
Not reflecting the application structure
In a related way, in many UI projects code has been organized into layers rather than features. A single feature of the program is then spread over several source files in different directories and it is often difficult to understand what constitutes a specific feature.
Lost in plumbing
UI code tends to be full of intricate plumbing. Something happens or changes, and the event needs to be propagated through the UI. More often than not, the amount of hand-written propagation code is non-trivial and a source of major difficulties. Almost every UI I have seen has leaked resources when you just cycle through all the views or occasionally views become inconsistent with the underlying state, because some update is not propagated properly.
Monolithic
Certain parts of UI code tend to be written in non-compositional ways such that they accumulate details from logically separable components. This tends to happen in pretty much every approach. In MVVM implementations the view models tend to become very complex. In Elm and Redux the update functions or reducers tend to accumulate more and more repetitive branches. The major downside of this is that only views or rendering code composes — everything else is tediously hand-written.

On the other, the approach we cooked up, has turned out to be:

Modular
Components, including views and view models, can be stateless and expressed as referentially transparent stand-alone functions that can be used, tested, and — most importantly — understood in isolation.
Plug-and-Play
Components, including views and view models, can generally be easily "plugged in" to their surrounding context with minimal plumbing. This is one of the major insights of the approach and very much an explicit feature rather than a lucky accident.
Refactorable
A complete GUI application can be written essentially as a single expression or parts of it can be extracted into separate functions (view functions, operations on view models, ...) without repercussions, because essentially everything is expressed as declarative referentially transparent expressions. Simple (and still useful) components can be as tiny as one line functions. Complex components can be broken up into understandable pieces that can be organized as desired.
Incremental
When the application state changes, updates to the UI are done efficiently in an incremental fashion such that all and only the affected parts of the UI components are updated to consistently reflect the state. This is also an intentional feature of the approach.
Portable
Our initial implementation was built on top of React. Later we have found out that the same model can be easily built on top of pretty much any legacy UI framework such as WPF, plain DOM, UIKit, or Android framework UI allowing one to easily use 100% of what those platforms offer out of the box.
Interoperable
Components using the approach can generally be used as children or as parents of "native" components.

Phew! I hope I didn't yet manage to put you into sleep! 😅


This story is continued in