This is a very interesting point independent of the discussion about the best syntax, perhaps important enough to be worth fleshing out before even delving into syntax.
In Swift, tuple elements, enum cases, and properties (whether computed or stored) all share the same
. operator to access them on a value. However, they carry very different semantics:
This can be seen in the case of enums, where you can add
staticproperties that can be accessed with the same syntax as though a case, but they absolutely aren't cases, as revealed readily when it comes to exhaustive switching.
In the same vein, when tuples conform to
Hashable(the proposal is accepted but not yet implemented), you'll be able to access their
hashValue, but that absolutely won't be a tuple element (unless you happen to label an element
hashValueshadowing the protocol implementation). And with variadic generics (type sequences) on the horizon, we may be even closer to conforming tuples to arbitrary protocols.
In both of these cases, there is a clear notion of the "structure" part of "destructuring," or to use terminology more commonly used in Swift, what constitutes a tuple's or an enum's value.
We already have similar notions for structures, but it doesn't hinge on whether something is a stored property or not. Indeed, it is actively desirable for the author of a type to be able to switch a property from stored to computed without breaking user code. Rather, we encounter this issue in several places; given a value
x of type
T with value semantics:
- What arguments do I need to pass to an initializer
T.init(...)in order to construct another value that's interchangeable with
- What are the 'salient' properties of
xthat are compared with my newly constructed value to determine equivalence? Relatedly, what are the properties of
xthat are fed into the hasher for
- What are the keys of
xthat need to be encoded and decoded for the purposes of
Codableconformance in order to roundtrip?
- If the type is
LosslessStringConvertible, what properties of
xare required to come up with the converted string?
I think there may be a role here for a
Value protocol that formalizes and makes consistent the answer to these questions for any given value type. For tuples and enums, there will be one and only one way to conform to
Value and it can be synthesized. But for structs, we already know (from existing conformances to
LosslessStringConvertible) that the answer to these questions will not correspond to stored versus computed properties but depends on the semantics of the particular type. Indeed, for a given type, there may be stored properties (for example,
private ones) that don't participate in some or any of these conformances and computed properties that do.
Now, returning to this pitch, I think we have a distinction to make here: Is the goal here to have a shorthand to access any property, or is it to destructure a value?
If one wishes to destructure a value, then the syntax should very much care that you get all the properties that are part of the "structure" of the value and none that aren't. We would want to work through what that means and whether it merits formalizing in some way with a protocol to which all destructurable values (including tuples and enums by default) should conform.
By contrast, if one wishes to have a shorthand to access any property (and this would include, for example, computed properties such as
hashValue on tuples that conform to
Hashable), then the syntax should very much not enforce any such thing. But these are, to my mind, two entirely separate--and in some ways incompatible--features.