I am a huge fan of the idea of property delegates. I also think this proposal is headed in the right direction. I also think the choice of making delegates a user-defined attribute is the right approach.
So I wish I didn't have to say this, but unfortunately it does not feel fully baked to me yet. I agree with previous reviewers who have suggested that we should continue refining the design before we accept this proposal.
I think it's important to keep in mind when evaluating this proposal that it is basically syntactic sugar with relatively small constant upper bound in code savings.
Without this proposal we can (and many of us do) write storage wrappers. When we do this we can choose to write .value
or to write a forwarding property. Explicitly writing out the backing storage and a forwarding property is not fun but it is also not that bad. We should provide sugar to automate this pattern but we should also have high confidence that we are committing to the right solution.
Since it is possible to write storage wrappers manually I am surprised that the proposal makes no accommodation for a frequently voiced concern that backing storage is typically an implementation detail that is private
. A syntactic sugar proposal like this one should acknowledge and support widespread use cases where the pattern is written explicitly.
Further, the most recent draft of the proposal introduced a new notion of delegateValue
. I think this feature is very interesting, but it also feels like something that needs more motivation and design. It isn't clear to me why the designs of the examples provided that use delegateValue
are superior to designs that don't require an additional layer of magic.
Why doesn't CopyOnWrite
just rename delegateValue to
valueand use private backing storage? Why should its users need to say
$foo` in order to write? What is the perceived advantage of this design?
Why doesn't Box
just expose a ref
property? What's wrong with having to say $foo.ref
? Or why not go further and collapse the two into a single Ref
delegate. What advantage is there for users to have to think about both boxes and refs when this distinction isn't strictly necessary?
Overall, I think this feature needs to be motivated by examples whose design is clearly superior to the alternatives.
On a design front, delegateValue
breaks out-of-line initialization. I think this is a problem and may indicate that we could find a better design for the feature itself and / or the delegates that might consider using it.
The most interesting thing to me about delegateValue
is that it allows you to completely hide the delegate itself from user code when there is no useful interaction available for users. However, this requires using a Void
delegate value. Having $foo
of type Void
isn't really desirable. And it is especially not worth breaking encapsulation to get it.
If there is no meaningful API for users of a delegate other than value
there is no reason to have $foo
available at all. One direction I suggested we might consider is not even synthesizing a $foo
backing storage if the delegate does not implement delegateValue
. This would require delegates to opt-in to having a visible backing storage. The flaw with this design, as with the proposed design of delegateValue
, is that it breaks out-of-line initialization.
One interesting idea that came up late in the discussion and didn't receive much attention is to not expose the backing storage via $foo
at all. Instead, relevant API would be available directly on the delegating property with `$-prefixed identifiers. This direction should be explored further. It would require further refinement but may be a better user-facing presentation of the delegate's behavior. One reason we should consider this direction is that autocomplete could support it, exposing the delegate's API in the place programmers would prefer to see it.
If the delegate's API is visible on the primary property there would be no reason to expose the backing storage to programmers at all. Again, this breaks out-of-line initialization. I think if we're going to consider any of the designs that hide the backing storage (including delegateValue
as proposed) I think we need to look harder for a way to still support out-of-line initialization of the backing storage. I believe the compiler is smart enough to be able to distinguish initialization from mutating assignment. Perhaps we should consider syntax that is only available for initialization and not otherwise available for reads and writes.
Beyond the topics above, I think there have been some interesting new suggestions in the review thread. Among others, @beccadax has a few that I think are worthy of discussion.
So while I very much want to see property delegates become a part of Swift and I do not want to see this topic deferred for another 3 years, I do think we should send it back for another round of design discussion. I hope we will be able to do that without letting this iteration die completely.