[Proposal] Property behaviors

Good point! I think making behaviors "zero-cost“ is a great idea!

-Thorsten

···

Am 25.01.2016 um 19:25 schrieb Joe Groff <jgroff@apple.com>:

On Jan 24, 2016, at 1:16 AM, Thorsten Seitz <tseitz42@icloud.com <mailto:tseitz42@icloud.com>> wrote:

Am 23.01.2016 um 20:30 schrieb Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>>:

On Jan 23, 2016, at 8:14 AM, Thorsten Seitz <tseitz42@icloud.com <mailto:tseitz42@icloud.com>> wrote:

Am 23.01.2016 um 01:33 schrieb Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

changing the proposed syntax for behavior member lookup to 'property.[behavior].member', as suggested by Tal Atlas and others. I think this has a nice symmetry with the `var [behavior]` declaration syntax, and doesn't occupy any new sigils. On the other had, Curt Clifton and others have raised the point that it could be mistaken for a subscript at a glance.

As a minor issue I’m not very fond of the behavior member lookup syntax because of the similarity to a subscript. The symmetry with the declaration syntax will break down a little bit as soon as the declaration syntax allows a list of composed behaviors.

To reduce the complexity of the initial feature review, I've also removed many of the bells and whistles from this initial version for separate consideration. For now, I'm saying initializer requirements are always "eager" (can't refer to self) and always inline-initialized. This imposes some inconvenience on some use cases, but is an additive feature. I've also left behavior composition, extending behaviors, overloading behaviors and […] as future extensions

I think behavior composition should be included right from the beginning as this might require breaking changes otherwise.

Practically speaking, I think there are inevitably going to be breaking changes here once we get some experience with the feature.

That’s a fair point :-)

It won't ship until 3.0 at the earliest. Many of the other features I've cut, including the initialization generalizations and extensions, would also be ABI-breaking changes at the implementation level.

Ok, thanks! But doesn’t this collide with the goal of Swift 3.0 of a stable ABI?

I hope we'll have the feature stabilized by then, of course. However, it's a strong goal for this feature to be "zero-cost" with little or no runtime footprint. If we're successful at that, there should be more or less no ABI impact, since behaviors would just be compile-time sugar.

Finally caught up. :-) I like this latest version better than the two previous versions, but I kind of think that if behaviors can't emulate "lazy", we've failed. Nothing has a stronger motivation than that at this point in time; there's lots of cool things we could use behaviors for, but you can still write all of them with a wrapper type.

Not sure what you mean. It can.

But you have this in your other message:

For now, I'm saying initializer requirements are always "eager" (can't refer to self) and always inline-initialized.

Is it really just "can't refer to self" and not actually "eager"?

`lazy var`s still can't refer to self today AFAIK (I know we tried to fix that, but we still get plenty of radars saying it doesn't work…), so I don't think it would be a regression for a behavior implementation not to immediately be able to either. (And I'm not proposing we immediately replace our existing 'lazy' implementation immediately either.)

- Someone else brought this up, but can I override behavior methods in a subclass with a new behavior? Can I invoke super's implementation of those behavior methods?

- Can I wrap a superclass property with a behavior? This version of the proposal doesn't have 'base', so I assume the answer is no, but that knocks out the other special-case language feature that was to be replaced by the general feature.

We're not done yet; this is just version 1. I'm subsetting composition out, since that's also an interesting discussion unto itself. You ultimately ought to be able to wrap a superclass property in new behaviors, but I don't see how you could replace a behavior without violating the superclass's encapsulation. Chaining to super behavior members also feels a bit overly intimate; do you have a use case in mind?

willSet or didSet wrapping a superclass setter is not at all uncommon, and the sort of things you wrap with didSet are usually things that want to know about reset() as well.

That's another interesting composition dilemma—if you wrap a property in observers in a subclass, then you'd probably want the base class's 'resettable' behavior to trigger the subclass's observers.

-Joe

···

On Jan 25, 2016, at 8:15 PM, Jordan Rose <jordan_rose@apple.com> wrote:

On Jan 25, 2016, at 18:54 , Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 25, 2016, at 6:40 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

I think there’s value for users in being able to group and scope the components associated with a particular behavior, so IMO it’s worth it. Overall, it makes usage of the language less complex in practice.

I tend to agree. There is definite value in having really independent things scoped out and cordoned off in their own areas.

On balance I like it too. Going with a behavior decl opens some questions though:

- Can behaviors be extended?

From an implementation perspective, I’d prefer not. From a user model perspective (which is what really matters :) we want to be able to add methods to them somehow. I think it would be fine to require more boilerplate for this (e.g. the backing store for lazy is actually a struct, and that struct is what gets the extension) - so long as it doesn’t cause boilerplate on the client side. You shouldn’t have to say myProperty.lazy.backingStore.clear(), just myProperty.lazy.clear().

I don’t know where that leaves us :-)

- Can behaviors be resilient? One nice thing about a fragile behavior is that we can inline its storage, if any, directly into its containing type without having to instantiate metadata for a nominal type, as we would for a struct-based property implementation. A resilient behavior, however, would end up needing more or less the same metadata to encapsulate the layout of the behavior's state behind the resilience domain, weakening that benefit.

I’d be fine with requiring an explicit struct to be defined inline in the behavior to get resilience.

- Should behaviors be able to control their default visibility policy? As Brent and others pointed out, most behaviors are implementation details, but the few that make sense as API generally always want to be API, such as `resettable` or `KVOable`.

I’d suggest defining them to be private by default, and allowing the "var (public lazy)” sort of syntax. After the basic proposal and model is done, we can then talk about adding a “public_by_default” trait to behavior to reduce boilerplate (if it is an issue in practice).

-Chris

···

On Dec 22, 2015, at 9:08 AM, Joe Groff <jgroff@apple.com> wrote:

