Model as a relational database for components

Hey everyone,

I was listening to Richard Feldman’s talk on relational immutable data. One of the big takeaways from this talk is that when dealing with immutable data we want to avoid nested data and have a single source of truth.

In Elm this is very difficult when we have things that are for all intents and purposes are a “component”. I define a component as something that has it’s own view model and update function. Often we don’t need them but sometimes they are unavoidable like Elm-sortable-table and page navigation. In the Elm spa example, the approach in elm-spa-example suffers from a few problems:

  1. Potential for multiple sources of truth if pages have shared data.
  2. We have a cardinality that is too high in our update function. We need to account for cardinality(Msg) * cardinality(Model) amount of different values when in practice the cardinality is only Msg. There are a lot of cases that we have to ignore that are invalid states that should never happen. We also have a default case (_,_), which means that there are no static guarantees that we’ve handled all pages.
  3. It’s cumbersome to modify components and adding new ones.

Edit:
I made an attempt at implementing a relational component structure and you can find the result down below with the pros and cons of this experiment.

Wouldn’t mind trying it out, but this link has expired. Have you got another?

I’ve done some refactoring and cleaned up the code quite a bit. I uploaded the result also below. I’ve also cleared up my thoughts a bit so I can summarize the pros and cons with this approach. The demo demonstrates how we can easily wire together multiple components that have the same parentId. We can also still control how the result should be displayed. This allows us to create reusable forms and we can wire them together easily as well.

The source code is here:

The benefit to this model over the one in Elm-spa-example:

  • Easy reuse of components and nesting them. While more exploration is needed it should be possible to compose forms or have a “list of components”.
  • Model has a flat structure even if we would have a page with a component.
  • We have a clearer separation from the Model and our view functions which allows us to share data between pages. However this might be an anti-pattern because if we need data sharing between components it’s a sign that data should be moved to the top.

The drawbacks:

  • All components are in a big sum type, basically rendering the component dictionary dynamic. That means that if wrong id is fed it will overwrite a component with a different component. There should be a way to mitigate this by testing that no component gets overwritten using property-based testing and smart constructors but I haven’t explored it.
  • Views become more complicated. We can think of the view as a tree while the model is a relational database. Therefore the view needs to convert the relational model into a tree which is a bit cumbersome. This also leads to the next point.
  • Even though we know beforehand which keys are safe, Elm doesn’t have justified-containers. This is something that could easily be added to the language without adding more language features. Essentially it would be a dictionary where, once created, you can only update the items and not remove or insert new ones. Then when using the library you have validate the keys beforehand which makes item fetching safe so you don’t need to null check on every fetch.

What about passing that shared data as an extra argument to the inner update functions? ie. it’s not part of their model, but of the parent model.

parentUpdate msg model =
    -- snip
    childUpdate model.sharedData childMsg model.child

childUpdate sharedData msg model =
    -- snip

Yes that’s correct. However what this solution does solve is when we have components that have relations, best example would be forms. With this solution we can easily add 100s of forms into a single page and then when we want to send that data we can query our state and send the result. All of this done without having nested models.

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