[swift-evolution] [Proposal] Property behaviors

Félix Cloutier felixcca at yahoo.ca
Wed Jan 13 22:44:50 CST 2016


> Le 13 janv. 2016 à 23:08:24, Joe Groff <jgroff at apple.com> a écrit :
> 
>> Notable points of confusion:
>> 
>> it's confusing to me that `self` is the containing type and the behavior name is the "behavior's self".
> 
> Others have noted this too. Would it be less confusing if one had to explicitly name the "container" as a member, e.g.:
> 
> var behavior synchronized {
>   container var parent: Synchronizable
>   base var value: Value
> 
>   get {
>     return parent.withLock { value }
>   }
>   set {
>     parent.withLock { value = newValue }
>   }
> }
> 
> An `init` parameter covers use cases where the initializer expression is used only during initialization, but doesn't let you use the initializer after initialization, which is necessary for `lazy`, `resettable`, and other use cases. Even with @autoclosure, it seems to me that, without `initializer`, we'd need to allocate per-property storage for the initializer expression to use it later, which is something I'd like to avoid.

I wish we didn't have immaterial members like `parent` (which would be an inout parameter to every method, it seems) and `initializer`, but I do prefer having an immaterial "container var parent" over a repurposed self. I can see the point with `initializer` (even though I'm not sure what that one would be).

How are property behaviors "merged" into container types? Is there any chance that `initializer`, whether an autoclosure or a value, could be optimized away in most cases (screwed be debug builds)? That would be my favorite outcome.

> 
>> I see (after reading it) that `var behavior foo<Value>: Value` means that foo "applies to"/"wraps" Value, but I find it confusing to use a syntax more typically associated with "extends" or "implements" or "is a".
> 
> Would it be less confusing if the type of the property were implicit? In discussion with Brent, I suggested a model where you say:
> 
> var behavior foo { ... }
> 
> and if you want to constrain the types of properties that can instantiate the behavior, you use a where clause:
> 
> var behavior foo where Value: NSCopying { ... }
> 
> which optimizes the common case (no constraint), and might be easier to read.
> 
>> Questions:
>> 
>> Can a behavior have generic parameters that can't be inferred? Could I write, say, [fooable<Int>]?
> 
> No, the generic parameters are only used to generalize the property type.

Given this, I think that it makes sense to make the generics and remove the `: Value` (without the obvious downside that Value and Self are less discoverable).

> 
>> What is the tradeoff between `eager` and `deferred`? Is it "only" that `deferred` side effects happen at the mercy of the behavior?
>> If so, isn't it a problem that behaviors aren't intrinsically explicit about whether they defer initialization? I can see that causing very subtle bugs.
> 
> The tradeoff is that an 'eager' initialization can be used in `init`, but that means that an initializer expression can't refer to `self`, because `self` is not fully initialized. This is how initializer expressions always work today:
> 
> struct X {
>   var x = 0, y = 1
>   var z = x + y // Error
> }
> 
> A deferred initialization can only be evaluated *after* init, but because of that, it can refer to `self`, which people would like to be able to do with `lazy` (but currently can't):
> 
> struct X {
>   var x = 0, y = 1
>   lazy var z = x + y // Theoretically OK
> }

Got it. Still, can it be a problem that it might not be obvious whether a behavior defers initialization or not, in terms of side effects?

Also, some behaviors (like resettable) use `initializer` multiple times. Are the side effects evaluated each time? That seems like a bad idea to me, but it does mean that `initializer`'s value would need to be stored otherwise.

>> Concerns:
>> 
>> It looks like if you had a [resettable, observable] property, calling resettable.reset() would change the value from under `observable`'s feet.
> 
> True. An unfortunate consequence of these things being user-defined is that there will always be "wrong" orderings of them. I'm not sure how much we can do about that.

In this case, it's problematic that resettable simply can't be ordered after observable because it has a base property.

Would it make sense to have a warning if a behavior down the chain has mutating functions? (Either at the declaration or at the mutating call site)

Speaking of which, `lazy`'s `get` accessor isn't marked as mutating in the proposal. Is it on purpose?

Thanks for your work on this.

Félix

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160113/75fec303/attachment.html>


More information about the swift-evolution mailing list