That parameterization could be achieved, somewhat more verbosely, using an accessor 'key { return "id" }'. If behaviors could be parameterized in-line, I wouldn't do it by passing the params to `init`, since that again imposes the need for per-instance storage to carry `key` from the behavior initialization to the property implementation.

Thinking about this more, maybe the need for instance storage could be avoided by letting behaviors declare `static` properties. It's likely that behavior instantiations will need some global data structure, at least in debug builds, to collect their parameterizations from property decls using the behavior. It'd make sense to put some of that global structure under the behavior's control. We'd still potentially need a separate `static init()` to initialize that storage once per declaration, rather than once per instance.

That's really, really, really clever. (Of course, it also means that a property will need to have some kind of existence on the type so we can attach static behaviors to it.)

I have to say, this feature of behaviors where "it's not really a type, it's a template for a bunch of stuff that gets added to your type" is a bit mind-bending. Expect to do a lot of writing in the Swift book to explain it!

This particular use case for mapped serialization/deserialization is interesting. A potential future extension to better support it might be to let behaviors optionally bind the name of a property using them as a `String` (or other `StringLiteralConvertible` type).

Yes, I'd like that too. `key ?? propertyName` would be quite handy here.

···

--
Brent Royal-Gordon
Architechies

Joe, your idea for initializer as an accessor makes sense to me. Also, it seems that if resettable's initializer isn't delayed, we don't need the redundancy since it can set the base value from its own init:

public var behavior resettable /* whatever goes here */ {
  eager accessor resetValue(): Value
  var value: Value = resetValue()
}

var [resettable] foo: Int { resetValue { return 42 } }

Even if I made it up just to support this case, I think that allowing `eager` and `delayed` on accessors might be a good idea. Right now, there appears to be no way to enforce that observed's willSet and didSet do not fire during property initialization. Marking them "delayed" could mean that.

I would support using "initializer" as a special accessor name that refers to the initializer value of the property (parsed as an autoclosure, mapping to whatever behaviors use behind the scenes). Having "delayed accessor"/"eager accessor" in front of it would make it much less magical in my opinion.

public var behavior resettable /* whatever goes here */ {
  eager accessor initializer(): Value
  var value: Value = initializer()
}

var [resettable] foo: Int = 42

Chiming in with plx:

## 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).

I also think that this is an interesting idea. In fact, when reflection details are better specified (I think that it's in scope for Swift 3, right?), I think that behaviors should have access to the property's "mirror".

## Semantics: Overrides + Behaviors

I think this is probably specified somewhere and I just don’t understand it, but I need to ask: how do overrides work for things like the following example:

Just like right now, I think that behaviors should be completely opaque to subclasses. This means you can't alter their behavior (unless they do a virtual call) and you're possibly not even aware that they have behaviors attached.

…but if you “expand” what happens within these behaviors, once you have multiple such behaviors in a chain (e.g. `[redraw, invalidateSize]`) you will of course have one `!=` comparison per behavior. Note that although, in this case, `!=` is hopefully not too expensive, you can also consider it as a proxy here for other, possibly-expensive operations.

My personal hope is that the accessors will compose and optimize as nicely as short private funcs. I wonder how that would play out with behaviors taken from other modules though.

## ObjC Interaction

I think that this is an issue with @objc. I agree that it should get the name right for boolean properties.

Now going back to a warning when a behavior can alter the property without other behaviors noticing.

We have a pretty small sample size of behaviors, but I predict that it will always be a problem when a property value is set without the whole chain being aware of it. For instance, [resettable, observed] is as much a problem as [resettable, backedByJSON]. Using resettable's reset method bypasses behavior that was deemed useful (or even necessary) by the developer in these two cases. In fact, I would like to challenge you to think of a behavior B with a setter, in a chain [resettable, B], where `reset` bypassing B's setter is what the developer intends.

I think that either one of these should be implemented:

warn when using a mutating function on a behavior when "outer behaviors" have setters;
ensure that mutating functions send their result value down the behavior chain like a normal assignment (not sure how feasible this is with behaviors that have state).

Félix

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?

No, I think you've got it. This seems like a general problem to me, though; it'd be nice if protocol conformances could be easily forwarded, for instance from Optional<T> to T.

Any thoughts about how that might work when requirements have a return value? Or are you just referring to forwarding conformances when the protocol doesn’t have members with return values?

···

On Jan 15, 2016, at 7:45 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 15, 2016, at 6:42 AM, plx via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 14, 2016, at 4:05 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@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).

I don't think this is currently allowed with attributes today, since the content of if blocks is still parsed as full statement/expression/declarations.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Thanks everyone for the first round of feedback on my behaviors proposal. I've revised it with the following changes:

- Instead of relying on mapping behaviors to function or type member lookup, I've introduced a new purpose-built 'var behavior' declaration, which declares the accessor and initializer requirements and provides the storage and behavior methods of the property. I think this gives a clearer design for authoring behaviors, and allows for a more efficient and flexible implementation model.
- I've backed off from trying to include 'let' behaviors. As many of you noted, it's better to tackle immutable computed properties more holistically than to try to backdoor them in.
- I suggest changing the declaration syntax to use a behavior to square brackets—'var [behavior] foo'—which avoids ambiguity with destructuring 'var' bindings, and also works with future candidates for behavior decoration, particularly `subscript`.

Syntax comments:

I still think these feel attribute-like to me, but if we’re not just going to use @lazy — and I agree that that does have some problems —I’m fine with [lazy].

I'm OK with using attribute syntax alongside the declaration approach.

"var behavior" is really weird to me, and the <T> doesn’t seem to fit and is pretty redundant in the common case. How about this:

  "behavior" var-or-let "[" identifier-list "]" (identifier | "_") ":" identifier ("=" identifier)? ("where" generic-requirement-list)?

