It does seem to me like an orthogonal feature that can be added later, what we probably need right now is strong motivation.
Would you be more comfortable with "var wrapper"?
True, I agree with you. The point I was trying to make that we should explore and design a potential generalized feature for multiple projections first (even if we‘re not going to implement them now) before we can reduce it to a single anonymous projection. That said, having thought about this idea for a day now I don‘t want to go back to the prefix form as I think it would be a wrong decision that won‘t pay off and lock down for future extension.
I did say this earlier:
So perhaps we stand on different sides on how similar/different single and multiple projections should look, and by extension, whether or not it could be added independently later on.
Dear core team and proposal authors, does anyone of you considered extending the idea of property wrappers beyond computed properties?
Why shouldn‘t we have wrapped subscripts with synthetized accessors in the future? In that sense Property wrapper
would be a wrong name.
@MyBehaviorWrapper subscript (index: Int) -> Element
How many of us have written subscripts with COW optimization? Why not allowing to wrap the behavior into a type as well?
Hmm. I think it's accurate enough, since there is going to be a var
at the heart of the implementation, regardless of whether it's a property or a variable, but it seems a bit semantically … empty.
Given that the @<whatever>Wrapper
type is forced to have a wrappedValue
member, why not call them value wrappers? A value wrapper then wraps its wrapped value, which seems like a good thing to have as a truth.
Hmm, wrapping functions seems rather tricky (oh my, now I see why subscript uses ->
) when most of the time you need to access the parent variables, which current proposal doesn’t seem to support.
It would be a future direction as it would require a few alternative rules, but in general a subscript has similar accessors as a computed property, and the current proposal mentions that it wants to solve the issue with accessing self
in the future. That‘s why I think we can go beyond describing behaviors for properties and create behaviors for subscripts as well (not methods).
PS: if would accept that as a future direction then the story of projections adds another view angle because as I mentioned above it would allow us to express new things in Swift (it‘s already almost like a small DSL).
And I also would like to suggest another name for the proposed feature.
- Behavior Wrapper
We do indeed wrap a certain behavior into a type and it would still apply to subscripts if it was a future direction on this proposal.
I think that @varWrapper
is the technically correct name for this. As others have mentioned, is possible to introduce a @subscriptWrapper
(which would work differently, and therefore should be a different thing) and it isn't unreasonable to expect @funcWrapper
(analogous to python decorators?) etc.
Value wrapper isn't specific enough because functions and subscripted things are also values.
-Chris
Could be:
@wrapper(var)
@wrapper(subscript)
@wrapper(func)
And then if you really want to go crazy:
@wrapper(for) // @Parallel for x in values { ... }
Given all of the churn on the name of this feature I'd like to bump my original post proposing that we name this @valueWrapper
.
A handful of others have voiced similar feelings:
Of course, given what @Chris_Lattner3 has said...
... this would get shot down pretty quickly, but only if we're serious about expanding "wrappers" beyond var
values to functions and subscripts. I'm bumping this because that idea has only just recently been introduced.
If this does get shot down for that reason, I'd be in favor of @varWrapper
and mildly in favor of @michelf's proposed spelling: @wrapper(var)
as a runner-up.
But what exactly is the advantage of being able to project a bunch of properties like that? The end user only saves one character, and logic at the use site ia very similar.
Sure it’s cool but it seems to provide very little beyond what projecting a wrapper value and/or the storage. Maybe I’m missing something?
Strong +1 to this syntax.
I would love to see function decorators on Swift!
Sorry to bring up access control again, but I feel allowing property wrappers to dictate a consuming type’s access level via wrapperType
to be a huge misstep. I can’t think of any other language feature in any language that allows a library author (in this case of a property wrapper) to have such a big impact on the public API of consuming types.
In lieu of custom access control for property wrapper’s storage/wrapperType
(e.g. @Wrapper public($foo) public var foo: Int
, the only safe option, to me, is to make everything private to the type using it. If you feel that the wrapper itself is so intrinsic to its use as to make it accessible at the same level of the property then, to be blunt, this proposal isn’t for you (yet).
The access rules here are really simple: The property is as declared, everything that supports the property is private to the type the property is declared.
Are there any existing use cases of property wrappers with wrapperType
that require the second revision's suggestion of internal
access? I found that odd (but kind of understandable if there's some existing uses inside Apple that need it). I find the suggestion to make them the same access level as the property itself dangerous.
As a smallish example consider:
class Widget {
@ResetableLazy public var foo: Int = …
@ResetableLazy public var bar: Int = …
public func reset() {
$foo.reset()
$bar.reset()
}
}
@ResetableLazy
is a property wrapper from a library the author of Widget
has imported. It's implemented without wrapperType
.
The type Widget
has a nice, clear public API: there's access to two properties: foo
, bar
, and function reset
which allows both the properties to be reset at once. Users of Widget
do not know how foo
and bar
are implemented, just that they're Int
s and instances of Widget
can be "reset" using reset
function.
Now, if the author of @ResetableLazy
decides that exposing ResetableLazy
is no longer desirable and they want to expose its functionality using wrapperType
instead, the author of Widget
has a problem.
Upon updating to new version of @ResetableLazy
, Widget
now has a confusing public API: as well as the original API it now has $foo
and $bar
. Dangerously, it allows users of Widget
to reset foo
and bar
separately, something the initial author didn't expect, and could lead to unexpected behaviour if foo
and bar
should be reset together.
Sure, you can "fix" this in the Widget
type by making foo
and bar
private
and exposing them publicly as properties that delegate to the private one (you could even write a @Private
property wrapper!), but as a conscientious API designer this means you now have to do this for all properties being wrapped by property wrappers you don't control just in case this happens to them as well.
To address @tanner0101's example more directly
I would expect the public API for this type to have name
and id
(and whatever's on Model
). Anything else is confusing. Why should I need to know the implementation of @Field
to know that @Field
is adding additional properties to my API? If I know it's a property wrapper, why should I care if it's implemented using wrapperType
or not? (I appreciate you'd rather everything were the same access level as the property so the wrapperType
vs. not distinction wouldn't matter).
This seems to be approached as the author of @Field
, and seems to be suggesting that every user of a property backed by @Field
must need to know that fact. To me this is not the spirit of property wrappers which is to provide a single property that hides an arbitrarily complex implementation to provide that property. Instead, here you seem to want a way to provide two properties for the price of one, which property wrappers kinda do, but only as an implementation detail.
I completely agree, and they absolutely can: on the type that uses them. If that type wishes to expose the underlying functionality of property wrapper they can add their own functions and/or properties that do so safely – as my example does. I don't see any examples on the proposal that require the wrapper to be as visible the property itself, and many that could leak privileged implementation details if they were.
I see the argument here, but as I said in my opening paragraph I can't think of any feature in any other language that allows the author of a type to have this much control over not just its consumers, but consumers of its consumers. Further, it's being conflated with a feature that a property author could quite reasonable want to use (wrapperType
) without also wanting to opt in to the access change.
If you'll allow me to be somewhat melodramatic, allowing wrapperType
to have this effect under the argument the API it provides is meaningful, or well-documented doesn't seem too far removed from allowing types to opt-out of access levels entirely. A type author could argue that their type should always be accessible externally to types that contain it for similar reasons being floated here, but this has never been their choice to make.
I have been staying out of this discussion because the design has been in constant flux and it did not seem ready for review. Now that the shape of the feature seems to be settling down a bit, there is a lot of material to read through.
I strongly agree with @jcrang (immediately above) that the author of a wrapper type should emphatically never be able to dictate the API of a type containing a property which uses that wrapper.
• • •
This proposal is called “Property Wrappers”, however it does not include the ability to, *ahem*, wrap a property. It does not do what it says on the tin. Instead, it requires that the wrapped property (aka. backing storage) be synthesized by the compiler.
Now, synthesis of the backing storage is great, and often quite convenient, however at other times it is undesirable.
Based on the vast body of pre-existing precedent, I find it abundantly obvious that the backing storage should be spelled with a leading underscore. Thus the backing for “var foo
” is “var _foo
” (ie. access to “foo
” is redirected to “_foo.value
”).
Furthermore, the access level for the synthesized backing storage should always be private.
On the other hand, if a programmer wants the backing storage to have a different access level than private
, they should be able to declare the backing storage themself. To receive the synthesized access redirection, the backing storage must still be named “_foo
”.
For example:
struct Bar {
@MyWrapper
public var foo: Int = 2
}
is equivalent to:
struct Bar {
private var _foo: MyWrapper<Int>
@MyWrapper
public var foo: Int = 2
}
And if instead the access level of _foo
were public
, it would still work and the backing storage would be visible publicly.
• • •
The whole todo about dollar signs and projections strikes me as the wrong direction entirely.
So far, the only compelling use-case I have seen is in SwiftUI, for establishing a Binding
. However, it seems to me that what a Binding
really needs, is a mutable reference to a property.
Moreover, a mutable reference to a property is a generally useful thing in a programming language. So, if we need them for SwiftUI, they are also generally useful, then we should introduce a way to spell them.
Thus, if we want the syntax $foo
to work for establishing a Binding
, then I would greatly prefer that the way it works is by having $foo
represent a mutable reference to foo
, which is then wrapped in a Binding
by whichever SwiftUI API it is passed off to.
That makes the whole story about wrapperValue
even worse and more restrictive. If $foo
means a mutable reference to foo
then you have to expose the wrapper type for the backing storage that is in use or otherwise how would the compiler know what to do? Not only that, who says that wrapperValue
or even wrappedValue
should be mutable? It is maybe a thing for Bindable
but it's not a general concept.
I am saying that “wrapperValue
” should not exist as a concept. There should just be the property itself, and its backing storage.
If the programmer wants more views and projections, they can write them manually just like anywhere else.
You can already make mutable reference to a variable or property, you just have to jump through hoops capturing it in two closures, one the “getter” and one the “setter”.
If the main property (eg. foo
) is an Int
, then $foo
is a mutable reference to an Int
. No other type needs to be visible.
It would be a compile-time error to create a mutable reference to something immutable.
I strongly disagree. Mutable references are definitely a general concept, not specific to any one library.
Are you suggesting that the user should write $foo
himself and route it to the _foo.wrapperValue
? I'm not sure I understood what you said.
I am saying that “$foo
” should only and always mean “a mutable reference to foo
”.