Trying to think way outside the box.
I still think if the focus of this proposal is to operate over an extracted set of stored properties, this new attribute should also reflect only that specific sub-case with its own name and not burn the generic typeWrapper
term. That said, storageWrapper
would fit way better here.
Let's get a bit abstract in here. I'll reuse the above example I used where are @typeWrapper
(not the @storageWrapper
, but the true generic version as mentioned above) could project a similar named $
-sign prefixed type.
Also let's put the concrete implementation details aside. I'm sure we would eventually be able to figure out the HOW if we only wanted, but that's not super important just for the sake of this little demonstration. The whole purpose of this is just to visually present one or two examples on how interesting true type wrappers could become.
@ValueSemanticsObject // ──┐
class Foo<T> // └───────── wrapped type ─────────┐
struct $Foo<T> // ◀─ projected value semantics type ─┘
// just for the sake of demonstration, let's assume
// a type wrapper would use a template type which the
// compiler could use to construct a brand new `$` type
// or simply built a type alias with it
@typeWrapper
struct ValueSemanticsObject<WrappedObject: AnyObject> {
struct Template
var _wrappedObject: WrappedObject
subscript<T>(
dynamicMember keyPath: ReferenceWritableKeyPath<Value, T>
) -> T { /* perform CoW here */ }
// add something to like `dynamicMethod` to project methods
// and not only properties
}
}
// the compiler could potentially synthesize something like this?
typealias $Foo<T> = ValueSemanticsObject<Foo<T>>.Template
// or if we ever get `newtype`
newtype $Foo<T> = ValueSemanticsObject<Foo<T>>.Template
Such a Template
shouldn't be limited to a struct
only, it could be anything. Of course we would have to figure out how to define a fairly good set of understandable and predictable rules to pull this off, but truly wrapping a whole type could do so much more besides pulling the stored properties into a new private storage type.
For the next small discussion, let's disable the automatic $Storage
generation for the @storageWrapper
and see what this would mean.
At first glance we would lose almost all benefits that this pitch provides in regards to automatic synthetization. Before I attempt to try elaboration on how we could fix that, let's see what we actually gained from this.
The strictness of the pitched rules is removed. A @storageWrapper
type does not need to have a generic type parameter to directly reference the storage type as it can now be done manually.
@storageWrapper
struct MyClassStorageWrapper {
var storage: MyClass.MyManualStorage
// details excluded for simplicity
}
@MyClassStorageWrapper
class MyClass {
struct MyManualStorage { ... }
}
As several people already mentioned above the pitched $Storage
type is fairly limited. With this change however we could restore the manual control over the concrete storage type and we can technically do whatever we want that type.
I previously mentioned that the automatic connection between the stored properties and the storage is lost. How can we fix this without reintroducing all the synthesized boiler plate code?
Let me answer that question with a counter question: What if we would first introduce a property wrapper for exactly that purpose?
class MyClass {
var wrapper: MyClassStorageWrapper
@Link(\.wrapper.storage.value)
var value: String
@Link(\.value.count)
var count: Int
// in the future?
@Link(\.value.contains(_:))
var contains: (String.Element) -> Bool
}
If such a property wrapper existed, it would allow us to re-link the lost connection. Sure it requires a bit of manual work, but even that seems like a win, as such a wrapper can not only link against the one storage type as was previously pitched, but it can link against anything that is reachable via a key-path, maybe even methods in the future.
At this point I think it becomes a bit more clear that the @storageWrapper
that does all that automatically for us is not really brining that much to the table. Don't get me wrong, I'm not trying to say it's not useful, but I think that an alternative solution to that particular problem seems a bit more promising as it also opens up other avenues.
However this would require us to explore further the ability for property wrappers to access the enclosing type, optimization of property wrappers where they could be just 'computed property wrappers' to be also allowed in extensions, etc. etc.
I hope that feedback will not derail the topic and still somehow help the author(s) and the community steering the evolution of these features towards the right direction.