So, for example,
  behavior var [lazy] _ : T where T : IntegerLiteralConvertible { … }

This is definitely taking the idea of “this is basically a macro” and running with it. Think of the stuff between “behavior” and the optional “where” as being a pattern for the declaration. So this pattern would match:
  var [lazy] x: Int
but not:
  let [lazy] x: Int
or:
  var [lazy] x : Int = foo()

Good idea, I like this approach. However:

The behavior list has to match exactly (or maybe as sets?).

Are you saying that there would be no ad-hoc composition of behaviors? This seems to imply that you'd need to implement every valid combination of behaviors by hand. That's a defensible position, given that it's easy to compose behaviors like "synchronized" in the wrong order, but significantly stifles behaviors like didSet/willSet that are more likely to be order-agnostic.

My first instinct is to say that ad-hoc composition is too treacherous to include in the first model, yeah.

I like the idea of having a model that works for literally everything that’s not pure-computed or pure-stored, but it seems tolerable to continue to build in things like willSet / didSet if it significantly simplifies the problem. willSet / didSet have some pretty custom behavior and dependencies on the container. OTOH, maybe that kind of thing is a core requirement for some of the stuff we’re thinking of doing.

I think you can define a reasonable facsimile of willSet/didSet under my proposal, but I'm willing to believe there are edge cases I'm missing. What sort of custom behavior do you have in mind?

Well, for one, we only load the old value if the didSet is actually declared. There’s also the longstanding idea — apparently still not implemented — that we’d only do so if didSet actually used the old value. The ideal result here really needs macro metaprogramming, I think, unless you’re going to get very sophisticated about accessor declarations.

One of the nice things about last month's function-based proposal was that it allowed overloading a behavior, which could allow for the right variation of behavior to be chosen given different accessors. We could possibly make this work for behavior decls too, treating the set of accessor requirements as the "signature" of a behavior. Even without overloading, we could separate 'didSet' and 'willSet' into separate behaviors that can be optionally composed.

One of my worries about this proposal in general is that you’ve listed out half-a-dozen different uses and every single one seems to require a new twist on the core semantics.

Yeah, that worries me too. I think though that these twists are sugar over the basic functionality, though. Fundamentally we want a way to factor out one or both of two things:

- The storage that gets instantiated for a property, and/or
- The get/materializeForSet/set implementations that get run for a property.

Most of the accessor and initializer complexity is essentially sugar for parameterizing the behavior. Many in this thread have proposed allowing direct parameterization of the behavior like 'var [behavior(param)]'; if the behavior could allocate and initialize static storage to capture those parameters, you could do everything you can with accessors or bound initializers (modulo "only" syntactic things like type-inferencing properties from initializer expressions).

-Joe

···

On Jan 19, 2016, at 6:38 PM, John McCall <rjmccall@apple.com> wrote:

On Jan 19, 2016, at 5:47 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 19, 2016, at 4:28 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 19, 2016, at 3:10 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 19, 2016, at 2:46 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 13, 2016, at 2:07 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

John.

-Joe

The property name, if bound, expands to a string literal within the behavior.

The type name is always a generic parameter. This interferes with the ability to make a pattern that only matches a concrete type, but I think that’s okay.

Seems reasonable, since unconstrained behaviors are likely to be the 95% case. Being able to match concrete types is something we ought to be able solve uniformly with the same limitation on constrained extensions.

Yeah.

The initializer name, if bound, expands to the original expression within the behavior. Maybe it should be coerced to type T first? Not sure.

Yeah, JoeP brought up a good question about how 'var' type inference should work with initializer expressions. There are two possible models I can see:

- We infer the type of the initializer independent of any applied behaviors, and raise an error if the behavior can't be instantiated at the given type.
- We add generic constraints from the behavior declaration(s) to the contextual type of the initializer.

In support of the latter approach, 'weak' properties currently factor their Optional constraint into type inference ('weak var foo = Foo()' gives you a property of type Foo?), and 'weak' has been raised as a candidate for eventual behavior-ization. The downside, of course, is that with arbitrary user-defined behaviors with arbitrary generic constraints, there's yet another source of potential surprise if the type context of behaviors changes the type-checking of an expression.

Yeah, especially because the initializer could be used in multiple places in the behavior. Coercing the initializer seems a lot less surprising.

John.

This is pretty much what I mean, and what the [delayed] example from the proposal provides. Matthew's making a case for a more constrained form of 'delayed' that still prevents you from exiting 'init' without having initialized everything.

-Joe

···

On Jan 20, 2016, at 3:17 PM, Chris Lattner <clattner@apple.com> wrote:

On Jan 20, 2016, at 2:08 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

One thought is that it might be good to allow behaviors to have `init` accessor that is used if the property is assigned by an initializer of the containing type (only the first time the property is assigned). This would clarify what happens during initialization of the containing type and allow for different init and set code paths when necessary. It would be distinguished from the behavior initializer by the lack of parens. If that is too subtle we could use a different name for the initialization accessor.

