Problem with Html.Lazy

226 views
Skip to first unread message

Yosuke Torii

unread,
Sep 13, 2016, 7:09:05 AM9/13/16
to Elm Discuss
Hi,

I have a question about Html.Lazy.

Now I'm optimizing rendering using Html.Lazy but I feel it is difficult to use. I found the arguments are not often referentially equal and the view functions are unfortunately called. There are some problems around this.


1. Sometimes new values are created every time.

view model = lazy someView { data = model.data }

2. Views do not know how arguments are passed.

view model = lazy grandchildView model

3. It is not easy to know if optimization succeeds or not.

view model = Debug.log "Unfortunately called!" <| text (toString model)


I made a demo to describe this problem.
(9 example views, but 4 ~ 9 are not optimized unfortunately)

So my question is:
  • Is it a bad idea to compare by normal equal? 
  • Have anyone already solved this problem?

Thanks.

James Wilson

unread,
Sep 13, 2016, 9:43:28 AM9/13/16
to Elm Discuss
I had similar feelings; lazy uses a magical referential equality between things that afaik isn't exposed elsewhere; to me it would be more obvious/clear if it used the same sort of equality as everything else (==), which I'd hope would be able to short circuit the meat of the checking if referentially equal anyway.

Basically, I'd be interested to hear more about the decision to use referential equality with lazy too :)

OvermindDL1

unread,
Sep 13, 2016, 10:34:26 AM9/13/16
to Elm Discuss
Lazy is purely and entirely for speed reason, the referential equality is for that purpose. If it used equality then imagine suddenly comparing a huge model that you passed in, the speed benefits are entirely gone at that point.  It is very much a corner case that should not be used except in very specific situations.  :-)

Mark Hamburg

unread,
Sep 13, 2016, 12:54:54 PM9/13/16
to elm-d...@googlegroups.com
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.


--
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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Yosuke Torii

unread,
Sep 14, 2016, 2:33:22 PM9/14/16
to elm-d...@googlegroups.com
Thanks for your responses.

If it used equality then imagine suddenly comparing a huge model that you passed in, the speed benefits are entirely gone at that point. 

I'm not sure if huge model always requires huge cost. If `{ largeModel = largeModel }` is passed and two largeModel are reference equal, it is cheap cost to compare them.

  view model = lazy someViewHelper model.data
  someViewHelper data = someView { data = data }

Yeah, this is good for workaround, but it needs interface to be changed. I often pass large-grained models to let views choose what parameters to use by their responsibility, but this way lazy never works. So breaking into small models are possible but I wonder it is good for maintainability.



--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/AFnzfJtzfRA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss+unsubscribe@googlegroups.com.

Frank Bonetti

unread,
Oct 23, 2016, 9:14:14 PM10/23/16
to Elm Discuss
I'd also like to hear way it was designed this way. I assumed that `lazy` would compare the argument values, not references. It appears to use `===` under the hood.

In its current state, lazy is pretty much unusable unless the given argument is a String, Int, Float, Bool (in other words, it must be a Javascript primitive). Records, Lists, Arrays, and Tuples will NOT pass the equality test even the value hasn't changed, since `===` on objects and arrays in Javascript always returns false unless the reference is the same. For example, { model | data = model.data } results in a new `model` reference, even though the value is exactly the same.

I understand that a deep equality check could potentially be very expensive, but it would be worth it if I could avoid having to diff and render thousands of elements. Perhaps we could have different lazy functions? One that checks on reference and one that checks of value equality?

In case anyone is wondering, I worked around this issue by removing the tuple argument from my view function and replacing it with regular arguments:

renderCell : ( Int, Int ) -> String -> Html Msg

changed to:

renderCell : Int -> Int -> String -> Html Msg


Reply all
Reply to author
Forward
0 new messages