There are things you can do in your code to help and things that Elm could do (maybe already does) in its implementation to help:
For the data construction issue, you need to create an intervening function that does the construction:
view model = lazy someViewHelper model.data
someViewHelper data = someView { data = data }
But probably not:
view model = lazy (\data -> someView { data = data }) model.data
The compiler could optimize that but might not. That leads to the list of things the compiler could do (and may be doing) to help:
- Lift constant expressions. This would help with anonymous functions, config records, etc.
- Optimize { m | field = value } to return m if m.field is already equal (referentially) to value.
Sadly, this last optimization doesn't get us anywhere if we are routinely working with tagged union members since we can't just return the value we already have. The compiler could cache the last few values constructed with any given type tag but that creates a drag on the GC.
Mark
P.S. I shifted my code for a while to a form where update had the signature: update : Msg -> Model -> ( Maybe Model, Cmd Msg ) with the maybe on the result being used so that unchanged could be indicated by returning Nothing. This, however, makes sequences of updates harder because we don't have the model always available which is nice for writing operators like the following:
(!>) : (model, Cmd msg) -> (model -> (model, Cmd msg)) -> (model, Cmd msg)
(!>) : (oldModel, oldCmd) update =
let
(newModel, newCmd) = update oldModel
in
(newModel, Cmd.batch [oldCmd, newCmd])
The version that works with maybe results needs triples for the state which can largely be hidden but does tend to then require beginUpdate and endUpdate functions as well.