That's a possibility, but inserting initializer calls is a bit more heroic than what our current definite initialization implementation can do. That's not necessarily a showstopper, but it was a goal of the current design to avoid inserting accessor calls within inits at hard-to-predict places. (cc'ing ChrisL, to get his opinion on this as DI owner)

Makes sense. Getting the behavior I would like to see out of a `phase2Immutable` behavior would definitely require updates to the DI rules. I think the added safety makes it worth doing something in this area eventually, but it could be added later. I’m interested to hear what Chris thinks.

I think it's important that delayed initialization also accommodate initialization *after* init() returns. There are many cases where full initialization depends on a process outside of init's control, especially in Cocoa with objects and outlets set up from nib files.

I don’t really consider that to be initialization in the current sense. IMO, without extending our DI model, a “delayed” behavior for a property of type T is best modeled with storage for T?, which is implicitly initialized to nil. The getter would do a force unwrap of the optional, and the setter would check that the storage is nil before assigning over it.

If/when we have support for a more dynamic initialization (e.g. dynamic typestate) then we could expand this to use it, but I don’t see how it would provide a functionally different model.

That's my general feeling too about this proposal. I just didn't know how to express what you said above.

To me this proposal feels like it's is about trying to find a solution to multiple problems at once. A new problem arise that looks like it could be solved by a behaviour, so the behaviour feature expands to accommodate it. It looks like the wrong approach to me.

The correct approach in my opinion would be to try to make various parts of this proposal standalone, and allow them to combine when it makes sense. For instance, if you wanted to define a standalone feature for defining custom accessors that can be used everywhere, you wouldn't come with something that requires a behaviour annotation at the variable declaration. You'll come with something simpler that might looks like this:

custom_acccessor willSet<T>(newValue: T) { // define a custom accessor...
  set { // ... by redefining the setter...
    willSet(newValue) // ...inserting a call to the accessor here...
    currentValue = newValue // ...before calling the underlying setter
  }
}
custom_acccessor didSet<T>(oldValue: T) {
  set {
    let oldValue = currentValue
    currentValue = newValue
    didSet(oldValue)
  }
}
custom_acccessor willChange<T>(newValue: T) {
  willSet {
    if currentValue != newValue {
      willChange(newValue)
    }
  }
}

Then at the declaration point you just directly use the globally accessible accessor:

  var myvar: Int {
    willChange { print("will change to \(newValue)") }
  }

This fulfills at least one of the use cases. Can't we do the same treatment to each proposed use cases and see if there are other parts that can stand on their own?

···

Le 19 janv. 2016 à 21:38, John McCall via swift-evolution <swift-evolution@swift.org> a écrit :

One of my worries about this proposal in general is that you’ve listed out half-a-dozen different uses and every single one seems to require a new twist on the core semantics.

--
Michel Fortin
https://michelf.ca

They can; it just requires a type annotation.

class C {
    lazy var b: B = B(c: self)
}

struct B {
    let c: C
}

···

On Tue, Jan 26, 2016 at 9:14 AM, Joe Groff via swift-evolution < swift-evolution@swift.org> wrote:

`lazy var`s still can't refer to self today AFAIK (I know we tried to fix
that, but we still get plenty of radars saying it doesn't work…), so I
don't think it would be a regression for a behavior implementation not to
immediately be able to either. (And I'm not proposing we immediately
replace our existing 'lazy' implementation immediately either.)

You shouldn’t have to say myProperty.lazy.backingStore.clear(), just myProperty.lazy.clear().

I don’t know where that leaves us :-)

I wonder if these should all just be flattened into the instance that owns the property. So instead of saying foo.myProperty.lazy.clear(), you just say foo.clearMyProperty() or some such. That approach isn't very principled, but it *is* very convenient.

···

--
Brent Royal-Gordon
Architechies

I don't have a great answer in mind, unfortunately. You're right that it only really makes sense for "sink"-like protocols, where none of the requirements produce a value, or theoretically for protocols where all results are of associated types that could be optionalized. The sink protocol use case comes up all the time, though, especially with delegates and callbacks, and is one the places where ObjC's nil-messaging behavior feels legitimate.

-Joe

-Joe

···

On Jan 15, 2016, at 5:54 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 15, 2016, at 7:45 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 15, 2016, at 6:42 AM, plx via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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?

No, I think you've got it. This seems like a general problem to me, though; it'd be nice if protocol conformances could be easily forwarded, for instance from Optional<T> to T.

Any thoughts about how that might work when requirements have a return value? Or are you just referring to forwarding conformances when the protocol doesn’t have members with return values?

Thanks everyone for the first round of feedback on my behaviors proposal. I've revised it with the following changes:

- Instead of relying on mapping behaviors to function or type member lookup, I've introduced a new purpose-built 'var behavior' declaration, which declares the accessor and initializer requirements and provides the storage and behavior methods of the property. I think this gives a clearer design for authoring behaviors, and allows for a more efficient and flexible implementation model.
- I've backed off from trying to include 'let' behaviors. As many of you noted, it's better to tackle immutable computed properties more holistically than to try to backdoor them in.
- I suggest changing the declaration syntax to use a behavior to square brackets—'var [behavior] foo'—which avoids ambiguity with destructuring 'var' bindings, and also works with future candidates for behavior decoration, particularly `subscript`.

Syntax comments:

I still think these feel attribute-like to me, but if we’re not just going to use @lazy — and I agree that that does have some problems —I’m fine with [lazy].

I'm OK with using attribute syntax alongside the declaration approach.

"var behavior" is really weird to me, and the <T> doesn’t seem to fit and is pretty redundant in the common case. How about this:

  "behavior" var-or-let "[" identifier-list "]" (identifier | "_") ":" identifier ("=" identifier)? ("where" generic-requirement-list)?

So, for example,
  behavior var [lazy] _ : T where T : IntegerLiteralConvertible { … }

This is definitely taking the idea of “this is basically a macro” and running with it. Think of the stuff between “behavior” and the optional “where” as being a pattern for the declaration. So this pattern would match:
  var [lazy] x: Int
but not:
  let [lazy] x: Int
or:
  var [lazy] x : Int = foo()

Good idea, I like this approach. However:

The behavior list has to match exactly (or maybe as sets?).

Are you saying that there would be no ad-hoc composition of behaviors? This seems to imply that you'd need to implement every valid combination of behaviors by hand. That's a defensible position, given that it's easy to compose behaviors like "synchronized" in the wrong order, but significantly stifles behaviors like didSet/willSet that are more likely to be order-agnostic.

