[swift-evolution] [Proposal] Property behaviors

Matthew Johnson matthew at anandabits.com
Fri Jan 15 10:08:54 CST 2016


> On Jan 15, 2016, at 8:42 AM, plx via swift-evolution <swift-evolution at swift.org> wrote:
> 
> One more “how will this work?” question: optionals.
> 
> Specifically, consider something like this:
> 
> // annoyingly-long protocol:
> protocol ControlExchanging {
>   typealias Context
>   func willTakeControlFrom(other: Self?, context: Context)
>   func didTakeControlFrom(other: Self?, context: Context)
>  
>   func takeControl(context: Context)
>   func cedeControl(context: Context)
>   
>   func willCedeControlTo(other: Self?, context: Context)
>   func didCedeControlTo(other: Self?, context: Context)
> }
> 
> var behavior exchangeState<Value:ControlExchanging where Self:Value.Context> : Value {
>   var value: Value
>   // here:
>   set { 
>     let oldValue = value
>     // boilerplate-choreography begins:
>     newValue.willTakeControlFrom(oldValue, context: self)
>     oldValue.willCedeControlTo(newValue, context: self)
>     oldValue.cedeControl(self)
>     value = newValue
>     newValue.takeControl(self)
>     oldValue.didCedeControlTo(newValue, context: self)
>     newValue.didTakeControlFrom(oldValue, context: self)
>   }
> }
> 
> // numerous extraneous details omitted:
> class GenericSwitchboard<Delegate:ControlExchanging were Delegate.Context == Self> {
> 
>   private(set) weak var [exchangeControl] delegate: Delegate? = nil
> 
> }
> 
> …which presumably won’t actually work unless I’ve either:
> 
> - added an additional implementation that’s typed-as `Value?` 
> - added a conditional-conformance for `ControlExchanging` to `Optional` (easy, but boilerplate)
> 
> ….both of which are workable, neither of which feels optimal (and for the latter, consider also that in many cases such conformances may generally be undesirable).
> 
> Is there a trick/detail I’m missing here?
> 
>> On Jan 14, 2016, at 4:05 PM, Joe Groff <jgroff at apple.com <mailto:jgroff at apple.com>> wrote:
>> 
>> Good point. Longer behavior compositions are something to consider. If behaviors have dedicated declarations instead of being applications of plain types or functions, it also becomes more reasonable to let them be used as attributes by themselves:
>> 
>> @mainThread @resettable @logged @changeObserved
>> public var foo: Int { ... }
>> 
>> I shied away from that design originally because it would be problematic to pollute the attribute namespace with potentially every function and/or type. That's less of a problem with this design.
> 
> I still like the aesthetics of your design better (until maybe when things get too long).
> 
> It might be a good general policy for proposals with syntax changes to always include at least one “heavy-duty/extreme” example.
> 
> Another argument in favor of a different positioning: it makes these kinds of things easier to write:
> 
> @resettable @changeObserved
> #if DEBUG
> @mainThread @onlyAfterViewLoaded @logged 
> #endif
> 
> …(which may or may not be a good thing).

Would it be possible to move the "behavior list” prior to decl modifiers without needing to use attributes?  The square brackets might not work in that case but maybe something else?  

I like the positioning but don’t like all the @ sigils and the fact that the behaviors would be potentially intermixed with other attributes.  I also like that the square brackets in property declaration syntax makes it very clear when behaviors are involved.

