Also note that while the approach of transforming your model into a more
view-specific data structure — e.g., extracting the pieces of interest —
can be a great pattern in terms of code clarity, it works against using
Html.Lazy. For background on the pattern, see Charlie Koster's piece on
selectors and view models:
https://medium.com/@ckoster22/upgrade-your-elm-views-with-selectors-1d8c8308b336
.

Concretely, structuring your code as:

toViewData : Model -> ViewData

displayViewData : ViewData -> Html Msg


and thence

view : Model -> Html Msg
view =
    toViewData >> displayViewData


works well — particularly if it allows for putting the view function into
its own module where you might feel more free about doing lots of exposes
for Html, Css, etc. However, it also means that the view function and any
pieces thereof cannot effectively be lazy based on the ViewData it is
passed because the ViewData instance will be new each time (even if it is
actually equal to the previous value) and Html.Lazy does a naive (also
known as fast) equality check based on instance identity.

If you know how Html.Lazy works, this issue is perhaps obvious in the code
above, but as view functions get bigger to deal with more complicated views
or moves to its own module, I've seen cases where developers think "this is
an obvious place to put in some laziness" only to have it simply result in
more work for the view system.

Why do we even care about laziness? Because it's the functional programming
answer when engineers from other backgrounds say "You re-render the whole
model on every change? Isn't that vastly less efficient than just updating
the parts of the view that changed?" It's also part of how Elm gets the
performance numbers that it gets on UI benchmarks.

So, great pattern. Use with caution.

Further notes below on laziness. Read if interested.

Mark

P.S. The way to get laziness in the above is:

view : Model -> Html Msg
view model =
    Html.Lazy.lazy doView model

doView : Model -> Html Msg
doView =
    toViewData >> displayViewData

We need to specifically define doView because every time we execute >> we
get a new function instance which will then fail the laziness match.

The unfortunate thing here is that if the model contains pieces that don't
directly affect the display — e.g., a queue of messages to send — then the
laziness also depends on those pieces of the model. Part of the appeal of
the ViewData intermediary is that it projects that stuff away.

P.P.S. Over-engineered solution (though it would also help subscriptions as
well): The implementation strategy that makes Html.Lazy work could probably
be extended to a general recompute pattern with laziness. The basic idea is
that while evaluating the tree, you can look at the previous tree and see
whether a lazy node has the same inputs as before and if so just use the
answer from before. You need a small imperative piece that deals with the
update from one tree to the next but that can live in the runtime.
Something like this could immediately address the potential issue that
calculating subscriptions after every update could be rather expensive
depending on the size of the model and the number of subscriptions. For the
discussion above, one would formalize the notion of view data — with
identity being the simple implementation for converting the model to view
data — and then do a "smart" re-computation of the view data as the first
step in rendering the view.

One could probably even do this without changing the runtime model with
just the addition of a native module akin to the Lazy module. Let's call
that module Cache and give it an interface like the following:

eval : Cache a b -> (a -> b) -> a -> b
    -- (eval cache fn a) evaluates to an equivalent value as (fn a); it may
use and/or update the supplied cache

eval2 : Cache2 a b c -> (a -> b -> c) -> a -> b -> c
    -- (eval cache fn a b) evaluates to an equivalent value to (fn a b); it
may use and/or update the supplied cache


etc

Caches are mutable but opaque state and their specification should mean
that the only visible effect from them should be reduced code execution.

Enhancements around deeper — but non-exception-throwing -- equality checks
could also help drive things back to states that will match with the
laziness match detectors.

If anyone writes this, let me know and I would be happy to start using it.
:-) If I get there first, I'll say so though obviously this would not be
available via the Elm package manager because of the need to access Elm's
internal data structures.


On Wed, Jun 21, 2017 at 9:47 PM, Aaron VonderHaar <gruen0aer...@gmail.com>
wrote:

> There is no performance penalty to passing the entire model to all nested
> view functions.  Elm will simply pass the reference to the object around,
> so the performance of passing the entire model vs passing just the
> necessary parts should be equivalent.
>
> However, the downside of doing passing the entire model everywhere is that
> you have to keep the entire model in your head when looking at any of your
> view functions.  In contrast, if you limit what data you pass to each
> function, then it limits the possibility of bugs and makes it easier to
> focus when you work on the smaller functions.  The smaller functions will
> also be potentially more reusable and it will be easier to set up initial
> data when testing.  This also makes it easier to extract modules later,
> since if all the functions take a Model, then any module you extract would
> also need to import Model and any other modules that data in the model
> depends on.
>
> I also personally find that having the caller process the data (instead of
> having the called function transform the entire model into the data that it
> needs to render) usually leads to a nicer separation of responsibilities.
>
> On Wed, Jun 21, 2017 at 10:03 AM, Tomaž Žlender <tomaz.zlen...@gmail.com>
> wrote:
>
>> Is it performance expensive if each view function gets a whole state as
>> an argument? So instead of passing only a portion of a data that view
>> function needs for rendering, each function gets whole state and inside
>> that function another function is called first that transforms data in a
>> format needed to generate view.
>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "Elm Discuss" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to elm-discuss+unsubscr...@googlegroups.com.
>> For more options, visit https://groups.google.com/d/optout.
>>
>
> --
> You received this message because you are subscribed to the Google Groups
> "Elm Discuss" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to elm-discuss+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups "Elm 
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to elm-discuss+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to