One Off the Slack: Why Are Stateful APIs Discouraged?

Alexander Maryanovsky asked:

There seems to be a general problem with Compose in that a lot of times it exposes state as a regular type, and its [statefulness] is implicit. For example, compose-desktop has WindowState.position of type WindowPosition and WindowStateImpl implements it as

   override var position by mutableStateOf(position)

but from just looking at the WindowState interface, there’s no way for you to know that position can be observed.

Wouldn’t it better if the type of WindowState.position was MutableState<WindowPosition> ?

What ensued from that was a 67-post Slack thread between Alexander and Google’s Adam Powell. Adam responded:

No, in fact I still need to update the API guidelines doc to discourage both parameters of public functions and public properties of type [Mutable]State<T>

Snapshot invalidation is about what was read or written, not about a container type exposed

The state types are implementation details, not required API surface, and hiding them from the API surface means a single property can access several snapshot state records and callers can invalidate accordingly when any of those records change. The caller doesn’t have to be aware of the details.

Alexander countered:

Ok, but the fact that a value is observable is not an implementation detail. If WindowState.position was implemented as a regular property, things would break.

Maybe at least mark the property with an annotation, and have the compose plugin enforce its implementation.

Adam replied:

That it snapshot invalidates is an API contract, as for annotations, one exists already: @Stable

or perhaps more precisely, snapshots are runtime-only and based in API contracts. Lower level than @Stable or the compose-compiler plugin. Composition cares about @Stable, and if any stable type can visibly change, it must invalidate via the snapshot mechanism when it does.

Alexander wasn’t sure if Adam understood the concern, and Adam continued:

Yes, I see the problem you’re talking about. I think it’s a concern born of familiarity with Flow and Rx, which express observability through stream types. It’s a different system and it’s easy to carry over assumptions that will hold back your designs in the long run.

I do think it’s valid for public API types like WindowState to call out in their documentation if their properties are snapshot-observable, but with compose it’s more of a baseline assumption.

but in terms of static analysis I don’t think building such a system is desirable, for the same reasons checked exceptions have been dropped by many languages since java. Snapshots invalidate even through agnostic infrastructure code.

and this is a feature

Alexander was unconvinced about the comparison:

That’s very much possible; I may develop different intuition for Compose over time. But where I’m standing now, I think trying to express as much of the behavior of an API as possible at compile-time is something to strive for. Don’t you agree? I mean that’s why I like static typed languages in the first place.

Checked exceptions are a bit of an outlier. They’re great in theory, but they’re just too cumbersome in practice.

Adam agreed:

Yes, exactly. Snapshots assert that the same is true of explicit state invalidation.

Alexander remained unconvinced:

I’m all for the convenience of automatic subscription/invalidation. I just want a way to know whether a property is something I can (auto) subscribe to.

Are there no solutions to this that aren’t cumbersome?

Adam’s answer was:

assume that anything that can change out from under you will snapshot invalidate

Part of the problem here is that Adam believes that “snapshot invalidation” is a commonly-understood term. I suspect that the number of developers who understand what that means can fit comfortably in a small auditorium. The likelihood that any development team has one of those people is very low.

The overall Slack thread is epic, and continues into discussions of:

Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!