I wrote an article explaining how Html.Lazy
works under the hood, with some exploration on other potential useful variants.
I’m very glad you’re also thinking of alternative designs!
My experience has clearly shown me a need for a best of both worlds solution. One in which I wanna declare exactly how each of the arguments should be cache-busted:
lazy2 : ((a -> b -> Html msg) -> (a, CacheOption) -> (b, CacheOption) -> Html msg
type CacheOption = Equality | Reference
I wish this could’ve been done using a record as an input, for flexible argument count, as in your previous post, but I’d be ecstatic even if something like this came into fruition.
I think this is possible and probably even variants like “Equality for N levels then Reference check”.
But the problem is likely going to be the API surface, because we’re multiplying number of arguments × equality method (and maybe even more if we decide to give different cache options for every argument).
One downside of multiplying different methods is the intuitiveness, in the sense “which option should I go for? Do I need reference or equality check? Is deep equality going to be too slow? How do I figure that out?”. With only one option there’s no choice. And then it’s up to you to figure out how to make it work for the current lazy approach. Not ideal but at least somewhat simpler.
I think a reasonable solution to combine and fast equality checks and deep equality checks is to take a record like in the lazyShallow
proposal, but instead of one, we’d take 2: one for the fast equality check and one for the deep equality check. That would be pretty flexible in practice.
The second argument would actually not even have to be a record I think?), though I think that that would technically allow ==
between different types (if there’s an unbound parameter like view : { some : record } -> unbound -> Html msg
), so we’d need to make sure that’s not a problem in practice.
Oh yeah, I’m totally onboard with two records, or a record and a type. I agree that it can be a whatever type which does a structural comparison ==
. The custom type I proposed allows for the flexibility you noticed, as in providing the depth of the equality check, however, your two inputs suggestion solves 100% of my problems. The ability to choose equality check depth sounds fancy, but I never really needed such thing.
A feature that I was thinking of, at one point, was custom cache busting behavior. It would be introduced as an additional variant of CacheOption
: Custom (a -> a -> Bool)
. This originated from data structures which hold functions internally.
What exactly are you concerned about, here?
Also, I looked into implementing your two-record solution, but I’m unable to see a way to it without introducing another case in the vdom switch. What do you think?
If our new function is something like this:
superLazy : (record -> a -> Html msg) -> record -> a -> Html msg
where record
is a record (could use { record | lazyDummy : () }
) where every field is shallowly equalled through JS’ ===
(like described in Beyond Html.Lazy’s argument limit), and where a
is equalled by Elm’s ==
.
In that case, I’m worried about something like this:
if condition then
-- Note here the last arg is a number
Html.Lazy.superLazy subView { some = field } 123
else
-- Note here the last arg is a string
Html.Lazy.superLazy subView { some = field } "string"
subView { some } _ =
Html.div [] [ ... ]
This would type check and be totally valid.
However, if condition
ever switches value, then at some point under the hood we will end up doing 123 == "string"
. Since that should never be legal in Elm code, I’m not sure the function Elm (_Utils_eq
) uses for equality properly handles the case where values are of different types.
Well, I just did some testing, and it doesn’t crash, but it’s also not correct:
_Utils_eq(123, "string")
// false, this is good
_Utils_eq({}, null) // null is () in Elm
// false, this is good
_Utils_eq("string", {})
// false, this is good
_Utils_eq({}, "string")
// true, this is NOT good
That is because at some point we look at the fields of the values, but assuming that both elements have the same fields, which will always be the case for current Elm code. Well, not with custom type constructors, but then the first field (the “tag”) will have a different value, so it will result in false
. Here is the code for that:
for (var key in x)
{
if (!_Utils_eqHelp(x[key], y[key], depth + 1, stack))
{
return false;
}
}
So yeah, you can have false positives (things are considered equal when they shouldn’t). In practice, it’s probably okay since I believe the value has to be unused for it to type-check (or another argument has to be different, in which case it’s not a problem either), so… yeah maybe it’s fine in practice.
Awesome, let’s proceed with the meat and potatoes:
case 55:
var xRefs = x.l;
var yRefs = y.l;
var i = xRefs.length;
var same = i === yRefs.length;
while (same && i--)
{
same = xRefs[i] === yRefs[i];
}
same = same && _Utils_eq(x.equalityRecord, y.equalityRecord);
if (same)
{
y.k = x.k;
return;
}
y.k = y.m();
var subPatches = [];
_VirtualDom_diffHelp(x.k, y.k, subPatches, 0);
subPatches.length > 0 && _VirtualDom_pushPatch(patches, 1, index, subPatches);
return;
This is the only line I added:
same = same && _Utils_eq(x.equalityRecord, y.equalityRecord);
For this to work, of course, we need:
var $author$project$ElmFramework$Lib$HtmlExtra$lazyFancy = F2(function(func, record1, record2)
{
var args = Object.entries(record1)
.sort(function([key1], [key2]) { return key1 < key2; })
.map(function([key, value]) { return value; });
return _VirtualDom_lazy_fancy(func, args, record2, function() {
return func(record1, record2);
});
});
and:
function _VirtualDom_lazy_fancy(func, record1Args, record2, thunk)
{
return {
$: 55,
l: [func].concat(record1Args),
equalityRecord: record2,
m: thunk,
k: undefined
};
}
Haven’t tested this yet, what do you think?
I think you started from an old version of lazyShallow
where I sorted keys. From feedback it seems like that’s not necessary. I have simplified it to a simple loop. That way you could also pass the func and record1Args in one argument to _VirtualDom_lazy_fancy
.
Otherwise I don’t see anything problematic with this. There could be an issue with monomorphism since you add a new field only in certain places, but I doubt that this is monomorphized already.
I’d say this should work, unless the equality check causes additional unforeseen consequences.
Try it in both development and optimized mode before sending it in real production
Great! Will try in around a week. I have some serious refactoring to do to prepare.
This thing has been in my backlog for centuries. Thank you!
This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.