[swift-evolution] [Proposal] Property behaviors

Joe Groff jgroff at apple.com
Thu Jan 21 23:40:42 CST 2016


> On Jan 21, 2016, at 8:05 PM, Michel Fortin <michel.fortin at michelf.ca> wrote:
> 
> Le 21 janv. 2016 à 21:36, Joe Groff <jgroff at apple.com> a écrit :
>> 
>> You're glossing over some of the subtleties of initialization. Recall that a "plain old" stored property can be initialized out-of-line:
>> 
>> func foo() {
>>  var x: Int
>>  ...
>>  x = 1
>> }
>> 
>> struct Bar {
>>  var y: Int
>> 
>>  init(y: Int) {
>>    self.y = y
>>  }
>> }
>> 
>> which creates complications for your 'init { }' model. If initializers chain the way you describe, then either storage_behaviors can never be initialized out-of-line, which would be a regression for didSet/willSet applications, or we need to gain the ability for definite initialization to turn 'x = 1' into an 'init' call. However, since storage_behavior can refer to its initializer anywhere, not only during initialization, the initialization assignment potentially has to *capture* the RHS value to be evaluatable later, which would be surprising.
> 
> The idea was that inside a `storage_behavior` the `initValue` is only available inside of `init`. There are two exceptions: if the behavior is `eager` the `initValue` is available everywhere. Or if the behavior is `deferred` then the `initValue` is available everywhere *except* in `init`.
> 
> It seems I mistook what `eager` was for in your proposal. My interpretation of `eager` was that the value had to be specified inline with the variable declaration and could not be specified later in a constructor. Thus, its the same thing as `deferred` except that evaluating the initializer does not depend on `self` so you can use `initValue` truly everywhere including inside of `init`. Thus it appears my `eager` has no equivalent in your proposal while your `eager` is the same as not putting any modifier in front of `storage_behavior`.
> 
> I'm unsure now if there is any reason for my version of `eager` to exist. But it turns out you don't need my `eager` to implement resettable, all you need is to allow some storage for the initial value:
> 
> 	storage_behavior resettable<T>: (initial: T, current: T) {
> 		init {
> 			currentValue = (initial: initValue, current: initValue)
> 		}
> 		func reset() {
> 			currentValue.current = currentValue.initial
> 		}
> 	}
> 
>> If a behavior wants to be able to override a superclass property, as didSet/willSet can do today, then initialization is out of the behavior's control. These are the factors that influenced the "base property" design in my proposal—if you want behaviors that compose, they really *can't* meddle in the underlying base property's initialization. These use cases can be addressed instead by your accessor modifier mechanism; however, if you try to break apart storage behaviors and custom accessors, then both features suffer—storage behaviors can't require accessors to parameterize behavior, and custom accessors can't introduce new storage if needed to apply their implementation over the underlying property.
> 
> That's true. In other words, you can't override a superclass property with a behavior that touches `initValue` or defines the base storage type. As you say, we probably need another modifier keyword alongside `eager` and `deferred` to express that restriction so we can apply a behavior like `logging` when overriding.
> 
> And finally, to allow storage in overrides (both behaviors and accessors), all you need is to allow `var` and `let` inside of variable declarations, just like I suggested for functions:
> 
> 	var myvar: Int {
> 		var observers: [Observer] = []
> 		func addObserver(o: Observer) { observers.append(o) }
> 	}
> 
> then it automatically becomes possible to put all that boilerplate code inside of a behavior:
> 
> 	storage_behavior observable<T>: T {
> 		var observers: [Observer] = []
> 		func addObserver(o: Observer) { observers.append(o) }
> 	}
> 
> and it could be allowed in custom accessors too, although I can't come with a use case for this.

I feel like, once you start stretching your design this way, we don't end up with something that's all that much simpler than what I've proposed. To help make review more digestible, I am planning to slim down the proposal a bit—I think we can subset out eager-vs-deferred initializers, composability via base properties, and extensions as future extensions to consider separately. That means the feature won't immediately be able to replace some of Swift 2's builtin features in all their subtleties, but it still gives us something useful that can be gradually generalized.

-Joe


More information about the swift-evolution mailing list