My first instinct is to say that ad-hoc composition is too treacherous to include in the first model, yeah.

I like the idea of having a model that works for literally everything that’s not pure-computed or pure-stored, but it seems tolerable to continue to build in things like willSet / didSet if it significantly simplifies the problem. willSet / didSet have some pretty custom behavior and dependencies on the container. OTOH, maybe that kind of thing is a core requirement for some of the stuff we’re thinking of doing.

I think you can define a reasonable facsimile of willSet/didSet under my proposal, but I'm willing to believe there are edge cases I'm missing. What sort of custom behavior do you have in mind?

Well, for one, we only load the old value if the didSet is actually declared. There’s also the longstanding idea — apparently still not implemented — that we’d only do so if didSet actually used the old value. The ideal result here really needs macro metaprogramming, I think, unless you’re going to get very sophisticated about accessor declarations.

One of the nice things about last month's function-based proposal was that it allowed overloading a behavior, which could allow for the right variation of behavior to be chosen given different accessors. We could possibly make this work for behavior decls too, treating the set of accessor requirements as the "signature" of a behavior. Even without overloading, we could separate 'didSet' and 'willSet' into separate behaviors that can be optionally composed.

One of my worries about this proposal in general is that you’ve listed out half-a-dozen different uses and every single one seems to require a new twist on the core semantics.

Yeah, that worries me too. I think though that these twists are sugar over the basic functionality, though. Fundamentally we want a way to factor out one or both of two things:

- The storage that gets instantiated for a property, and/or
- The get/materializeForSet/set implementations that get run for a property.

Most of the accessor and initializer complexity is essentially sugar for parameterizing the behavior. Many in this thread have proposed allowing direct parameterization of the behavior like 'var [behavior(param)]'; if the behavior could allocate and initialize static storage to capture those parameters, you could do everything you can with accessors or bound initializers (modulo "only" syntactic things like type-inferencing properties from initializer expressions).

That’s reasonable, but it’s definitely treading on future directions for hygienic macro definition. I think the current syntax leaves plenty of room for that.

It’d be a pretty awkward way of doing willSet / didSet, though.

John.

···

On Jan 20, 2016, at 9:19 AM, Joe Groff <jgroff@apple.com> wrote:

On Jan 19, 2016, at 6:38 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 19, 2016, at 5:47 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 19, 2016, at 4:28 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 19, 2016, at 3:10 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 19, 2016, at 2:46 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 13, 2016, at 2:07 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

-Joe

John.

-Joe

The property name, if bound, expands to a string literal within the behavior.

The type name is always a generic parameter. This interferes with the ability to make a pattern that only matches a concrete type, but I think that’s okay.

Seems reasonable, since unconstrained behaviors are likely to be the 95% case. Being able to match concrete types is something we ought to be able solve uniformly with the same limitation on constrained extensions.

Yeah.

The initializer name, if bound, expands to the original expression within the behavior. Maybe it should be coerced to type T first? Not sure.

Yeah, JoeP brought up a good question about how 'var' type inference should work with initializer expressions. There are two possible models I can see:

- We infer the type of the initializer independent of any applied behaviors, and raise an error if the behavior can't be instantiated at the given type.
- We add generic constraints from the behavior declaration(s) to the contextual type of the initializer.

In support of the latter approach, 'weak' properties currently factor their Optional constraint into type inference ('weak var foo = Foo()' gives you a property of type Foo?), and 'weak' has been raised as a candidate for eventual behavior-ization. The downside, of course, is that with arbitrary user-defined behaviors with arbitrary generic constraints, there's yet another source of potential surprise if the type context of behaviors changes the type-checking of an expression.

Yeah, especially because the initializer could be used in multiple places in the behavior. Coercing the initializer seems a lot less surprising.

John.

Ah ok. Well, such a thing could be built.

-Chris

···

On Jan 20, 2016, at 3:19 PM, Joe Groff <jgroff@apple.com> wrote:

On Jan 20, 2016, at 3:17 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:

On Jan 20, 2016, at 2:08 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

One thought is that it might be good to allow behaviors to have `init` accessor that is used if the property is assigned by an initializer of the containing type (only the first time the property is assigned). This would clarify what happens during initialization of the containing type and allow for different init and set code paths when necessary. It would be distinguished from the behavior initializer by the lack of parens. If that is too subtle we could use a different name for the initialization accessor.