> 
>>> ## Request: Declared-Property-Name
>>> 
>>> If possible, a property analogous to `self` / `parent` / etc. getting at a behavior’s concrete “declared-name” would be amazing, although I know it’s not quite that easy, either (see @objc questions later).
>> 
>> That's definitely an interesting extension to consider. I think it can be separated from the core proposal, though.
> 
> Agreed it is an extension; consider it in the spirit of “if a bunch of other such things get added, perhaps this should be too”.
> 
>> 
>>> ## Semantics: Overrides + Behaviors
>>> 
>> 
>> I would expect each override's [changeObserved] behavior to wrap the previous override, as happens if you override with `didSet`/`willSet` today. You raise the interesting question of what happens with the behavior names, since you do end up with two same-named behaviors applied at different times. That potentially requires deeper qualification by type; if we use Tal's suggested `.[behavior]` qualification syntax, you could refer to `.[BaseView.changeObserved]` and `.[RefinedView.changeObserved]` if you needed to.
> 
> The behavior is what I’d expect, but it seems that that behavior will be an occasional source of subtle bugs in scenarios like this:
> 
> class Simple : Synchronizing {
>    var [changeObserved,synchronized] delicate: Delicate
> }
> 
> class Fancy : Simple {
>   override var [changeObserved] delicate: Delicate {
>     willSet { /* consider timing vis-a-vis base's `synchronized` */ }
>     didSet { /* consider timing vis-a-vis base's `synchronized` */ }
>   }
> }
> 
> …but I’m note sure there’s an easy way to make property behaviors flexible-enough to solve this problem purely at the property-behavior level; especially when it is easy in this sort of circumstance (and arguably better) to just write appropriate hooks into your class, and override from there.
> 
>> 
>>> ## Semantics: Redundancy/“Static” Parameterization
>>> 
>> 
>> It seems to me you could factor at least some of this boilerplate into a behavior, reducing it to:
>> 
>> var [invalidate] foo: String { invalidates { return [.Display, .ContentSize] } }
>> 
>> Brent mentioned in the other thread about modeling `lazy` with an accessor some possibilities to whittle this down further, by allowing for implicit single-expression return in accessors, and inferring behaviors from accessor names:
>> 
>> var foo: String { invalidates { [.Display, .ContentSize] } }
> 
> That’s a really nice trick and makes this a proposal a lot more feature-complete for me than I had realized, particularly if the implicit-return suggestion is taken.
> 
>> 
>>> ## ObjC Interaction
>>> 
>> 
>> You could potentially declare another @objc property that forwards to the stored property, though that's even less ideal of course:
>> 
>> var permitted: Bool
>> private var _objcPermitted: Bool {
>>   @objc(isPermitted) get { return _permitted }
>>   @objc(setPermitted:) set { _permitted = newValue }
>> }
>> 
>> Behaviors definitely exacerbate this issue, since you have more interesting properties without native get/set accessors that you may want to control the ObjC interface for. The private @objc trick above at least still works.
> 
> That’s another really nice way to do it. But, I’ll take this reply to mean an improvement to @objc-renaming is independent of this proposal, which is fine.
> 
> Overall this revision of the proposal is looking really nice.
> 
>> 
>> -Joe
>> 
>>> 
>>> On the one hand, this proposal doesn’t seem to change this situation.
>>> 
>>> On the other hand, if it can be changed, this seems like a reasonable time/place to do it.
>>> 
>>> That’s it for the moment.
>>> 
>>> With this proposal it seems like a really nice feature to have.
>>> 
>>>> On Jan 13, 2016, at 8:04 PM, Joe Groff <jgroff at apple.com <mailto:jgroff at apple.com>> wrote:
>>>> 
>>>> 
>>>>> On Jan 13, 2016, at 5:12 PM, plx via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>> 
>>>>> Quick Q1: is it the case that `var behavior redrawing<Value where Self:UIView> : Value { … } ` would work or no? I’d assume so, but I don’t think there are any examples with a non-protocol constraint on `Self`, making it hard to tell at a glance.
>>>> 
>>>> Yeah, you should be able to use arbitrary generic constraints.
>>>> 
>>>>> 
>>>>> Quick Q2: is there anything you can do in this scenario:
>>>>> 
>>>>> // w/in some behavior:
>>>>> mutating func silentlySet(value: Value) {
>>>>> value = value // <- probably not going to work
>>>>> self.value = value // <- not right either, `self` is the behavior’s owner, right?
>>>>> }
>>>>> 
>>>>> …other than change the argument name to avoid conflict?
>>>> 
>>>> I thought I mentioned this in the proposal—you could use `behaviorName.value` to qualify a reference to the behavior's members within the behavior.
>>>> 
>>>>> 
>>>>> Remark: it definitely feels a bit odd to be using both `Self` and `self` to mean something that’s neither consistent with the rest of the language nor, really, to mean `Self` (or `self`). 
>>>>> 
>>>>> I get not wanting new keywords, but this feels like it could be an economy too far; perhaps I’m misunderstanding some aspect of how it’s meant to work.
>>>> 
>>>> I'm not totally comfortable with it either. It at least corresponds to the notion of `self` you'd get if you'd coded a property by hand within its enclosing type, so the meaning might be useful for refactoring code out of concrete property implementations into behavior templates.
>>>> 
>>>> -Joe
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
> 
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

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


More information about the swift-evolution mailing list