[swift-evolution] [Proposal] Property behaviors
Joe Groff
jgroff at apple.com
Wed Jan 13 23:22:40 CST 2016
> On Jan 13, 2016, at 8:44 PM, Félix Cloutier <felixcca at yahoo.ca> wrote:
>
>
>> Le 13 janv. 2016 à 23:08:24, Joe Groff <jgroff at apple.com <mailto: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).
One benefit of making the container binding explicit is that we can avoid referencing the container `inout` in behavior implementations if it isn't used (and we're willing to say that a behavior can't resiliently adopt a container requirement if it wasn't originally written with one).
>
> 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.
The initializer expression and accessors should manifest themselves as methods on the containing type. There shouldn't be any implicit per-instance storage overhead to using behaviors, and the abstraction should be optimizable by inlining and generic specialization in optimized builds.
>
>>
>>> 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.
It occurs to me that a bound `initializer` is in a sense a lot like a sugared accessor. Both `lazy` and `resettable` could be designed in terms of accessor requirements, at the cost of some sugar, and some redundancy in the case of `resettable`:
var behavior lazy {
var value: Value?
accessor initialValue() -> Value
mutating get {
if let value = value { return value }
let val = initialValue()
value = val
return val
}
}
var [lazy] x: Int {
initialValue { return 679 }
}
var behavior resettable {
base var value: Value
accessor resetValue() -> Value
mutating func reset() {
value = resetValue
}
}
var [resettable] y: Int = 1738 {
resetValue { return 1738 }
}
If we could live with that, we could avoid the complexity of the deferred/eager initializer, and `resettable` could also be made composable by 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)
Not sure what you mean, exactly. Seems like that would warn on a lot of legitimate use cases.
> Speaking of which, `lazy`'s `get` accessor isn't marked as mutating in the proposal. Is it on purpose?
Another oversight; thanks!
-Joe
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160113/ffd989ee/attachment.html>
More information about the swift-evolution
mailing list