[swift-evolution] [Proposal] Property behaviors

Joe Groff jgroff at apple.com
Tue Jan 19 12:07:22 CST 2016


> On Jan 18, 2016, at 4:29 PM, Jacob Bandes-Storch <jtbandes at gmail.com> wrote:
> 
> Here are some of my thoughts from reading through the current version (https://gist.github.com/jckarter/50b838e7f036fe85eaa3 <https://gist.github.com/jckarter/50b838e7f036fe85eaa3>). Apologies if this duplicates what anyone else has said; the thread is kind of unmanageable :)
> 
>   // Behaviors can declare that properties using the behavior require
>   // a `deferred initializer` expression. When deferred, the
>   // initializer expression is assumed to be evaluated after
>   // initialization of the containing value, which allows it to refer
>   // to `self`. If declared, `initializer` is bound in accessors and
>   // methods of the behavior.
>   deferred initializer: Value
> 
> This seems like an important feature, but the syntax is strange to me. It looks like it would be declaring storage inside the behavior, but it's really specifying the type of something used in the containing object's property declaration.
> 
> I can think of a couple alternatives:
> 
> 1. Rather than passing an initial value in the containing object like "var [lazy] prop = someInitialValue", pass it explicitly as a parameter to the behavior,
>     like "var [ lazy({ return someInitialValue }) ] prop: Type"
> 
>     I think it might be generally useful for behaviors' initializers to take arguments; it'd handle more than just this case. For example, you could have a behavior called synchronized(maxConcurrentRequests: Int) which would allow arguments passed to affect the behavior's...behavior.

You can parameterize using accessors, which can also theoretically replace all uses of a bound initializer. For example:

var [lazy] prop: Type { initialValue { return someInitialValue } }

> 2. Make the "deferred"-ness a modifier/attribute on the behavior declaration, like "public @deferred var behavior lazy<Value>: Value { ... }", which would make the implicit initialValue inaccessible from the behavior's init(). The same with @eager.
> 
> 
> x.lazy.clear() // Invokes `lazy`'s `clear` method
> 
> As I think others have mentioned, this is ambiguous if x itself has a property called "lazy". I'd be reasonably satisfied with any of the proposed solutions to this.
> 
> 
>   base var value: Int
> 
> I don't think I like the fact that this needs to be explicitly declared. Do all behaviors have to use the same identifier for them to be composable? Could you use "super" to mean this, instead of explicitly declaring a base property?

The identifier 'value' is up to the declaration to decide. Some behaviors don't have a base; anything that needs to control the storage, such as 'lazy', can't really be composed this way. I thought about using 'super' for this, but a behavior would still need a way to declare whether it has a 'super' or not. It's also weird to bind both 'self' and 'super' with totally different meanings. A number of people have objected to the special treatment of 'self' I've proposed already.

> 
> Accessor requirements can be made optional by specifying a default implementation:
>   mutating accessor willSet(newValue: Value) {
>     // do nothing by default
>   }
> 
> Up until this point, I was thinking of accessor declarations like protocol requirements, in that they have no implementation ("accessor foo()" like "func foo()"). I think the lack of implementation is what makes it clear that these are requirements, not things the behavior is implementing.

It's planned for the future to allow protocol declarations to contain default implementations in-line, and I see 'optional' protocol requirements as being only for ObjC compatibility and not something you should use in pure Swift designs.

> 
> So perhaps you could use the "optional" specifier to indicate that they aren't required, rather than allowing an implementation block in the behavior. "optional accessor foo()" would allow the behavior's implementation to use "foo?()".
> 
> 
> var [foo] x: Int { 
>     bar(myArg) { print(myArg) } // `arg` explicitly bound to `myArg` 
> }
> 
> Why not require a type annotation for parameters here? I recognize this matches the current syntax of set(newValue), but it would be more flexible if this were more like a function declaration.

We could optionally accept type annotations, but the argument types should almost always be inferrable.

> 
> To preserve the shorthand for get-only computed properties, if the accessor declaration consists of code like a function body, that code is used as the implementation of a single accessor named "get".
> 
> This seems a bit vestigial. Maybe it could be allowed only when a computed property is declared without using any behaviors.

That would be reasonable.
> 
> A few more questions:
> 
> - Can a behavior's own properties/storage use other behaviors? Can a behavior be recursive?

Yes, and yes, as long as in doing so you don't produce infinite storage.

> 
> - What of deinitializers for behaviors? Would it be possible, for example, to make an observable behavior whose willSet/didSet run during init and deinit (which Swift's current property observers can't do)?

I don't think we want to change how behaviors work in init and deinit. It isn't possible to invoke methods before `self` is initialized, and it's dangerous to do so during `deinit`.

> 
> - Are accessor implementations allowed to access the "current" property value? Currently, inside "var foo { didSet { … } }" you can access the current value by referencing "foo".

Accessor implementations can access the behavior's storage, so they can either access their stored property containing the property value directly, or factor more complex accessor logic out into a helper method.

> 
> Overall this looks great. I'm looking forward to it. :-)

Thanks!

-Joe
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160119/9d8b08c1/attachment.html>


More information about the swift-evolution mailing list