About `full value classes`, they have no identity....
# compose
u
About
full value classes
, they have no identity.. doesnt that break strong skipping?
m
They don't have stable identity (meaning the same value object might be "materialized" as two different boxes), but they allow
===
operation. How do you see this breaking strong skipping?
u
I dont know details super well but from what I know compose compiler deals with stability, which they cannot determine if a type is from a different module, and strong skip mode works around that by doing identity check first, which effectively works on unstable types as well (i.e. makes them behave as stable unchanged instance-ish in practise) but you probably know that so I was just wondering whats gonna happen, since identity will be gone from value classes, and say theyre from different module, means it will run strutural checks all the time right? which are more expensive than identity checks?
m
That's a cool question! There are two interconnected, but separate things you're asking here at the same time. First, by the time the behavior of
===
is changed to be structural for value types, both we and Compose will update so that the performance in general doesn't suffer. And that is relevant not only to strong skipping, but in general to the expectations about
===
operation. Second, before that happens, with unstable identity for value types you would sometimes get different boxes for effectively the same data, which means strong skipping would not skip when it actually could. But for that to happen, the value type should also be unstable (as stable types are still compared via
==
), and many of the value types will be stable. So it does not really break strong skipping, but it makes it miss some optimization opportunities. Side-note: IIRC, if a module is compiled with Compose, the stability information for its types is preserved and used in dependent modules. So if smth is from a different module, it is not automatically unstable.
u
1) how can it be optimized if theres seemingly no options? or will identity still be there for language internals to use? 2) that would be news to me if stability can be propagated across modules, ill look into that
okay so you’re right, but most models are data classes and they do identity check first in their equals..but okay small price to pay (I generally like to keep all of state in a single State type, where changing one field emits new state. but then unrelated properties are sams instance so cheapest no-op for compose) but i do worry about the unstable value types, as most models, atleast in my project, don’t live in ui modules where the compose compiler is applied, sure there is ui level model, but theres lots of newtypes, which dont and are properties to the ui models, and its nit reasonable to copy everything so if there is no identity for those, then they’re always recomposed, so back to before strong skipping, and anecdotally strong skipping helped perf noticably (now im reading compose compiler gradle plugin, doesnt reuire android gradle plugin, which is extra slow compared to jvm one, which is why i avoid it at all cost … news)
but as I think it through..stability of compose is mostly a workaround for lack of real value types, so I would assume they can just not apply current rules at all on value classes, regardless of project
s
Ideally, Compose would treat the value class as a bag of properties and check each field independently. This is what we do for current inline classes, it only checks the backing property value. I am not sure if it is the case for multifield classes right now though, in the commits I reviewed it behaved as a regular class. Another issue is that destructuring class into the fields will require holding each of those fields independently in the slot table and a lot of codegen, which is copied everywhere the class is used. Note that treating value classes as blanket stable is not great either, we might end up comparing huge lists on each recomposition, something that strong skipping intentionally avoided. Btw we just have talked about stability and how it can cause problems just this week - see

this talk▾

.
u
> Note that treating value classes as blanket stable is not great either, we might end up comparing huge lists on each recomposition yea... same goes for non-compose usage when comparing
data class
(to be
value class
) holding such list by say
MutableStateFlow
which does that implicitly on update
but I guess thats the way it is with structs/value types in general? unless some SIMD magic happens
s
Well we are trying to avoid that whenever possible. In a way, life would be simpler if value classes reference comparison was just comparing every field by reference, but it can only happen on the vm level
u
hmm.. so identity check is done for stable types as well? i was under impression that (with strong skipping) its just stable = always just structural equals unstable = always just identity check
s
yeah, that state of the world is not ideal either, but better than just inferring stability for the whole instance I guess it depends on what you are optimizing for
u
just to clarify, value class world then can only become stable = always just structural equals unstable = always recomposed right?
s
it depends, if you preserve the instance, it won't recompose even when unstable
like if it is a list of value classes etc
u
wdym preserve the instance? oh you mean of non value classes like lists?
s
Anyways, for now we are going to treat value classes as regular classes. There are cool things we can do in some cases, like destructuring in memoization etc, but I want to see how people are using them first to make sure we are addressing a real problem
🤝 1
u
im confused on “we are oing to treat value classes as regular classes” how can that be the case if identity wont be available?
s
Well from the vm perspective they are a regular class really
u
so the scalarization and all that jazz isn not happening?
i know its just best effort, but i was under impression nothing from user code can access the instance, even compiler plugins (?)
s
I am not sure what you mean? You can pass it as a parameter and put it into variable
u
well i naively imagine strong skipping as
===
check, and that wont compile
i meant the instance as in identity, not values on it, sry
s
it is something like
Copy code
fun changedInstance(val: Any): Boolean =
  rememberedValue() === val
so from the runtime perspective it is the same as any other any
u
hm okay so the only thing we are then missing is the implicit identity check shortcut (present in datacclasses) in value class structural equals? or is that still there as well
s
I assume it is, didn't check the codegen really
u
i see thank you
but maybe a step back to the > it is something like > > >
Copy code
fun changedInstance(val: Any): Boolean =
>   rememberedValue() === val
>
> > so from the runtime perspective it is the same as any other any doesn’t this then force the identity existence and therefore prohibiting scalarization etc? sort of like primitives boxing?
s
it does, yes we do specialize for primitives, and that is why I would prefer if we unbox each property individually
u
okay i see now it clicked thanks
are we sure value classes sill be net positive? 😁 i mostly want just the ergonomics
s
I think yes, given Valhalla etc, but it is going to take some time
u
plus all the work youll do doesn’t apply to stateflows etc
s
right now it is practically a data class minus some things (that I didn't like anyways), so it feels like net positive maybe native can also optimize it into structs sometimes idk
u
minus api things sure but if identity shortcut is gone from structural equals, then
Copy code
data class State(val foo: Foo, val bar: Bar)

mutableStateFlow.update { it.foo = foo2 }
will run deep eq check on Bar as well, which it currently doesnt (identity shortcut)