[Pitch #3] Property wrappers (formerly known as Property Delegates)

I've been thinking long and hard about Property wrapper's wrapperValue. I came to believe that it's necessary, almost natural, to have, and I do have some suggestion:

  • Make $foo usable only when wrapperValue is defined
  • Added init(initialWrapperValue: WrapperType) to allow initialization from $foo, ie. $foo = WrapperType()

tl;dr: this would make a mental model of foo always accessing Wrapper<...>.value/wrappedValue, and $foo always accessing Wrapper<...>.wrapperValue, which would be an easier mental-model.

Longer version:

I was trying to find the relation between Property wrapper, wrapperValue and wrappedValue. And I came up with a conclusion that Property Wrapper is the common storage for the tightly-coupled wrapperValue and wrappedValue. So the base storage is inaccessible on its own, but vent out 2 accessible variables, wrappedValue and wrapperValue.

The distinction between base storage and $foo is rather important since there are cases where base storage is not accessible at all (due to wrapperValue existence).

    ┌───────┐      @Wrapper var foo: Foo
    |Storage|      
    └───┬───┘      // Synthesized
        |          var _base: Wrapper<Foo> // Inaccessible
wrapped | wrapper  var foo: Foo {
   ┌────┴────┐       get { _base.wrappedValue }
   |         |       set { _base.wrappedValue = $0 }
┌──┴──┐   ┌──┴──┐  }
│ foo │   │$foo │  var $foo: ... {
└─────┘   └─────┘    get { _base.wrapperValue }
  ↑ ↓       ↑ ↓      set { _base.wrapperValue = $0 }
access    access   }

foo could be regarded as front-facing interface that is used for most access behavior (get/set), while $foo is back-facing property that is used for the added extra behavior (Reset variable; add Binding, Link, Delegate, Observer, etc.).

We may not always want to have direct access to PropertyWrapper itself if the only useful thing you can do is $foo.alternativeRepresentation, esp. since many PropertyWrappers will be in a form of one-off struct/class that's not very useful on its own, and those that are (useful on its own) don't even need direct access. But of course if direct access is wanted one can do

@propertyWrapper struct Wrapper {
  var wrapper: Self {
    get { return self }
    set { self = $0 }
  }
}

Then, all PropertyWrapper that doesn't need extra functionality can omit wrapperValue, which will in turn disable $foo access.

Furthermore foo and $foo syntactically should be proper variable in its own right. So we should allow for initializer to be done via $foo, to which I suggest init(initialWrapperValue:).

init(initialWrapperValue:) is needed because $foo may not refer to its wrapper type (even in current pitch).

As per composition, foo should work much the same way as in the current pitch, but $foo has extra restriction, that only upto one (1) PropertyWrapper can have wrapperValue. $foo will be ill-formed otherwise.

So, here I conclude my thought process, that PropertyWrapper are one that wrapper around any normal type, and then provide extra functionality via $foo. I tried to decouple PropertyWrapper and whatever $foo is (getting B object from A variable, <SharedStorage?>), but in the end it seems like PropertyWrapper is almost a light extension of <SharedStorage?>. So (as implied in any internet comment), any thought?

6 Likes