Sorry I really don't want to get on your nerves or something, but are you saying that this will be the final state of access control forever you will drop the future direction part of access control from the current proposal?
I'm fine with leaving it as a possible future direction.
Doug
+1
The above access control you mentioned can be circumvented to expose the backing storage through some composition which is quite cool.
@propertyWrapper
struct Exposed<Value> {
var wrappedValue: Value
var wrapperValue: Value {
get { wrappedValue }
set { wrappedValue = newValue }
}
}
@Exposed @Lazy public var property: Value
// translates to:
private _property: Exposed<Lazy<Value>> = ...
public var $property: Lazy<Value> {
...
}
public var property: Value {
...
}
I think we found the golden middle here.
I'm not sure if this has been brought up, but I think that the name "property wrapper" should be made more general and more accurate, because these wrappers do not only apply to properties, but also local/global variables. I believe this feature should be called variable wrappers.
1. Local and global variables are not properties, but "property wrappers" are designed to apply to them.
According to the official language reference, var
declarations in a global or local scope are not called "properties"āthey are called "variables".
Global and Local Variables
The capabilities described above for computing and observing properties are also available to global variables and local variables . Global variables are variables that are defined outside of any function, method, closure, or type context. Local variables are variables that are defined within a function, method, or closure context.
The following line in the proposal can be revised to
-Property wrappers can be applied to properties at global, local, or type scope.
+Variable wrappers can be applied to variables at global, local, or type scope
+ (in which case, variables are properties).
2. "Variables" generalizes both local/global variables and var
properties.
let
constant properties are also properties, but "property wrappers" do not apply to let
properties because they cannot have a getter. The word "property" may cause confusion to the user because one may assume that "property wrappers" apply to all properties.
The word "variable", on the other hand, accurately describes both local/global variables and var
properties. The official language reference even uses the word "variable" to define a var
property:
Stored Properties
In its simplest form, a stored property is a constant or variable that is stored as part of an instance of a particular class or structure. Stored properties can be either variable stored properties (introduced by the
var
keyword) or constant stored properties (introduced by thelet
keyword).
As such, I believe the word "variable wrapper" is a much more accurate name than "property wrapper", and will not cause the wrong implication that these wrappers can apply to let
constants.
Speaking of which, I don't know the status quo, but could we have var
/let
designation to apply to base storage? This way we could declare properties (variable?) that can't use mutating
functions/accessors.
I should have included a non-private
initializer for Foo
. The example wasnāt intended to prevent Bar
from initializing foo
, only to prevent access to x
.
Sure, although to be clear: Iām not concerned about access that has to go around the language and directly access the metadata.
Iām not sure the underlying wrapper storage should be exposed via StoredPropertyIterable
. Maybe the wrapped value should be exposed instead. Since property wrappers is coming first we donāt need to decide that now. But it is something I imagine weāll have plenty of discussion about when the time comes.
Apologies again if I wasnāt clear. I was specifically discussing the final design of wrapperValue here. Specifically, I was referring to the idea that had been discussed previously that it would allow the author of the wrapper to hide the wrapper instance from users of the wrapper. Maybe encapsulation is the wrong word here, but the ability to hide the instance could be desirable.
This isnāt what Iām saying. Iām saying that it is true of a design that changes the meaning of $foo
based on the presence of wrapperValue
. That is what pushes this over the edge of being too much magic IMO.
I didnāt say it breaks encapsulation. What I said is that the previous design that was presented introduced an additional encapsulation capability. As I said above, maybe this isnāt the right word to use. Having a name that is visible to users of the wrapper certainly does remove the ability to hide the instance. I think it is tenable to argue that this could be useful for wrapper authors in some cases.
My position is that getting wrapperValue
to carry its weight requires a design that minimizes magic and maximizes utility. I think it will be a useful feature and want to see it happen. Iām just not convinced the design in the review draft accomplishes this.
Iām still not convinced we need to expose the base storage value directly instead of allowing it to be exposed via wrapperValue
if desired. That said, assuming the bikeshed produces a reasonable name (I donāt like _$foo
) itās a design I can live with. It solves the biggest issue in the review draft where the meaning of $foo
changes based on the presence of wrapperValue
.
If we called it $
then the syntax sugar for users just drops two characters: _foo.$
would be equivalent to $foo
. In such a design the real purpose of $
is not so much syntactic sugar as it is allowing foo$
to be visible where _foo
(and therefore _foo.$
) is not.
Also worth observing is that foo$
is very close to _foo.$
while $foo
is more distant. Maybe postfix $
would make more sense if the implementing property on the wrapper itself is simply named $
.
I donāt like referenceValue
because this feature could be used in ways where that name doesnāt really fit. Maybe proxyValue
or projectedValue
or exposedValue
would provide a more accurate description of the capabilities this feature enables.
All of this said, I think there is a good argument that we should reserve the single character $
for something more important.
+1. If weāre going in this direction this is the best solution. And it just so happens to be compatible with SwiftUI as it was presented at WWDC.
If weāre going to open the bikeshed on naming with this context in mind, I think āstorage providerā is worth considering.
Strangely, this doesn't work against the latest master (6/18) snapshot. In fact, that toolchain seems further behind the 5.1 branch, as I'm seeing build errors for wrappers that were previously working.
Property delegate type 'A' does not contain a non-static property named 'value'
However, it does work on the 5.1 toolchain, and the init()
version is what I needed, thanks.
Edit: Looks like I spoke too soon. I have an init()
equivalent:
init(initialValue: Value? = nil, name: String = UUID().uuidString, center: NotificationCenter = .default) {
value = initialValue
self.name = Notification.Name(rawValue: name)
self.center = center
}
But that isn't picked up. So it looks like the plain init()
must be exactly that. Is that expected or a limitation?
Maybe I'm missing something, but is there a reason not to use rawValue
instead of wrappedValue
? This would have nice consistency with enums and be (I think) fairly intuitive to a new user of propertyWrapper
s.
It is interesting but what if you need rawValue
for a different purpose than for this feature on your property wrapper that itself is an enum? I donāt have a concrete example, but it 'could' be an issue for some clever design that we donāt know about yet.
This is not directly related to the proposal itself, but I would just like to thank @Douglas_Gregor for continuously accepting feedback and revising the proposal. I think that it's really great that this feature is being deliberated very heavily and across a wide span of time. It's important to get this right!
Does that mean that the access level of the wrapperValue property is ignored? That would also seem slightly surprising.
The access level of wrapperValue
is required to be the same as the variable wrapper itself.
That is irrelevant as the backing storage is always private
anyway, and $foo
does not try to replicate the access level of wrapperValue
. The contract is only that if a property wrapper type has wrapperValue
se compile will generate a $property
for you and link it to wrapperValue
while the access level is set by property
itself.
Also as I showed above you can write a property wrapper that will expose the nested property wrapper though $property
via composition rules.
Sorry for the short post, but I still strongly believe āproperty behaviorā is more descriptive and obvious than āproperty wrapperā.
Is the name not really being used because of a previous proposal of the same name? I donāt understand the conflict there
Thanks!
BK
Sure, the backing storage is private, but since the access level of the optionally exposed wrapperValue is allowed to be public if the property itself it public, you could also imagine that the propertyWrapper implementer gets to decide the maximum access level.
I suppose it makes sense to let it agree with the property itself, but then the answer to my question seems to be āyesā, whatever you set as access level on wrapperValue will be ignored. OR it will only be allowed to be public perhaps.
You can't expose it using wrapperValue
if wrapperValue
is already used to expose an other type.
So you must have an other way to access it.
Whatever the feature is called, as we keep calling that "property storage", so I think that storedValue
could be a better name for wrappedValue
.
The actual storedValue
would the one wrappedValue
used, not the wrappedValue
itself.
What about value wrappers? We often spell them as SomeWrapper<Value>
and speak in terms of the wrapped "value" internally (wrappedValue
, wrapperValue
) so it makes sense.
We could even move toward spelling wrapperValue
as valueWrapper
. That all seems very clean and unified to me.