I’m ok with a wrapper having to choose one value to expose in addition to the wrapped value. Either itself or something else. Since the first time I understood the original delegateValue
I thought it was cool that the wrapper is able to hide its instance.
Isn’t wrapperValue
a bit misleading, it’s not actually the wrapper value. For @State for example, the type of the wrapper is State<T>
but wrapperValue has type Binding<T>
. I would prefer exposedValue
.
This is part of the reason the proposal is getting revised again and this aspect renamed.
I happy with the current name. I think while swift calls property what other languages call field, that the word property when taken from the point of view of its definition, better matches what a stored variable is.
Perhaps the only sort of confusing thing is the word wrapper because Optionals are also called a wrapper.
I would suggest propertyStorage, heck maybe even go back to the original name propertyBehavior since technically this thing doesn’t have to store anything.
Edit.
propertyBinding
Does anybody know if a property wrapper can provide a default initialization?
@Default var foo:String
print(foo) // print an empty string ""?
swift-evolution/0258-property-wrappers.md at master · apple/swift-evolution · GitHub
I believe you should be able to using an initializer that takes in zero arguments, but when I tried it, I go t a crash
Stack dump:
0. Program arguments: /Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift -frontend -interpret Development/Playgrounds/Codable.playground/Contents.swift -enable-objc-interop -sdk /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -color-diagnostics -module-name Contents
1. While emitting IR SIL function "@$s8Contents3FooVACycfC".
for 'init()' (at Development/Playgrounds/Codable.playground/Contents.swift:13:8)
0 swift 0x0000000112e9d963 PrintStackTraceSignalHandler(void*) + 51
1 swift 0x0000000112e9d136 SignalHandler(int) + 358
2 libsystem_platform.dylib 0x00007fff64960b5d _sigtramp + 29
3 swift 0x000000010fa96a40 swift::Type::transformRec(llvm::function_ref<llvm::Optional<swift::Type> (swift::TypeBase*)>) const + 6848
4 swift 0x000000010eb710d1 llvm::GetElementPtrInst::getGEPReturnType(llvm::Type*, llvm::Value*, llvm::ArrayRef<llvm::Value*>) + 113
5 swift 0x000000010eb70e2d llvm::GetElementPtrInst::Create(llvm::Type*, llvm::Value*, llvm::ArrayRef<llvm::Value*>, llvm::Twine const&, llvm::Instruction*) + 173
6 swift 0x000000010eb70b90 llvm::IRBuilder<llvm::ConstantFolder, llvm::IRBuilderDefaultInserter>::CreateConstInBoundsGEP2_32(llvm::Type*, llvm::Value*, unsigned int, unsigned int, llvm::Twine const&) + 304
7 swift 0x000000010ed9da52 swift::irgen::ElementLayout::project(swift::irgen::IRGenFunction&, swift::irgen::Address, llvm::Optional<swift::irgen::NonFixedOffsetsImpl*>, llvm::Twine const&) const + 850
8 swift 0x000000010ecf0039 swift::irgen::RecordTypeInfo<(anonymous namespace)::LoadableStructTypeInfo, swift::irgen::LoadableTypeInfo, (anonymous namespace)::StructFieldInfo, true, true>::initialize(swift::irgen::IRGenFunction&, swift::irgen::Explosion&, swift::irgen::Address, bool) const + 201
9 swift 0x000000010ecf0057 swift::irgen::RecordTypeInfo<(anonymous namespace)::LoadableStructTypeInfo, swift::irgen::LoadableTypeInfo, (anonymous namespace)::StructFieldInfo, true, true>::initialize(swift::irgen::IRGenFunction&, swift::irgen::Explosion&, swift::irgen::Address, bool) const + 231
10 swift 0x000000010ecf0057 swift::irgen::RecordTypeInfo<(anonymous namespace)::LoadableStructTypeInfo, swift::irgen::LoadableTypeInfo, (anonymous namespace)::StructFieldInfo, true, true>::initialize(swift::irgen::IRGenFunction&, swift::irgen::Explosion&, swift::irgen::Address, bool) const + 231
11 swift 0x000000010ecf0057 swift::irgen::RecordTypeInfo<(anonymous namespace)::LoadableStructTypeInfo, swift::irgen::LoadableTypeInfo, (anonymous namespace)::StructFieldInfo, true, true>::initialize(swift::irgen::IRGenFunction&, swift::irgen::Explosion&, swift::irgen::Address, bool) const + 231
12 swift 0x000000010ecf0057 swift::irgen::RecordTypeInfo<(anonymous namespace)::LoadableStructTypeInfo, swift::irgen::LoadableTypeInfo, (anonymous namespace)::StructFieldInfo, true, true>::initialize(swift::irgen::IRGenFunction&, swift::irgen::Explosion&, swift::irgen::Address, bool) const + 231
13 swift 0x000000010ed4bf39 swift::SILInstructionVisitor<(anonymous namespace)::IRGenSILFunction, void>::visit(swift::SILInstruction*) + 37433
14 swift 0x000000010ed4018a swift::irgen::IRGenModule::emitSILFunction(swift::SILFunction*) + 9818
15 swift 0x000000010ebf3147 swift::irgen::IRGenerator::emitLazyDefinitions() + 9095
16 swift 0x000000010ed1d0d0 performIRGeneration(swift::IRGenOptions&, swift::ModuleDecl*, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule> >, llvm::StringRef, swift::PrimarySpecificPaths const&, llvm::LLVMContext&, swift::SourceFile*, llvm::GlobalVariable**) + 1344
17 swift 0x000000010ed1a7d9 swift::performIRGeneration(swift::IRGenOptions&, swift::ModuleDecl*, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule> >, llvm::StringRef, swift::PrimarySpecificPaths const&, llvm::LLVMContext&, llvm::ArrayRef<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, llvm::GlobalVariable**) + 825
18 swift 0x000000010eb0cc2c performCompile(swift::CompilerInstance&, swift::CompilerInvocation&, llvm::ArrayRef<char const*>, int&, swift::FrontendObserver*, swift::UnifiedStatsReporter*) + 37516
19 swift 0x000000010eb000a4 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 6868
20 swift 0x000000010ea8f333 main + 1219
21 libdyld.dylib 0x00007fff647753d5 start + 1
[1] 76927 segmentation fault swift Development/Playgrounds/Codable.playground/Contents.swift
Guess this calls for a bug report.
Here is the SR: https://bugs.swift.org/browse/SR-10983
Ok, that was weird. The crash went away when I remove the initialValue
initializer from the property wrapper.
Implicitly, when no initializer is provided and the property wrapper type provides a no-parameter initializer (
init()
). In such cases, the wrapper type'sinit()
will be invoked to initialize the stored property.
You can create Default
like so
protocol Initializable {
init()
}
extension String: Initializable {}
@propertyWrapper
struct Default<Value> where Value: Initializable {
var wrappedValue: Value
init() {
wrappedValue = Value.init()
}
}
@Default var foo: String
// translates to (I'm going to use the `_` form which has been discussed recently)
private var _foo = Default<String>()
var foo: String {
...
}
Fantastic, this is a really great idea, I think this will be a really big step forward, particularly if you call it $
as you mention. I'd love to see you take this one teeny tiny further step forward to ensure future generality of this proposal. For background, fundamentally, I see this proposal as introducing two things at the same time:
-
it is introducing the ability to define a "property wrapper" (but as others have mentioned, a better name for this would be "variable wrapper") which allows overloading the getter and setter of a variable with additional behavior.
-
It introduces the ability to refer to a derived value that is linked to the variable with the
$foo
syntax. The first revision of this proposal implied that this name was tied to the storage of the variable wrapper, but the second revision of this proposal clarified that the variable wrapper can actually overload this name to be anything the wrapper desires.
I find this direction (even with your proposed refinements above) concerning for two reasons: First, it is conflating these two different features into one proposal, and second, it is a mistaken assumption that there is exactly one associated view on the variable that could be useful to sugar. I am particularly concerned about this because the spelling of this feature paints us into a corner that could prevent future evolution on this feature from being as elegant as we want it to be.
My view on this is based on the belief that variable wrappers are a critically important feature that will shape the future of a wide variety of APIs that go way beyond SwiftUI - this point was made in the Modern Swift API Design talk at WWDC, which touched on just a few examples of this critical feature - but there are far more that will be explored over the coming years of Swift's evolution, and I'd really love to see this feature service a wide range of these library applications over time.
I'd recommend considering this proposal to be the first two steps of a three step proposal. In theory each step should be independently considered, but given that SwiftUI is a critical use-case and needs the first two steps in conjunction, considering the first two steps at the same time makes sense. The three step proposal would look something like this:
First Step
Introduce the notion of a "variable wrapper attribute". The variable wrapper gets an attribute on it, and has a wrappedValue
property, here's an example:
@variableWrapper
struct UserDefault<T> {
...
var wrappedValue: T { get {...} set {...}}
}
If you use a variable wrapper like this with the @UserDefault var foo : T
syntax, then you'd get a foo
computed property that routes through the wrapper, and you'd get a foo$storage
property with private
access control. This achieves your first goal of providing the storage with a consistent name that can be found through reflection, explicit initialization, the Xcode debugger, and all the other things that stored properties appear in. I think that adding the name "storage" to this makes it much more clear what this is, eliminates surprise, and (given its infrequency of use) I think that adding a word to its name improves clarity above and beyond the $$foo
or _$foo
proposals.
Second Step
Some clients want to provide one privileged computed view of a variable - it could be the storage directly or could be a derived variable that projects the storage into a form intended to be API of a variable with this wrapper. It is reasonable for these to be really really short given the bindings use-case in SwiftUI and other clients are likely to want other similar default projections when there is only a single possible interpretation (e.g. a resettable property wants a reset method on its storage).
To support this, we allow defining a $
property on top of the base proposal which provides access to this in a variable-wrapper defined way:
@variableWrapper
enum Lazy<Value> {
...
var wrappedValue: Value { ... }
public var $ : Lazy<Value> { get { return self } set { self = newValue } }
}
We'd require that the $
member have the same access control level as the wrapper itself, and thus (in the cross-module case) it matches the access control level of any properties that use the wrapper type. This gives wrapper authors control over what they want the $
member to do, the specific API they want to expose, and whether they want to expose it. This also ensures that a $
member is not exposed unless the wrapper author explicitly opts into it.
If a wrapper author defines this member, then users would get a suffix $
member, e.g. a foo
variable would get a foo$
member of this name. This is a tiny delta over the proposal as you suggest it above, but this tiny difference is critical because there isn't necessarily a primary view and a single projection of a variable -- and most importantly, it allows a future proposal to extend this in the third step.
Third Step
While the proposal as written covers the first two steps, I think it is important to think beyond the immediate SwiftUI use case, and consider more general applications of variable wrappers. In particular, it is not clear that the fundamental model of a variable is one primary view and exactly one more projection of that view -- nor is it clear that all uses of variable wrappers will want a projection that has such a trivial name as $
. Swift is designed to allow domain experts to design APIs with evocative APIs and think about the tradeoff (e.g.) between anonymous arguments and keyword arguments, and it would be really unfortunate if this feature prevented API authors from being able to use descriptive names for projections in variable wrappers.
Beyond clarity of naming, the fundamental nature of variables allows there to be "N" projections of a variable, particularly in advanced cases -- for example, it is easily imaginable that you might want to have a data projection, a database handle projection, and have an exportable bindable projection. Other cases may want more than one projection for other reasons: e.g. because the underlying foo$storage
is complex or private, and there are several curated views that have different sorts of (protocol or semantic) requirements.
As such, the third step is to allow named computed variable suffixes, which can be easily extended onto the first two proposed steps, by allowing named properties, e.g.:
@variableWrapper
enum MyWrapper<Value : MyProtocol> {
var wrappedValue: Value { ... }
// "public var $" : is still allowed, just not shown in this example!
public var $dbhandle : DBHandle<Value> {... }
public var $view : MyView<Value> {... }
public var $binding : MyBinding<Value> {... }
}
When applied to a v : T
variable, such a variable wrapper would produce private v$storage
(of type MyWrapper<T>
), as well as several computed properties that match the underlying access control level of v
: v$dbhandle
, v$view
, v$binding
.
This extension allows significant added modeling power, more clarity of APIs, doesn't overload prefix $
(currently widely used by anonymous closure arguments), doesn't conflict with LLDB, and provides a more logical access control story -- since the declarations that are being defined align clearly with the declarations that clients can use.
I'd really like for the core team to consider a direction like this, even though it means moving the location of the dollar sign from the beginning of the derived variable name to the end of the name.
-Chris
Even though I don't like the variable$view
visual appearance, I'm quite intrigued by the 0 to N projection idea which can become quite handy and more important as you mentioned a very powerful tool.
One question regarding the syntax change you're proposing.
Do we treat $
in this case like a .
?
// if the answer is yes, this should be allowed as well
variable
$view
.member
.member
// if the answer is no, can we treat it like an operator
variable $ view
I personally hope that the answer is a 'yes' because I would really want to write the former style iff needed.
Building on top of that idea, why should we stop at variables? I think it's reasonable to go beyond that and allow methods as well.
@variableWrapper
struct S<Value> {
...
func $baz() { ... }
}
@S var foo: Value
foo$baz() // should be valid as well.
-1 on the renaming to @variableWrapper
. Yes, local variables are not called properties, but neither are members of types called variables. And (I believe) this feature will be used much more heavily for properties than for variables.
Furthermore, using "property" as part of the name for things that can be used on both properties and local variables has precedence in the language already for Property Observers (willSet and didSet). Even though you can use will/didSet
on local variables, the more common use case is for properties.
For one, I see that if we’re to stay with prefix $
, we could instead use suffix $
for custom token.
var view$ : MyView<Value> {...}
Then we do view$foo
instead. Since $
is not a valid operator of any kind, there should be no ambiguity at all either way (except of course, outside of the language like in lldb).
We may even go further to allow both prefix and suffix form.
var prefixed$ : MyView<Value> {...}
var $suffixed : MyView<Value> {...}
to which we now do prefixed$foo
and foo$suffixed
. I personally like this, but that’s a discussion for another day.
As is, $
is used mainly as variable (no function support, yet), which provides member access via suffix .
. So suffixing could interfere with such access.
Anyhow, I don’t see us paint ourselves into corner that easily.
Hi,
StorageWrapper
, WrappingStorage
or ValueWrapper
seems to best suit the usage for me. And also I prefer wrappedValue
for the ... wrapped value
@Chris_Lattner3 instead of desugarring every variable wrappers $
member into the scope of the wrapped variable we could consider to move the exposure somewhere else.
Here is an example that should also work with global/local variables:
@variableWrapper
enum MyWrapper<Value : MyProtocol> {
var wrappedValue: Value { ... }
public var $: Lazy<Value> { ... }
public var $dbhandle : DBHandle<Value> { ... }
public var $view : MyView<Value> { ... }
public var $binding : MyBinding<Value> { ... }
public func $baz() { ... }
}
@MyWrapper public var property: Value
// swift interface for that property
public var property: Value {
get
set
var $: Lazy<Value> { get set }
var $dbhandle: DBHandle<Value> { ... }
var $view: MyView<Value> { ... }
var $binding: MyBinding<Value> { ... }
func $baz()
}
The property wrapper itself is not exposed if not required, all projections are exposed right from the 'computational scope' (not sure if that's the correct term here).
That way we can treat $
like we treat .
today and use line breaks for better code formatting.
property
$ // this is possible, but not really readable
// prefer for that case
property$
// all should be possible
property$dbhandle.clearDataBase()
property
$dbhandle
.clearDataBase()
property$baz()
property
$baz()
Is this really so different from:
@variableWrapper
enum MyWrapper<Value : MyProtocol> {
var wrappedValue: Value { ... }
public var $: Self { return self }
public var dbhandle : DBHandle<Value> {... }
public var view : MyView<Value> {... }
public var binding : MyBinding<Value> {... }
}
…and then accessing it using $v.dbhandle
, $v.view
, $v.binding
?
It is if your wrapper type has more members that are accessible. The way @Chris_Lattner3 pitched is that the wrapper type author can control the projection of a specific set of members on the wrapper type.
In you case you can do:
extension MyWrapper {
func foo() {}
}
// then
$v.foo()
While the author of the wrapper type would like you to only access the $
projected view types, not the main wrapper type itself.
Edit: Also in your case you will be forced to expose the property wrapper type outside the module boundary if you want to expose the projection itself. What Chris pitched avoids that situation, see my example above that will keep everything nicely in one place.
Also,
why not choosing a better symbol than $
? Because...
- it is a money symbol
- it is the money symbol of the US dollar, so it may seem a little ego-centric, not to say "political". Think of the alternatives which would also suffer from the same defect:
£
,€
, etc.. - it brings me back to BASIC days, and some other Languages I don't fancy anymore
- It is visually cumbersome... After all it is a crossed letter.
Just saying...
This just looks more and more like like https://github.com/apple/swift-evolution/blob/master/proposals/0252-keypath-dynamic-member-lookup.md but instead of using .
we use $
. Why not design it the same way?
Anything that has the $
gets the wrapper
foo.$view
food.$binding
food.$
would be weird. specially if we allow food.$.bar
all the properties should be named.
foo.$storage
this would allow nesting too.
foo.$storage.$storage.$storage
what about
foo@.view
instead of $foo.view
which would read the "foo_Wrapper
_ dot view"
I like that it repeats the same @
which denotes a special Wrapped var, example:
@Lazy var foo: Int = 0
foo@.wrappedValue
I'm not sure I understand why this third step is necessary when it can be achieved by making those projections available on the "privileged computed view":
@variableWrapper
enum MyWrapper<Value : MyProtocol> {
var wrappedValue: Value { ... }
var wrapperValue: MyWrapper { return self }
public var dbhandle : DBHandle<Value> {... }
public var view : MyView<Value> {... }
public var binding : MyBinding<Value> {... }
}
@MyWrapper var foo = 5
foo // 5
$foo // MyWrapp
$foo.dbHandle // DBHandle