Hi guys,
Quick disclaimer
I am very new to Elm and while reading An introduction to Elm
something bugged me. The fact that only views are shown as composable (cf. https://guide.elm-lang.org/reuse).
I tried to find an elegant solution to let sub components/modules/functional slices of the app/… update their own model without having to know about the top level app model.
This is a very humble proposal and it’s probably missing a lot of cases (for example Command
are not handled in my current proposal). I am looking for feedback on this idea. There might be a way that I missed to do that already, so I am up for any feedback
It’s highly inspired of the Cycle.js
way to do that (with cycle-onionify
and lenses https://github.com/staltz/cycle-onionify).
In the rest of the issue I’ll use the term component
to talk about a functional slice of an app (something that knows how to render, that possesses the business logic to update its own state and that returns some effects (the former is not part of this proposal)).
My problem: Components cannot define their own update
function
So far, what I see is that there can only be a single signature for any update function of the app update : AppMessage -> AppModel -> AppModel
.
Whether you are building an update
function for the root component or for a nested leaf component, the function will always take a full AppModel
.
The problem is that leaf component then need to know about the structure of AppModel
and cannot be reused in another context (only their view can be reused).
Concrete example: the checkbox
example
Let’s work on a concrete example to illustrate my proposal. The checkbox
example is a good one I think http://elm-lang.org/examples/checkboxes
The model looks like this
type alias Model =
{ notifications : Bool
, autoplay : Bool
, location : Bool
}
The views are factorized with the checkbox
function
The update
method is defined by the parent that handles all the messages from the checkbox
views.
Lenses to the rescue
The idea is to be able to select
a specific slice of the main model and let the component
work on that slice independently. This way the component
can work without knowing anything about the app structure.
In order to achieve that, I came up with an isolate
function that will feed the component the right slice of the model and when an update occurs, update the parent model with the updated model of the component.
type alias Getter parentModel childModel =
parentModel -> childModel
type alias Setter parentModel childModel =
parentModel -> childModel -> parentModel
type alias Update message model =
message -> model -> model
isolate : Getter parentModel childModel -> Setter parentModel childModel -> Update message childModel -> Update message parentModel
isolate getter setter update message model =
setter model (update message (getter model))
Final result
Here is the current update
function of the given example
update : Msg -> Model -> Model
update msg model =
case msg of
ToggleNotifications ->
{ model | notifications = not model.notifications }
ToggleAutoplay ->
{ model | autoplay = not model.autoplay }
ToggleLocation ->
{ model | location = not model.location }
Here is the isolated version
-- checkbox.elm
checkboxUpdate: Msg -> Bool -> Bool
checkboxUpdate msg model = not model
-- main.elm
notificationUpdate = isolate (model -> model.notification) (model -> notification -> {model | notification = notification}) checkbodUpdate (\_ -> model -> model)
-- same for autoplay and location
update: Msg -> Model -> Model
update msg model = notificationUpdate msg >> autoplayUpdate msg >> locationUpdate msg
This enable easy reuse of components at different level of the application without having to write a reducer every single time.
Limitations
This proposal is not yet a viable option for, as far as I know, two reasons:
-
Commands
are not handles (I might have a idea for that) - messages are not isolated, meaning that all the
checkbox
will react to a click on a single one of them (I have no idea yet how to fix that). - … probably some other limitations that I cannot see due to my limited knowledge of Elm.
What I am looking for?
Pretty much anything from “this is a super bad idea because of this, this and that” or “this is not the elm philosophy at all” to “Good idea, I might have some idea to fix the limitations” or “Well, is already possible with this solution”.
Working on this was super interesting anyway and I really had a great time working on this, so even if this is a bad idea, my time was not lost at all
BTW: really the language!!