That's a possibility, but inserting initializer calls is a bit more heroic than what our current definite initialization implementation can do. That's not necessarily a showstopper, but it was a goal of the current design to avoid inserting accessor calls within inits at hard-to-predict places. (cc'ing ChrisL, to get his opinion on this as DI owner)

Makes sense. Getting the behavior I would like to see out of a `phase2Immutable` behavior would definitely require updates to the DI rules. I think the added safety makes it worth doing something in this area eventually, but it could be added later. I’m interested to hear what Chris thinks.

I think it's important that delayed initialization also accommodate initialization *after* init() returns. There are many cases where full initialization depends on a process outside of init's control, especially in Cocoa with objects and outlets set up from nib files.

I don’t really consider that to be initialization in the current sense. IMO, without extending our DI model, a “delayed” behavior for a property of type T is best modeled with storage for T?, which is implicitly initialized to nil. The getter would do a force unwrap of the optional, and the setter would check that the storage is nil before assigning over it.

If/when we have support for a more dynamic initialization (e.g. dynamic typestate) then we could expand this to use it, but I don’t see how it would provide a functionally different model.

This is pretty much what I mean, and what the [delayed] example from the proposal provides. Matthew's making a case for a more constrained form of 'delayed' that still prevents you from exiting 'init' without having initialized everything.

One of my worries about this proposal in general is that you’ve listed out half-a-dozen different uses and every single one seems to require a new twist on the core semantics.

That's my general feeling too about this proposal. I just didn't know how to express what you said above.

I *did* somewhat strategically pick my examples to try to cover the breadth of different things I see someone wanting to do with this feature.

To me this proposal feels like it's is about trying to find a solution to multiple problems at once. A new problem arise that looks like it could be solved by a behaviour, so the behaviour feature expands to accommodate it. It looks like the wrong approach to me.

The correct approach in my opinion would be to try to make various parts of this proposal standalone, and allow them to combine when it makes sense. For instance, if you wanted to define a standalone feature for defining custom accessors that can be used everywhere, you wouldn't come with something that requires a behaviour annotation at the variable declaration. You'll come with something simpler that might looks like this:

custom_acccessor willSet<T>(newValue: T) { // define a custom accessor...
  set { // ... by redefining the setter...
    willSet(newValue) // ...inserting a call to the accessor here...
    currentValue = newValue // ...before calling the underlying setter
  }
}
custom_acccessor didSet<T>(oldValue: T) {
  set {
    let oldValue = currentValue
    currentValue = newValue
    didSet(oldValue)
  }
}
custom_acccessor willChange<T>(newValue: T) {
  willSet {
    if currentValue != newValue {
      willChange(newValue)
    }
  }
}

Then at the declaration point you just directly use the globally accessible accessor:

  var myvar: Int {
    willChange { print("will change to \(newValue)") }
  }

This fulfills at least one of the use cases. Can't we do the same treatment to each proposed use cases and see if there are other parts that can stand on their own?

I considered this approach. It works for behaviors that don't need to control a property's storage and only change the property's access behavior. To be fair, that covers a lot of ground, including things like observing, NSCopying, resetting, and locking synchronization. We would still need a feature, which could certainly be a different one, to generalize annotations that control the storage policy for decorated properties, which could cover things like laziness, indirect storage, unowned/weak-ness, dirty-tracking, C-style atomics, and pointer addressability—basically, anything where a plain old stored property of the API type isn't sufficient. (You could even throw get/set in this bucket, if you wanted to be super reductionist.) Finally, there's the feature to add operations on a *property* independent of its *type*, which interacts usefully with both other features—you need a way to reset a resettable or lazy property; maybe you want to bypass a synchronized property's lock in one place, etc. We'd like to improve on the "classic" answer of exposing an underlying ivar or property in these cases. If you want to break it down in micro-features, I guess there are three here:

1. Factoring out storage patterns,
2. Factoring out accessor patterns, and
3. Adding per-property operations.

(1) tends to be tightly coupled with (2)—if you're controlling storage, you almost certainly want to control the accessors over that storage. And (3) is useful with both (1) and (2). If there are separate features to be factored out here, I think they're very entangled features.

-Joe

···

On Jan 20, 2016, at 6:12 PM, Michel Fortin <michel.fortin@michelf.ca> wrote:
Le 19 janv. 2016 à 21:38, John McCall via swift-evolution <swift-evolution@swift.org> a écrit :

No single language feature necessarily has to "control" the accessor. The accessor is really of a pile of "custom accessor code" wrapping one another, some in the variable declaration, some in the variable's behaviours, and so why not some others in a global accessor declaration? They just pile up on top of each other.

Last post was about (2), so let's try to attack (1) using this approach. Basically, this is going to be the same thing as your behaviour proposal, minus `var` inside of it, minus custom accessors defined inside of it, minus functions inside of it (we'll revisit that at the end), and where the base property is implicit (`currentValue`) and the initializer is implicit too (`initValue`).

I'll start with an "identity" behavior, a behavior that wraps a base property while actually doing nothing:

// identity for type T creates storage of type T
storage_behavior identity<T>: T {
  // regular initializer, works with eager or deferred initialization
  init {
    // initValue is implicitly defined in this scope
    currentValue = initValue // initializing the storage
  }
  get {
    return currentValue
  }
  set {
    currentValue = newValue
  }
}

Sometime you need to force the initializer to be eager or deferred. So we can do that with a keyword, in which case for `eager` the `initValue` becomes available anywhere inside the behavior, and for `deferred` it becomes available everywhere outside of `init`:

// identity for type T creates storage of type T
eager storage_behavior eagerIdentity<T>: T {
  // initValue is implicitly defined in this scope

  // eager initializer (takes not argument)
  init {
    currentValue = initValue // initializing the storage
  }
  get {
    return currentValue
  }
  set {
    currentValue = newValue
  }
}

Note at this point how this is basically the same syntax as with the `custom_accessor` examples from my last post, minus there is no accessor to call in `get` or `set`, plus you have the ability to affect the storage. The main difference for the user is that, since it's a behavior, you opt in to it by annotating the variable. Whereas in the case of `custom_accessor` you opt it by writing the custom accessor body inside of the variable's accessors.

Ok, now let's write lazy:

// lazy for type T creates storage of type T?
deferred storage_behavior lazy<T>: T? {
  init {
    // defered initValue not available in this scope
    currentValue = nil // initializing the storage
  }
  get {
    if currentValue == nil {
      currentValue = deferredInitValue
    }
    return currentValue! // converting from T? to T
  }
  set {
    currentValue = newValue // converting from T to T?
  }
}

Now let's make it atomic:

storage_behavior atomic<T>: Atomic<T> {
  init {
    currentValue = Atomic(initValue)
  }
  get {
    return currentValue.payload
  }
  set {
    currentValue.payload = newValue
  }
}

Note how I can avoid adding variables inside the behavior by just changing the underlying type to be some kind of wrapper of the original type. That makes the feature simpler.

We can also write synchronized (for variables in a context where self is Synchronizable):

