My use case is implementing observable properties. Exposing a separate property for the observable is not ergonomic.
Here I’ll use propertyWrapper author, library author, and end user to disambiguate between the three parties.
I find this example not exactly apt for the (upcoming) design, since you referred to the author’s post on latest (again, upcoming) change, we should be on the same page.
Since reset
ties to the backing storage itself, it would be the propertyWrapper’s method, and not the wrapperValue
’s method. So you should access it via _foo.reset()
which is private
by default. In fact, there’s no way currently to grant _foo
(the backing storage) more access level.
As a matter of fact, in the (upcoming) design, the propertyWrapper’s author has no control over the access level of the wrapped properties at all:
foo
(wrapped value) is dictated by library author_foo
(backing storage) is always private$foo
(wrapperValue
) default to matchfoo
, and (as future direction) can be changed by the library author
That’d be a totally new concept, and I’m not sure is appropriate within Swift’s ownership system.
Am I right that you also saying that wrapperValue
should have the same type as wrappedValue
then? This is a general question not about this particular Int
case.
- What's the point of having
wrapperValue
then? - How does the compiler know when to allow
$foo
or not? - How can you expose
$foo
outside module boundaries if$foo
is a mutable reference but not a direct member of the type with a wrapped property? - How can the compiler on the client side know what
$foo
should do?
Remember wrappedValue
can be immutable (it can be a computed property that is returning _wrappedValue
for example, but _wrappedValue
can be a var
), while wrapperValue
can be mutable.
- How can the property wrapper author or the author of a library that uses such a wrapper control the exact exposure?
I think $foo
as "a mutable reference" opens too many questions while just restricting the current design to be just like a Bindable
and not allow anything else. I'd be strongly against such a move.
Sorry for the flood of questions, but I really want to understand your design and why you think it would surpass and simplify the current wrapperValue
design.
In the same spirit I proposed earlier that bindings should be created using &
, like inout
. The main difference between the two is bindings can escape. Unfortunately, the idea of overloading with inout
seem to make some people worry about type checker performance. (And also the videos about SwiftUI.)
So I guess we can use the $
prefix to mean escaping reference. But then if the meaning of $
changes for each wrapper, the semantics of $
become muddy and I'd be really worried about that. I'd be more at ease if the $
prefix was reserved to mean binding and something else was used to access the wrapper's public properties.
My general concern about this proposal is that it is trying to cover too many use cases. I guess there's no avoiding that if you have to introduce the feature using made up examples while trying to keep the real motivating case under wraps. If the motivation behind this proposal is bindings, I think we can do better by focusing on this use case. I fear it's a little late though.
No. I am saying that wrapperValue
should not exist, at all.
There should be the main property “var foo: Int
”, and there should be the backing storage which is synthesized as “private var _foo: MyWrapper<Int>
” (or explicitly declared with a different visibility).
One property, one backing storage, that’s it. Simple and straight to the point.
We can already create a mutable reference, it is just cumbersome and annoying:
struct MutableReference<T> {
private var getter: ()->T
private var setter: (T)->()
var value: T { return getter() }
func setValue(_ x: T) { setter(x) }
init(_ g: @escaping @autoclosure ()->T, _ s: @escaping (T)->()) {
getter = g
setter = s
}
}
At the use-site:
var x: Int = 4
let xRef = MutableReference(x, {x = $0})
Now any changes to x
are reflected in xRef.value
, and any calls to xRef.setValue()
will modify the value of x
. You can even pass xRef
to code in a different module, and it will still work properly.
As I see it, $foo
should always be a mutable reference to foo
. It doesn’t matter whether foo
is a stored property or a computed property or a local variable, or whether it was declared with a property wrapper, or anything else.
Sometimes you want a mutable reference, such as when binding UI state to model values, and in those situations it would be a great convenience to write “$foo
” instead of “MutableReference(foo, {foo = $0})
”, and be able to use assignment syntax instead of calling a setValue()
method.
There is no wrapperValue
in my design.
Property wrappers are one simple, standalone feature: foo
is backed by _foo.value
, which makes it easy to get the behavior encapsulated by MyWrapper
.
Mutable references are another simple, standalone feature: $foo
is a mutable reference to foo
.
These two features are entirely separate and orthogonal. They each do exactly one thing, and they do it well.
Furthermore, these two features also compose naturally with each other. You can create a mutable reference to a property *regardless* of whether that property uses a wrapper or not.
I see it exactly the opposite way. The proposal, with its magical projections and views and rigidly-fixed access levels, is eminently non-Swifty. I would go so far as to say it is anti-Swifty.
It is making a giant, complicated mess out of what should be a simple matter of one property providing storage and behaviors for another.
• • •
In fact, I think everything in the current proposal about “$
” and its related uses, should be removed entirely. Then this proposal would be small, simple, and hopefully noncontroversial.
A second, separate, small and simple proposal could then be made to introduce mutable references, presumably with the spelling $foo
so that they work with SwiftUI sample code unchanged.
I think I like the idea of using & but maybe for the backing storage instead of the binding.
If I pass a value as inout that has a backing storage, I think it makes sense that &someProp refers to the backing storage. What else would it be passing if not the backing storage? So it this way if people want the backing storage, they are going to need to get it as an inout which should help with exclusivity.
Apologies for not being precise here, but I think the net effect still stands? The library author (of Widget
) might get a hint that something's changed because _foo.reset()
is no longer valid, but they'll still have their public API changed to include wrapperType
'd $foo
and $bar
? Maybe it's somewhat disingenuous to say defining wrapperType
dictates access level, but it still dictates how external users see the wrapped property (or functionality it provides). By first not implementing wrapperValue
, then doing so later, the property wrapper author is still affecting their consumers in a way I see no precedence of.
I think this use case falls into the "not for you (yet)" camp I mentioned. I think this should require a way for you to specify access levels yourself which isn't part of this proposal.
FWIW, I see the value of wrapperValue
(or whatever it ends up being called), but think it's so closely related the the backing store (they're both providing access to stuff defined on the property wrapper) that making its access level anything other than exactly the same as the backing store is counter intuitive.
Touché. I don't have a good solution for that, but I figured most of property wrappers will be of 2 types; either modifying the getter/setter behaviour, or venting out the wrapperValue
, so adding/removing it would be unlikely.
We could consider adding/removing wrapperValue
to be non-backward-compatible change.
That does not scale pass the module boundary from my point of view. If the library author chooses not to expose the property wrapper type used to wrap some property and this property is get only publicly then there is no $foo
for the client, while the current wrapperValue
triggers the compiler to create a property that can later be exposed to clients while keeping the @WrapperType
annotation and the wrapper type itself a secret.
I understand your desire of having a fixed meaning of $
prefixed values, but this does not fully cover the story around wrapperValue
functionality. It rather removes a proposed feature and adds more issues which really shouldn't exist to the property wrapper types.
I’m mostly in agreement with @Nevin regarding the state of wrapperValue
, and agree that it should at the very least be part of a follow up proposal, but this mutable reference syntax is necessary with the constraints that have been laid down by the Core Team, since using $foo
to create a Binding
in a SwiftUI context is non-negotiable. Given that, I would much prefer a solution which folds the $
syntax into a consistent and clear language construct rather than an arbitrary user-defined API which is completely inscrutable at point-of-use.
I accidentally called property wrappers as property builders. That got me thinking, what are the main differences from a user facing API standpoint of view between @funcWrapper and Function Builders.
What we are missing is people being able to create their own attributes.
Can you consider making backing storage fileprivate
instead of private
. I miss the simplicity of early Swift access control.
I think we should seriously consider this direction.
One question: With $foo
just meaning a mutable reference, how would you expose things like observe()
on an @Observable to outside of the type. I don't think it necessarily needs to have a $, but we do need some way of exposing desired functions like observe()
without manually providing it for each observable property (since that would erase the benefit of an @Observable wrapper)
One possibility is using an infix @ similarly to how people were discussing @Chris_Lattner3's third step earlier:
myVar.myObservableProp @ observe {...} //I'd also be ok with myObservableProp.@observe {...}
The nice thing about this is it mirrors the public declaration of the feature:
struct MyType {
@Observable var myObservableProp : Int
}
If we go this route, I would recommend using a second attribute (instead of access control) to mark the properties which are exposed in this way:
@ValueWrapper
struct Observable {
@WrapperFunc func observe(...) {...) //Someone probably has a better name for this
}
Public properties and functions not annotated in this way would be available on _foo
.
That leaves $foo
as @Nevin's (and SwiftUI's) concept of a mutable reference.
Actually, now that I think about it, I kind of prefer:
myVar.myObservableProp.@observe {...}
Everything else would be the same as I wrote above...
Bike-shedding: wrapperValue
-> associatedValue
.
Since, generally speaking, it doesn't really wrap any of the value, but provide a functionality closely related to wrappedValue
.
Also, it seems to me that wrapperValue
looks like associatedType
for a protocol
, used when there's another value closely associated with it, but is more or less independent from the property wrapper itself.
This is very similar to the associated object capability of Objective-C, so I'm not sure it's a very good alternative.
Afair, the original concept took the syntax from Kotlin - and imho this option has been discarded too quickly:
- It's a tested design
- It has no ambiguities with nesting / order of wrappers
- It has an obvious answer for access control, which has become a big source for controversy (think
public var test: Int by public Lazy { 55 * 44 }
Also, I don't think the existing use of "@" is a argument in favor of the current spelling at all:
Property wrappers won't be the only thing that makes use of this syntax, will it?
So we'll have several completely different things which are expressed in the same way, and I don't think this is any better than having a dedicated way to specify wrappers.
This thread is incredibly long and I haven't followed the preceding discussions closely so apologies if this has already been discussed, but has a function-call-like syntax been discussed for accessing the wrapper and backing storage? Something like this:
@MyWrapper var foo: Int = ...
#storage(foo)
#wrapper(foo)
It's slightly more verbose, but with the benefit clarity and explicitness. It seems arbitrary to use the $
character for this purpose.