I'm thinking that since now the author can omit the wrapperValue, access to it is meaningful, and if we have multiple of them, we can't access the inner wrapperValue at all. Furthermore, if we have wrapper without wrapperValue in the mix, it could be confusing if $foo doesn't always mean the outermost wrapperValue.
@NoWrapper @HasWrapper var foo: Foo
$foo
// Should this be error, because NoWrapper has no wrapperValue?
// or should this use _base_storage.value.wrapperValue (HasWrapper's implementation)?
One thing we could do is that we can differentiate them by type, that is
@propertyWrapper struct Wrapper1 {
var wrapperValue: Foo1 { ... }
}
@propertyWrapper struct Wrapper2 {
var wrapperValue: Foo2 { ... }
}
@Wrapper1 @Wrapper2 var foo: ...
let bar1: Foo1 = $foo // Uses Wrapper1 declaration; _base.wrapperValue
let bar2: Foo2 = $foo // Uses Wrapper2 declaration; _base.value.wrapperValue
With this, foo will have full access to all wrapperValue as overloads, and so collision shouldn't be much of a problem.
I have hard time convincing myself that self is a sensible default, especially since the most basic of wrappers won't need it (Atomic, Lazy, etc). While that's definitely debatable, I feel that it's at least as important that the author can opt out of it, and I think this would be the best way to allow that.
It does seem reasonable. However, I don't see how having access to the implicitly-created wrapper variable is actively harmful, even when it has no useful API.
The one thing I'd like to chime in about is that I'm not enamored of using a notation that's currently order-insensitive for non-commutative composition.
I'd hope to see some sort of indication of non-commutative nesting, and I don't think it has to be onerous:
// Notation in proposal
@DelayedMutable @Copying var path1: UIBezierPath
// $path1 has type DelayedMutable<Copying<UIBezierPath>>
// Bikeshed notation
@DelayedMutable(@Copying) var path1: UIBezierPath
// $path1 has type DelayedMutable<Copying<UIBezierPath>>
This bikeshed color doesn't interact well with the ability to invoke initializers on the wrapper. Maybe we could require users to write the generics explicitly, but allow inference of the value argument so it would be:
How about requiring a comma between property wrapper attributes? It would require that wrappers be listed with intervening attributes, but I don't think that would be too much of a burden.
@DelayedMutable, @Copying var path1: UIBezierPath
The comma gives it a list-like implication, which implies ordering.
I'm leaning toward switching to wrappedValue. I'm fretting over whether that means init(initialValue:) should become init(initialWrappedValue:), because... I really like init(initialValue:).
I think we can keep init(initialValue:) as it does not hurt anyone nor it's wrong to require the compiler or the user to write wrappedValue = initialValue. As a bonus we don't need an explicit self. here. I mean it even reads well as "wrapped value is assigned with initial value".
I would favorite this style where the inner generic type parameter lists can be omitted in some cases. This plays well with property wrappers that have more generic type parameters and this would avoid repetition of property attributes.
@propertyWrapper struct A<Value, X, Y> { ... }
@propertyWrapper struct B<Value>
@A<B, Int, String> var property: Value
I'm inclined to make this change, although I'd always put the initialValue: argument first (since it won't be defaulted or variadic, whereas others might be).
I think you misunderstand what I meant to say here. In this particular example <Value> from B is omitted, while Value for A is indeed B, but B alone isn't the full type, however B<Value> is.
// this makes the following
@A<B, Int, String> var property: Value
// as a short form for
@A<B<Value>, Int, String> var property: Value
I understand what you meant, but in its full form, property itself will be of type B<Value> instead of Value if we're to omit the type since B<Value> is still a valid type.
@A<Value, Int, String> var property // : Value
@A<B<Value>, Int, String> var property // : B<Value>
Edit:
nvm, it seems not to be a unique problem to this syntax.
It's implying that B<some inferred generic arguments> == Value, which is exactly what it's doing. Moreover, the type inference rules in the latest draft imply that this will work. I went ahead and changed @DevAndArtist's example to not use composition:
@_propertyWrapper struct A<Value, X, Y> {
var value: Value
}
struct Z<T> { }
struct Test {
@A<Z, Int, String> var property: Z<Float> // okay! Infers <Float> for the `Z` in `A<Z, Int, String>`
}
The "master" implementation of property wrappers, which implements the new type inference rules, makes this work. It's completely reasonable for this to work in the composition case (once I get around to implementing composition).
This would change the type grammar. We shouldn't do that.