Eonil, 2020.
This explains how I organize code of interactive programs.
- As a one word, this approach is Mostly REPLized, a little more functional-styled MVC.
- The simplest and easiest interactive app implementation strategy is REPL. (e.g. Elm, Redux)
- “eval” is a function which converts input (state + user control) to output (state + rendering)
- REPL is a sort of origin of all interactive apps.
- REPL is easy to design, implement and test.
- REPL is oneway data flow.
- But Apple frameworks are all designed in MVC. (including SwiftUI due to data binding)
- Apple frameworks do not guarantee oneway data flow.
- Although, they are slowly moving to REPL…
- Bidirectional data flow can remain and involve at any time.
- Strict REPL structure would make things painful and difficult to maintain.
- Therefore, we must keep MVC structure at the base.
- But construct details in REPL manner on MVC base.
- Instead of calling command methods of model, sends command packed in an immutable value.
- Instead of calling rendering methods of views, pass immutable state snapshot packed in a value.
- Some command value can be executed at controller level to support MVC.
- This has to be minimized. Use this only absolutely needed.
- Record versions of each state to avoid full diff cost if possible.
- Perform diff only if needed. See “Assumptions for Performance” section about diff performance.
- Model is the business logic.
- Therefore, model is isolated and independent.
- View and controller are only implementation details.
- Model don’t depend on them at all.
- Model shouldn’t even know their existence. (isolated model)
- Model is a collection of discrete states and defines clear input and output.
- View is an implementation detail.
- Every isolated component should be abstracted with interfaces.
- Expose only minimum interface that are absolutely required.
- Hide all the details that are not necessary to public users.
- Large apps are complex. Reducing overall complexity is very important.
- Model is fully REPL based. (e.g. Elm, Redux)
- There’s no need for anything else.
- Model can be divided into several subsystems.
- Each subsystem can work like a smaller model. (sub-REPL)
- Model accepts and queues action commands and executes them serially.
- Model updates model states and sends appropriate commands to subsystem components.
- Model processes outputs of subsystem components and update its state again.
- Model announces updates in state to public at consistent moment.
- Owner of model (controller) should scan model state and pass it to view.
- Inversion-of-Control principal wants view to have dedicated, isolated and abstracted interfaces.
- In other words, MVVM.
- But in many cases, view interfaces are exact replication of model states.
- In that case, why should I copy same interface?
- We don’t need to replicate them. Just use model state types as-is.
- View can depend on model.
But what about reusability?
- UI of a UI app is dedicated only for single app.
- UI is not re-usable. Only a few of low level components of UI are re-usable.
- Such re-usable low level components need to have isolated and dedicated interface.
- Such components are mostly already defined in platform UI frameworks.
- You usually don’t need to write reusable view component.
- Unless you are a platform vendor like Apple or Microsoft…
- Even in this case, re-usable components are supposed to be defined at design level.
- Composition of such low level components are usually coupled with specific model features.
- It’s natural to use model state types as-is.
- Sticking to reusability seriously limits flexibility of UI design.
- Reduced flexibility in UI design produces lower quality UX.
What about testability?
- In this case, model output becomes view input.
- In MVVM term, a view-model.
- So, testing model output is testing view-input.
How about testing of view internals?
- That should be done at view level without involving model or controller.
- Testing of view state (model output, view input or view-model) to final rendering would require manual test.
- In most cases, there’ no good way to test highly interactive custom UI.
- Shape of model is bound to shape of the app.
- Don’t forget that model of a UI app is mostly designed to represent UI.
- No one can predict business requirements.
- Any part of an app must be able to access and render any part of model state.
- Therefore we need to pass whole model state to each screens.
- Therefore we always pass whole model state snapshot to view.
- GUI apps are designed for human users.
- Humans cannot handle large amount of data at once.
- About <100 would be the maximum. (Or a few hundreds)
- Listing >100 items in GUI is meaningless.
- You are supposed to render only small portion of total data.
- Therefore, such cases should be prevented at design stage.
- Therefore, most collection-like data for GUI is limited 100 elements.
- This is O(100), and we can consider copy/diff as O(1) in most mobile devices.
- We can deal with exceptional cases using several tricks.
- B-Tree, version/op recording.
- But this needs far more efforts to solve.
- Anyway, exceptional cases are rare.
- Take benefit of simplification in most cases.
- Design becomes more important.
Anyway we still can have large amount of data. For example, complex graph points.
- Usually, such large data is provided for snapshot based rendering.
- Such cases usually does not require diff. Just render them all as a snapshot.
- And visibility cullig if needed.
- Logically, this is single item with large attached data.
- Well, some data can be divided into several pieces, but number of pieces will be in human-recognizable range.
- If we can sure that depending frameworks are all MVC-free, we can easily convert program into REPL.