storage_behavior synchronzied<T where Self == Synchronizable>: T {
  init {
    currentValue = initValue
  }
  get {
    return self.withLock {
      return currentValue
    }
  }
  set {
    self.withLock {
      currentValue = newValue
    }
  }
}

Here is a minimalistic logging behavior:

storage_behavior logging<T>: T {
  // implied "identity" version of init, get, and set.
  willChange {
    print("\(currentValue) will change for \(newValue)")
  }
}

This last example is interesting: it shows that you can use a globally-defined accessor inside of the behavior; you don't have to define a `set` or a `get` to make it useful. This logging behavior is basically just a shortcut for defining a variable like this:

  var myvar: Int {
    willChange {
      print("\(myvar) will change for \(newValue)")
    }
  }

  @logging var myvar2: Int // way less boilerplate!

So in the end, a storage behavior model defined like this brings to the table the `init` and the modified storage type. Otherwise it's just a nice and convenient way to avoid repeating boilerplate you can already write inside of a variable declaration. Which is great, because with less special rules the whole behaviour feature looks much more approachable now.

For micro feature (3) I'm not sure what to suggest right now. I'm not 100% convinced exposing functions is necessary, and the visibility rules in your proposal seem quite complex. And while I can see its usefulness, I don't see why this feature should be limited to property behaviours. I think it would make a lot of sense to have that available directly in the variable declaration too, somewhat like this:

  var myvar: Int = 0 {
    mutating func reset() { myvar = 0 }
  }

  myvar#.reset() // some syntax to call the function

And if this was allowed, it'd only be natural that you could write the same thing inside of a behavior because behaviours are all about reducing the boilerplate of writing those custom accessors:

  eager storage_behavior resettable<T>: T {
    mutating func reset() { myvar = initValue }
  }

  @resettable myvar: Int = 0
  myvar#.reset()

If you want, this could be extended to allow computed and stored properties inside of the variable declaration, which would naturally extend to the behaviour too.

And that could be the final part of the puzzle. Three simpler features that can be useful on their own but which you can also combine to fulfill all the use cases of your proposal.

···

Le 20 janv. 2016 à 21:44, Joe Groff <jgroff@apple.com> a écrit :

On Jan 20, 2016, at 6:12 PM, Michel Fortin <michel.fortin@michelf.ca> wrote:

Le 19 janv. 2016 à 21:38, John McCall via swift-evolution <swift-evolution@swift.org> a écrit :

One of my worries about this proposal in general is that you’ve listed out half-a-dozen different uses and every single one seems to require a new twist on the core semantics.

That's my general feeling too about this proposal. I just didn't know how to express what you said above.

I *did* somewhat strategically pick my examples to try to cover the breadth of different things I see someone wanting to do with this feature.

To me this proposal feels like it's is about trying to find a solution to multiple problems at once. A new problem arise that looks like it could be solved by a behavior, so the behavior feature expands to accommodate it. It looks like the wrong approach to me.

The correct approach in my opinion would be to try to make various parts of this proposal standalone, and allow them to combine when it makes sense. For instance, if you wanted to define a standalone feature for defining custom accessors that can be used everywhere, you wouldn't come with something that requires a behavior annotation at the variable declaration. You'll come with something simpler that might looks like this:

custom_acccessor willSet<T>(newValue: T) { // define a custom accessor...
  set { // ... by redefining the setter...
    willSet(newValue) // ...inserting a call to the accessor here...
    currentValue = newValue // ...before calling the underlying setter
  }
}
custom_acccessor didSet<T>(oldValue: T) {
  set {
    let oldValue = currentValue
    currentValue = newValue
    didSet(oldValue)
  }
}
custom_acccessor willChange<T>(newValue: T) {
  willSet {
    if currentValue != newValue {
      willChange(newValue)
    }
  }
}

Then at the declaration point you just directly use the globally accessible accessor:

  var myvar: Int {
    willChange { print("will change to \(newValue)") }
  }

This fulfills at least one of the use cases. Can't we do the same treatment to each proposed use cases and see if there are other parts that can stand on their own?

I considered this approach. It works for behaviors that don't need to control a property's storage and only change the property's access behavior. To be fair, that covers a lot of ground, including things like observing, NSCopying, resetting, and locking synchronization. We would still need a feature, which could certainly be a different one, to generalize annotations that control the storage policy for decorated properties, which could cover things like laziness, indirect storage, unowned/weak-ness, dirty-tracking, C-style atomics, and pointer addressability—basically, anything where a plain old stored property of the API type isn't sufficient. (You could even throw get/set in this bucket, if you wanted to be super reductionist.) Finally, there's the feature to add operations on a *property* independent of its *type*, which interacts usefully with both other features—you need a way to reset a resettable or lazy property; maybe you want to bypass a synchronized property's lock in one place, etc. We'd like to improve on the "classic" answer of exposing an underlying ivar or property in these cases.

If you want to break it down in micro-features, I guess there are three here:

1. Factoring out storage patterns,
2. Factoring out accessor patterns, and
3. Adding per-property operations.

(1) tends to be tightly coupled with (2)—if you're controlling storage, you almost certainly want to control the accessors over that storage. And (3) is useful with both (1) and (2). If there are separate features to be factored out here, I think they're very entangled features.

--
Michel Fortin
https://michelf.ca

Will property behaviors work on computed properties? Because for now, we
can change a storage to a computed property and a computed for a storage
property without breaking anything.

···

Em qui, 21 de jan de 2016 às 00:44, Joe Groff via swift-evolution < swift-evolution@swift.org> escreveu:

> On Jan 20, 2016, at 6:12 PM, Michel Fortin <michel.fortin@michelf.ca> > wrote:
>
> Le 19 janv. 2016 à 21:38, John McCall via swift-evolution < > swift-evolution@swift.org> a écrit :
>
>> One of my worries about this proposal in general is that you’ve listed
out half-a-dozen different uses and every single one seems to require a new
twist on the core semantics.
>
> That's my general feeling too about this proposal. I just didn't know
how to express what you said above.

I *did* somewhat strategically pick my examples to try to cover the
breadth of different things I see someone wanting to do with this feature.

>
> To me this proposal feels like it's is about trying to find a solution
to multiple problems at once. A new problem arise that looks like it could
be solved by a behaviour, so the behaviour feature expands to accommodate
it. It looks like the wrong approach to me.
>
> The correct approach in my opinion would be to try to make various parts
of this proposal standalone, and allow them to combine when it makes sense.
For instance, if you wanted to define a standalone feature for defining
custom accessors that can be used everywhere, you wouldn't come with
something that requires a behaviour annotation at the variable declaration.
You'll come with something simpler that might looks like this:
>
> custom_acccessor willSet<T>(newValue: T) { // define a custom accessor...
> set { // ... by redefining the setter...
> willSet(newValue) // ...inserting a call to the accessor
here...
> currentValue = newValue // ...before calling the
underlying setter
> }
> }
> custom_acccessor didSet<T>(oldValue: T) {
> set {
> let oldValue = currentValue
> currentValue = newValue
> didSet(oldValue)
> }
> }
> custom_acccessor willChange<T>(newValue: T) {
> willSet {
> if currentValue != newValue {
> willChange(newValue)
> }
> }
> }
>
> Then at the declaration point you just directly use the globally
accessible accessor:
>
> var myvar: Int {
> willChange { print("will change to \(newValue)") }
> }
>
> This fulfills at least one of the use cases. Can't we do the same
treatment to each proposed use cases and see if there are other parts that
can stand on their own?

I considered this approach. It works for behaviors that don't need to
control a property's storage and only change the property's access
behavior. To be fair, that covers a lot of ground, including things like
observing, NSCopying, resetting, and locking synchronization. We would
still need a feature, which could certainly be a different one, to
generalize annotations that control the storage policy for decorated
properties, which could cover things like laziness, indirect storage,
unowned/weak-ness, dirty-tracking, C-style atomics, and pointer
addressability—basically, anything where a plain old stored property of the
API type isn't sufficient. (You could even throw get/set in this bucket, if
you wanted to be super reductionist.) Finally, there's the feature to add
operations on a *property* independent of its *type*, which interacts
usefully with both other features—you need a way to reset a resettable or
lazy property; maybe you want to bypass a synchronized property's lock in
one place, etc. We'd like to improve on the "classic" answer of exposing an
underlying ivar or property in these cases. If you want to break it down in
micro-features, I guess there are three here:

1. Factoring out storage patterns,
2. Factoring out accessor patterns, and
3. Adding per-property operations.

(1) tends to be tightly coupled with (2)—if you're controlling storage,
you almost certainly want to control the accessors over that storage. And
(3) is useful with both (1) and (2). If there are separate features to be
factored out here, I think they're very entangled features.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Or with the anonymous self-calling closure dance.

···

On Tue, Jan 26, 2016, at 02:40 PM, Jacob Bandes-Storch via swift-evolution wrote:

On Tue, Jan 26, 2016 at 9:14 AM, Joe Groff via swift-evolution <swift- > evolution@swift.org> wrote:

`lazy var`s still can't refer to self today AFAIK (I know we tried to
fix that, but we still get plenty of radars saying it doesn't work…),
so I don't think it would be a regression for a behavior
implementation not to immediately be able to either. (And I'm not
proposing we immediately replace our existing 'lazy' implementation
immediately either.)

They can; it just requires a type annotation.

class C { lazy var b: B = B(c: self) }

struct B { let c: C }
_________________________________________________
swift-evolution mailing list swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I agree that "myProperty.clear()" is very appealing, but it has the potential to be confusing when behavior methods are shadowed by members of the front-facing property. You'd still need a way to unambiguously refer to behavior methods when they're shadowed too.

-Joe

···

On Dec 22, 2015, at 8:42 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

You shouldn’t have to say myProperty.lazy.backingStore.clear(), just myProperty.lazy.clear().

I don’t know where that leaves us :-)

I wonder if these should all just be flattened into the instance that owns the property. So instead of saying foo.myProperty.lazy.clear(), you just say foo.clearMyProperty() or some such. That approach isn't very principled, but it *is* very convenient.

Totally agree it could be useful in many cases. Thanks for pointing out the associated type case. I hadn’t considered that one but you’re right that it is theoretically possible.

I’m going to get back to wrapping up the second draft of my protocol-driven forwarding proposal soon. I wasn’t planning to include forwarding to optional members in the core proposal, but mention that it could be added down the road. Do you agree with holding off on that or do you think it’s useful enough to include in the core proposal?

I also hadn’t thought about forwarding from Optional<T> to T but that should be a pretty straightforward addition to the feature if it supported forwarding to optionals.

Matthew

···

On Jan 16, 2016, at 1:27 PM, Joe Groff <jgroff@apple.com> wrote:

On Jan 15, 2016, at 5:54 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 15, 2016, at 7:45 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 15, 2016, at 6:42 AM, plx via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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?

No, I think you've got it. This seems like a general problem to me, though; it'd be nice if protocol conformances could be easily forwarded, for instance from Optional<T> to T.

Any thoughts about how that might work when requirements have a return value? Or are you just referring to forwarding conformances when the protocol doesn’t have members with return values?

I don't have a great answer in mind, unfortunately. You're right that it only really makes sense for "sink"-like protocols, where none of the requirements produce a value, or theoretically for protocols where all results are of associated types that could be optionalized. The sink protocol use case comes up all the time, though, especially with delegates and callbacks, and is one the places where ObjC's nil-messaging behavior feels legitimate.