[Proposal] Property behaviors

Perhaps we could use '...' to distinguish composable and non-composable behaviors:

// Implementation of exactly [lazy, synchronized]
behavior var [lazy, synchronized] _: Value where Self: Synchronizable { ... }

// Implementation of 'observed' composed with any other behavior
behavior var […, observed] _: Value { ... }

-Joe

···

On Jan 19, 2016, at 3:10 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

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.

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.

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.

···

On Jan 19, 2016, at 3:10 PM, Joe Groff <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,

I’m wondering if you missed my comments. I had a few questions I haven’t seen answered yet so I’m bumping them.

-Matthew

The proposal makes it clear when an initializer is required but is a little bit less clear about when it may be left off. Is this correct?

var [baseProp] x // no initializer, ok as long as base doesn't have init req?
var [initializerReqt] y // no initializer, error because of the initializer requirement?

Sorry, I thought I explained this. The default stored property that gets instantiated for a base property can be initialized out-of-line, so the inline initializer expression is optional, yes.

Another thing that isn’t clear is what happens when a property with a behavior is set within the initializer of the containing type:

struct S {
  var [observed] s: String
  init(s: String) {
    // What happens here? Is the behavior’s “set” accessor called?
    // This may not always be desirable, as in the case of “observed"
    self.s = s
  }
}

Just like today, assignments within the initializer would bypass the behavior and directly initialize the storage.

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)

-Joe

···

On Jan 20, 2016, at 9:31 AM, Matthew Johnson <musical.matthew@mac.com> wrote:

This would also allow us to support a variant `delayedImmutable` that *must* be assigned during initialization of the containing type, but not necessarily during phase 1. That behavior would facilitate maximum safety when we must pass `self` to the initializer when constructing an instance to assign to a property (not an uncommon use case).

If the compiler could enforce slightly relaxed initialization rules that require initialization of the property before the initializer exits and before the property is read in the initializer body, but not necessarily during phase 1, then we could achieve nearly complete static safety. The only window for error would be any uses of self that happen outside the initializer body before the property is initialized.

The behavior might look like this:

public var behavior phase2Immutable<Value>: Value {
  private var value: Value? = nil

  get {
    guard let value = value else {
      fatalError("property accessed before being initialized")
    }
    return value
  }
  
  init {
    value = initialValue
  }
}

This would be a significant improvement over delayedImmutable in many use cases IMO.

Joe,

I’m wondering if you missed my comments. I had a few questions I haven’t seen answered yet so I’m bumping them.

-Matthew

The proposal makes it clear when an initializer is required but is a little bit less clear about when it may be left off. Is this correct?

var [baseProp] x // no initializer, ok as long as base doesn't have init req?
var [initializerReqt] y // no initializer, error because of the initializer requirement?

Sorry, I thought I explained this. The default stored property that gets instantiated for a base property can be initialized out-of-line, so the inline initializer expression is optional, yes.

Another thing that isn’t clear is what happens when a property with a behavior is set within the initializer of the containing type:

struct S {
  var [observed] s: String
  init(s: String) {
    // What happens here? Is the behavior’s “set” accessor called?
    // This may not always be desirable, as in the case of “observed"
    self.s = s
  }
}

Just like today, assignments within the initializer would bypass the behavior and directly initialize the storage.

Thanks for clarifying.

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.

-Matthew

···

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

On Jan 20, 2016, at 9:31 AM, Matthew Johnson <musical.matthew@mac.com <mailto:musical.matthew@mac.com>> wrote:

-Joe

This would also allow us to support a variant `delayedImmutable` that *must* be assigned during initialization of the containing type, but not necessarily during phase 1. That behavior would facilitate maximum safety when we must pass `self` to the initializer when constructing an instance to assign to a property (not an uncommon use case).

If the compiler could enforce slightly relaxed initialization rules that require initialization of the property before the initializer exits and before the property is read in the initializer body, but not necessarily during phase 1, then we could achieve nearly complete static safety. The only window for error would be any uses of self that happen outside the initializer body before the property is initialized.

The behavior might look like this:

public var behavior phase2Immutable<Value>: Value {
  private var value: Value? = nil

  get {
    guard let value = value else {
      fatalError("property accessed before being initialized")
    }
    return value
  }
  
  init {
    value = initialValue
  }
}

This would be a significant improvement over delayedImmutable in many use cases IMO.

Hi Joe,

I have a few of questions after reading your proposal.

Question #1 :

Let’s say that the property behavior has an init() method as in your example for the lazy property behavior where you simply assign nil to the value. After the declaration of the lazy behavior you show how to use this behavior for a variable declared in the global scope, It appears to me that the init() gets called at the time the property is declared because you mention in the comment that the property is inited to nil:

var [lazy] x = 1738 // Allocates an Int? behind the scenes, inited to nil

My question is as follows: What about when the property is declared inside a class? At what point is the init() called?

The init() is called at the beginning of initialization, as if the behavior's storage were expanded out with an initial value:

class Foo {
  var [lazy] x = 1738

  // Compiler expands this behind the scenes:
  var `x.[lazy]`: Int? = lazy.init()
}

It's useful for this to happen later, since many behaviors would want to allow out-of-line initialization in the containing type's init method:

class Bar {
  var [runcible] x: Int

  init() {
    x = 679 // Would like runcible.init(679) to trigger here
  }
}

There are some subtleties to this, so I wanted to subset that out as a separate extension.

Question #2 :

In your example for the synchronized property behavior, you have the following line of code in the declaration of the behavior:

  var value: Value = initialValue

The get and the set accessors use withLock { … } to get / set the value. However, the code above that stores the initialValue in the value variable does not use withLock { … }. Does it matter? And similar to question #1, at what time is this code executed?

The initializer will occur at the start of the container's initialization, as in the previous example (though `synchronized` is really somewhere you want to be able to trigger the initialization out-of-line). For `synchronized`, it's unlikely to be necessary to synchronize the initialization, since a class in Swift can't be shared across threads until it's already fully initialized.

Comments About this Proposal:

I’ve been following the threads on property behaviors as it is one of my favorite threads. I don’t always understand everything that you are all saying, for example, I am not sure if I understand what you guys means by out-of-line initialization in this context.

In general, I like the proposal. At first I thought that the square brackets notation could be confusing as it looks like an array. I understand there aren’t many options to use. However, now that I think of this as a list of property behaviors preceding the property name, I think the square brackets are the right choice.

Thanks! Don't be afraid to ask questions about things that aren't clear. When I refer to out-of-line initialization, I refer to the ability to initialize properties after their initial declaration. For local properties, this is possible by assigning the initial value to the property after its immediate declaration:

func foo() {
  var x: Int
  // some other stuff happens that doesn't use x
  x = 22 // out-of-line initialization of x
}

and for properties inside classes, this is possible during the class's `init` implementations:

class Foo {
  var x: Int

  init(x: Int) {
    self.x = x // out-of-line initialization of self.x
  }
}

-Joe

···

On Jan 22, 2016, at 7:38 PM, Ricardo Parada <rparada@mac.com> wrote:

Practically speaking, I think there are inevitably going to be breaking changes here once we get some experience with the feature. 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.

-Joe

···

On Jan 23, 2016, at 8:14 AM, Thorsten Seitz <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.

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?

-Thorsten

···

Am 23.01.2016 um 20:30 schrieb Joe Groff <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>>:

I don’t know enough about how ABIs work, but if this is out of scope for Swift 3, is there a way to add some “reserved for future use” bits or something now so that the ABI changes less often? It’d be a shame for people to be hesitant about this stuff for Swift 4 (or whatever) because they don’t want to break compatibility with some 3rd-party library that isn’t being maintained anymore.

- Dave Sweeris

···

On Jan 23, 2016, at 11:30, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

[…] Many of the other features I’ve cut, including the initialization generalizations and extensions, would also be ABI-breaking changes at the implementation level.

-Joe

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.

Deferred questions along that line of thought:
- Can a behavior optionally have an initial value, and provide its own default otherwise?

Not yet.

- Can a behavior optionally have an initial value, and require phase-1 initialization in an instance initializer otherwise?

Not yet.

- Does a behavior with no initializer requirements not allow initial values?

Yes for now. I'm trying to keep the initial model simple. Rounding out the initialization model is a design discussion unto itself.

Other comments on the actual proposal:

- Is 'Self' the static type or the dynamic type of 'self'? That is, what does 'Self()' do in a class with subclasses?

Good question.

- 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?

- I'm not sure why changing the storage in a public behavior is inherently fragile, unless they are public. I think it's more that behaviors are all inlined, so if the implementation changes there's no guarantee that existing users of the behavior are using the updated version. That means changes aren't breaking, just not guaranteed universal.

Maybe fragile is the wrong word—'inlineable' is more what I meant.

- Like many others I'm still not happy with the syntax—either the declaration syntax or the use syntax. I agree that it looks like a subscript and has nothing to do with other square brackets in the language…although there are only so many sigils. (Okay, I guess it's vaguely like capture lists.)

- I don't like that the name of the behavior shows up in the member access. I feel like the behavior of "Resettable" or "Observable" could be implemented by different behaviors, and I would want the freedom to resiliently change that while keeping the same public interface. Further down that direction lies "behavior protocols", though, which is way too much complexity for this…

- Nitpick: why are "willSet" and "didSet" mutating? (I know they are now, but that seems like the wrong interface once we have something more flexible.)

I think that's all I have for now.

-Joe

···

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

I’m not sure exactly what you’re asking here, so let me respond to several points that you might be asking :-)

- We generally prefer small proposals that stand on their own, which can then have other proposals built on top of them.

- I think that property behaviors is a strong goal for Swift 3, because we’d like to clean up lazy, @NSManaged, etc to get magic out of the compiler.

- I think we want a fully baked implementation of property behaviors for swift 3, but would be willing to subset out advanced features if it makes sense. It doesn’t have to solve all conceivable problems and can be extended over time.

- Predicting what will end up in Swift 3 (e.g., “will we achieve our goals?") is inherently fraught with peril and cannot be done perfectly. There are a ton of unknowns in design and implementation of any feature (e.g., how do you know how long it will take to implement behaviors when we don’t have a concrete design?), and new work gets added all of the time as we find new problems to solve and the community identifies new problems. It is also impossible to predict what open source contributors will provide.

Part of the reason we have to say “no” to good ideas is that we are incredibly design and implementation bound. We have high goals for Swift 3 as it is, but we aren’t 100% certain we’ll be able to achieve them (that’s why they are “goals”, not “certainty”). I think that we’ve had a consistent approach with Swift 3, where we’re focused on fixing core deficiencies in the base language, fixing implementation issues and designing resilience features that affect ABI stability, while taking on small scope extensions to the language. This has come at a cost of having to defer discussion on “large” extensions to the language that do not affect the core model (e.g. the memberwise init revamp) but I’m hoping that we will still get some of those for Swift 3.

Overall, this comes back to a higher order philosophy about what Swift 3 is really about: it is about driving the next wave of adoption of Swift by even more people. This will hopefully come from new audiences coming on-board as Corelibs + Swift on Linux (and other platforms) become real, SwiftPM being great and growing into its own, and the Swift language/stdlib maturing even more.

What does this mean looking forward? Well, Swift 2 to Swift 3 is going to be an unavoidably disruptive change for code since Cocoa renamification is going to land, and we’re going to be building impressive migration technology again. As such, we should try to get the “rearrange all the deckchairs” changes into Swift 3 if possible, to make Swift 3 to 4 as smooth as possible. While our community has generally been very kind and understanding about Swift evolving under their feet, we cannot keep doing this for long. While I don’t think we’ll want to guarantee 100% source compatibility from Swift 3 to Swift 4, I’m hopefully that it will be much simpler than the upgrade to Swift 2 was or Swift 3 will be.

-Chris

···

On Jan 23, 2016, at 5:58 PM, Curt Clifton via swift-evolution <swift-evolution@swift.org> wrote:

Can the core team comment on the likely evolution of proposals like property behaviors that are separated into first steps and future work? That is, should we expect that some of the future work might be in scope for Swift 3, depending on the implementation of the first steps? Or is the hope to lock down the scope for Swift 3 sooner rather than later?

I don't really understand what you're trying to say here. The goal you describe (of not being able to copy or pass the synchronized value around) is satisfied by having some way to declare a struct that cannot be copied (but can be moved, because moves are always fine as long as there's no code (e.g. other threads) that is still expecting the value to be at its original location). Describing synchronized behavior as a non-copyable type like this actually works extremely well in practice.

The Rust language is an excellent demonstration of this fact, where literally all of the threading and synchronization behavior is expressed in the libraries by leveraging the type system and borrow checker to guarantee safety. If you want a value with an associated lock, you just have a value of type Mutex<T>, and the only way to access the underlying value is by using an accessor that returns a MutexGuard, which is a smart pointer (Rust has smart pointers) that lets you access the underlying value and releases the lock when the guard goes out of scope. And because of Rust's borrow checker, it's impossible to leak a reference to the underlying value past the scope of the MutexGuard (and you're not allowed to copy MutexGuards or to send them to other threads, but you can still move the guard around locally and the borrow checker will statically ensure the guard itself cannot outlive the mutex). The Mutex value itself also cannot be copied, but (if you have no outstanding guards) you can move it or even send it to other threads, and you can put it in a location that is visible to multiple threads at once (otherwise what's the point?).

Ultimately, I would love to have non-copyable structs in Swift. But the only way to really do that is to have references in the language (otherwise the moment you call a method on a non-copyable struct, you've lost the struct as the value is moved into the method). And if you have references, you really want them to be safe. Personally, I'd love to have the power of Rust's borrowchecker and lifetimes system, but as I mentioned in another thread recently, there is a pretty steep learning curve there.

-Kevin Ballard

···

On Fri, Dec 18, 2015, at 07:18 AM, Michel Fortin wrote:

Le 17 déc. 2015 à 22:47, Kevin Ballard via swift-evolution <swift-evolution@swift.org> a écrit :

> Good point. I'm also inclined to say that Michel's motivating example (of a property that requires going through a `synchronized` accessor) would be better modeled just as a type Synchronized<Value> that exposes the `synchronized` accessor. No need for behaviors, you just say
>
> var data: Synchronized<T> = ...
>
> Behaviors are interesting because they affect how normal property access works. Properties that don't expose normal property accessors aren't really properties at all.

>

> The only real downside to the Synchronized<Value> type is there's no way to prevent anyone from copying the value (it could be a class, but then you're paying for the overhead of using a separate object). This same limitation is also one of the problems with adding an Atomic<T> type, which is something I would dearly love to have.

There's indeed the problem that you can't disallow copying a value, and that makes anything involving a mutex a bit fragile. Even if you append a mutex to a property through a behaviour, can a struct containing that property be safely copied? Or should that behaviour be reserved for properties in classes?

And then the question about the definition of a "property"? Is it still a property if the getter is inaccessible? Maybe not. But this thread is all about redefining properties.

The reason I'm suggesting implementing synchronized as a behaviour instead of a type is because I found out with experience that synchronization should be related to variables, not types. Types exists in a vacuum while variables are bound to a context, and a synchronized access pattern should usually stay encapsulated in a particular context (struct or class). A Synchronized<T> should not be copied or passed by reference and used out of its context; a property behaviour makes that just impossible, which is better.

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

-Chris

···

On Dec 21, 2015, at 6:06 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 21, 2015, at 5:33 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 21, 2015, at 5:21 PM, Jordan Rose <jordan_rose@apple.com> wrote:

:-( I'm worried about increasing the size of the language this much. I really want to be able to say "behaviors are just syntactic sugar for declaring accessors and storage, and then everything else behaves normally". This makes them another entirely orthogonal decl kind, like operators.

I'd prefer not to have a new decl as well, if that was the best choice. However, it's still just syntactic sugar for declaring accessors and storage.

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'm worried about increasing the size of the language this much. I really want to be able to say "behaviors are just syntactic sugar for declaring accessors and storage, and then everything else behaves normally". This makes them another entirely orthogonal decl kind, like operators.

I'd prefer not to have a new decl as well, if that was the best choice. However, it's still just syntactic sugar for declaring accessors and storage.

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.

+1

···

On Dec 21, 2015, at 8:06 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 21, 2015, at 5:33 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 21, 2015, at 5:21 PM, Jordan Rose <jordan_rose@apple.com> wrote:

-Dave

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

Sorry, I meant Tal Atlas’ idea.

-Chris

···

On Jan 13, 2016, at 9:35 PM, Chris Lattner <clattner@apple.com> wrote:

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

Relatedly: How first-class are behaviors? Can you assign `foo.bar.lazy` to a variable, or pass it as a parameter? Or can you pass a value along with its behavior?

  func consumeThing(inout thing: [resettable] Thing) {
    thing.use()
    thing.resettable.reset()
  }

That's an interesting idea; I think we could add something like that later if it's useful. It's my intent in this proposal to avoid treating behaviors as first-class types and keep them mostly instantiation-based, in order to avoid the metadata instantiation overhead of a type-based approach. That would mean that `bar.lazy` isn't really a first-class entity.

Random point, if bar.lazy isn’t a first class thing, then that reinforces Talin’s syntax idea of bar.[lazy].reset() as the syntax to operate on the behavior. It would be nice to move towards a model where all simply-dotted things are curryable.

Also, why the `: Value`? Can that ever be anything but `: Value`? What does it mean if it is?

Also also, why the `<Value>`? There will always be exactly one generic type there, right? I get that you can dangle requirements off of it, but can't you do that by supporting a `where` clause?

You might want to constrain the behavior to apply only to some subset of a generic type:

var behavior optionalsOnly<Value>: Value? { ... }
var behavior dictionariesOfStringsOnly<Key>: Dictionary<Key, String> { ... }

Ah, so the type on the right side of the colon is the allowable variable type? That's a pretty handy feature. Could use a little more explanation, though. :^)

I agree that an unconstrained type is likely to be the 95% case in practice. Maybe an approach more like what we do with generic extensions is appropriate; we could say that `Value` and `Self` are both implicit generic parameters, and use a freestanding `where` clause to constrain them.

No, your way is probably better. For instance, I think it's probably more useful for `optionalOnly`'s `Value` type to be the unwrapped type.

Should we actually force people to declare the `value` property?

That's a bit fiddly. `lazy` has optional storage, but produces a property of non-optional type, and other behaviors can reasonably have different (or no) storage needs. I feel like the storage should be explicitly declared.

That's fair.

While we're here, why does `initializer` have an explicit type? Can it be different from `Value`? What does it mean if it is?

As proposed, no, but it might be possible to relax that. My concern with `deferred initializer` floating on its own is that it looked weird. To my eyes, `initializer: T` makes it clear that something named `initializer` of type `T` is being bound in the behavior's scope.

It does look a little weird, but it has precedent in, for instance, `associativity` declarations.

I feel like there ought to be a way to build this part out of something that already exists. Perhaps a deferred initializer is just one with an autoclosure?

  public var behavior resettable<Value>: Value {
    initializer: Value
    ...
  }
  
  var behavior lazy<Value>: Value {
    initializer: @autoclosure Void -> Value
    ...
  }
  
  public var behavior delayedMutable<Value>: Value {
    // No initializer line, so it doesn't accept an initializer
    ...
  }

Maybe it should be a `var` or `let` property, and it just has a specific name. That might be a little odd, though.

@autoclosure instead of `deferred` is an interesting idea. Also, as I mentioned to Felix, if we didn't have a way to bind the initializer, the same functionality could be achieved using custom accessors, at the loss of some sugar. If that's acceptable, it would greatly simplify the proposal to leave out the ability to hijack the initializer.

Why not? I would assume that behaviors are initialized from the inside out, and an eager-initialized property will need to initialize its base property's value. Or is there an implicit rule here that a behavior with a base property cannot take an initializer?

The base property is out of the current behavior implementation's control, since it could be a `super` property or the result of another behavior's instantiation. In the `super` case, the `base` property's storage won't be initialized until later when super.init is invoked, and in the behavior composition case, the `base` property's initialization is in the prior behavior's hands.

Ooh, that's a good point about overrides.

Do you think it makes sense to allow non-base properties to accept initializers? If so, what does it mean? What happens if one behavior wants deferred and another wants eager?

I think, fundamentally, only the innermost behavior can control initialization. If you innermost behavior itself has a base, then there's essentially an implicit "stored" behavior nested within it, which has the standard stored property initialization behavior.

Capture is tricky; thanks for bringing it up, since I hadn't thought of it. As I see it being implemented: in a mutating context, `self` is inout, and the behavior's storage always has to be implicitly projected from `self` in order to avoid overlapping `inout` references to self and the behavior storage. Closures would have to capture a shadow copy of `self`, giving the often unexpected behavior you see with captured `inout` parameters today losing attachment to their original argument after the `inout` parameter returns. In nonmutating contexts, everything's immutable, so `dispatch_async` can safely capture the minimal state referenced from within a `get`.

That's not the greatest behavior, but it's probably the best you can do.

If Self is a reference type, will capturing the behavior hold a strong reference to `self`? I assume that in that scenario, it will, and then assigning to the behavior's properties *will* work properly.

If Self is class-constrained, we could always strongly capture it. Otherwise, we'd conservatively only be able to do so in nonmutating contexts.

Relatedly: How first-class are behaviors? Can you assign `foo.bar.lazy` to a variable, or pass it as a parameter? Or can you pass a value along with its behavior?

  func consumeThing(inout thing: [resettable] Thing) {
    thing.use()
    thing.resettable.reset()
  }

That's an interesting idea; I think we could add something like that later if it's useful. It's my intent in this proposal to avoid treating behaviors as first-class types and keep them mostly instantiation-based, in order to avoid the metadata instantiation overhead of a type-based approach. That would mean that `bar.lazy` isn't really a first-class entity.

Fair enough. It's certainly not so critical that we need to stop the show until we have it.

One more thing: should there be a way to pass arbitrary data, rather than initializers or accessors, into a behavior? For instance, it would be nice if you could do something like:

  var behavior backedByJSON<Value: JSONRepresentable where Self: JSONObjectRepresentable>: Value {
    var key: String
    
    init(key: String) {
      backedByJSON.key = key
    }
    
    get {
      return Value(JSON: self.JSON[key])
    }
    set {
      self.JSON[key] = newValue.JSON
    }
  }
  
  struct User: JSONObjectRepresentable {
    var JSON: [String: JSONType]
    
    var [backedByJSON(key: "id")] ID: Int
    var [backedByJSON(key: "name")] name: String
    
    var [backedByJSON(key: "posts")] posts: [Post]
  }

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.

-Joe

···

On Jan 13, 2016, at 9:38 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

In favor of them being just functions (even if they were declared as part of a class/struct/whatever)…

Isn’t there a proposal for a function composition operator floating around? I think it was “•” (option-8)… Anyway, assume that’s what I mean by “•”.
var <lazy•resettable•changeObserved> foo: Int = {…}

Plus, it’d make the order in which they were applied obvious.

Actually, speaking of using functions that were declared as part of an object… Could this all be done now by introducing an Observable<T> struct which itself uses the existing will/didSet functions? Like this?
var foo = Observable(3, lazy•resettable•changeObserved)

I think the only thing off the top of my head that wouldn’t pretty much Just Work is automatically converting between Observable<T> and T, the way you can with Implicitly Unwrapped Optionals. I might play with it a bit tonight after dinner.

- Dave Sweeris

···

On Jan 14, 2016, at 14:05, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

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

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?

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

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

···

On Jan 14, 2016, at 4:05 PM, Joe Groff <jgroff@apple.com> wrote:

-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@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 13, 2016, at 5:12 PM, plx via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Thanks for the earlier feedback (you did mention how to resolve reference ambiguity).

After re-reading it this is a really nice proposal overall. Even in it’s current state it is a huge upgrade, and could lead to a huge productivity / implementation-quality upgrade in application code.

I do have a bunch of questions, though.

## Syntax: “In the Large”

One thing that does worry me is readability in real-world variable declarations; I agree that `@behavior(lazy)` is clunky in the small, but in the large the current syntax is questionable:

class SomeView : UIView {

@IBInspectable
  public internal(set) weak var [mainThread,resettable,logged,changeObserved] contentAreaInsets: UIEdgeInsets = UIEdgeInsetsZero {
   logName { “contentAreaInsets” } // <- see note later, btw
   didChange { setNeedsUpdateConstraints() }
}

}

…which is admittedly an extreme example, but it illustrates the point.

The below is definitely worse in the small, but feels better in the large:

class SomeView : UIView {

@IBInspectable
@behavior(mainThread,resettable,logged,changeObserved)
public internal(set) weak var contentAreaInsets: UIEdgeInsets = UIEdgeInsetsZero {
   logName { “contentAreaInsets” } // <- see note later, btw
   didChange { setNeedsUpdateConstraints() }
}

}

…(I could add line breaks in the first one, but a line break anywhere between the `var` and its the name seems odd).

To be clear, I am not suggesting the above syntax as-is; I am more trying to make sure some consideration is “how does this look with longer behavior lists”, since all the examples in the proposal are for “nice” cases.

Putting the behavior-list after `var` but before the name and type seems unfortunate once you want to add a line-break in there.

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.

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

## 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:

class BaseView : UIView {

var [changeObserved] text: String {
   didChange { setNeedsLayout() }
}

}

class RefinedView : BaseView {

// is this right? (recall property is already `changeObserved` in parent):
override var text: String {
   didChange { invalidateIntrinsicContentSize() }
   // ^ does parent’s didChange get called also? can I control that?
   // ^ when is this override called? when is the parent called (if it is)?
}

// or is this right? (needing to add another `changeObserved` here?)
override var [changeObserved] text: String {
   didChange { invalidateIntrinsicContentSize() }
   // ^ does parent’s didChange get called also? can I control that?
   // ^ when is this override called? when is the parent called (if it is)?
}

}

…I’m really not sure which of the above is more reasonable.

The first variant would seem to have confusing timing (when do the calls happen relative to each other and to other behaviors)?

The other one seems to introduce a new, identically-named behavior, which seems like it’d lead to ambiguity if you had to use behavior methods/behavior properties.

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.

## Semantics: Redundancy/“Static” Parameterization

This is an extended example, but it sort-of has to be to illustrate the concern.

Suppose we wanted to define a bunch of behaviors useful for use on UIView. I’ll provide a few examples, including just the `set` logic to keep it as short as possible:

// goal: `redraw` automatically calls `setNeedsDisplay()` when necessary:
var behavior redraw<Value:Equatable where Self:UIView> : Value {
set {
   if newValue != value {
     value = newValue
     self.setNeedsDisplay()
   }
}
}

// goal: `invalidateSize` automatically calls `invalidateIntrinsicContentSize()` when necessary:
var behavior invalidateSize<Value:Equatable where Self:UIView> : Value {
set {
   if newValue != value {
     value = newValue
     self.invalidateIntrinsicContentSize()
   }
}
}

…(and you can consider also `relayout`, `updateConstraints`, `updateFocus`, accessibility utilities, and so on…).

With all those in hand, we arrive at something IMHO really nice and self-documenting:

class CustomDrawnView : UIView {

// pure-redrawing:
var [redraw] strokeWidth: CGFloat
var [redraw] outlineWidth: CGFloat
var [redraw] strokeColor: UIColor
var [redraw] outlineColor: UIColor

// also size-impacting:
var [redraw, invalidateSize] iconPath: UIBezierPath
var [redraw, invalidateSize] captionText: String
var [redraw, invalidateSize] verticalSpace: CGFloat

}

…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.

On the one hand, it seems like it ought to be possible to do better here — e.g., do a single such check, not one per behavior — but on the other hand, it seems hard to augment the proposal to make it possible w/out also making it much more complex than it already is.

EG: the best hope from a readability standpoint might be something like this behavior:

var behavior invalidate<Value:Equatable where Self:UIView> {
// `parameter` here is new syntax; explanation below
parameter display: Bool = false
parameter intrinsicSize: Bool = false

// as-before:
var value: Value

// `get` omitted:
set {
   if newValue != value {
     value = newValue
     if display { self.setNeedsDisplay() }
     if intrinsicSize { self.invalidateIntrinsicContentSize() }
     // also imagine constraints, layout, etc.
   }
}
}

…but to achieve that “omnibus” capability you’d need a lot of flags, each of which:

- needs to get set somehow (without terrible syntax)
- needs to get “stored" somehow (without bloating the behaviors, if possible)

Syntax to set the flags seems awkward at best:

// this seems close to ideal for such parameters:
var [invalidate(display,intrinsicSize)] iconPath: UIBezierPath

// but this seems the best-achievable option w/out dedicated compiler magic:
var [invalidate(display=true, intrinsicSize=true)] iconPath: UIBezierPath

…and at least to my eyes that "best-achievable syntax" isn’t all that great, anymore.

Likewise you’d need some way to actually store those parameters, presumably *not* as ordinary stored fields — that’s going to bloat the behaviors! — but as some new thing, whence the new `parameter` keyword.

Between that and the naming/parameter-passing, it feels like a big ask, probably too big.

FWIW, for sake of comparison, this seems to be about the best you can do under the current proposal:

class CustomDrawnView : UIView {

// pure-redrawing:
var [changeObserved] strokeWidth: CGFloat {
   didChange { invalidate(.Display) }
}

var [changeObserved] outlineWidth: CGFloat {
   didChange { invalidate(.Display) }
}

var [changeObserved] strokeColor: UIColor {
   didChange { invalidate(.Display) }
}

var [changeObserved] outlineColor: UIColor {
   didChange { invalidate(.Display) }
}

// also size-impacting:
var [changeObserved] iconPath: UIBezierPath {
   didChange { invalidate([.Display, .IntrinsicContentSize]) }
}

var [changeObserved] captionText: String {
   didChange { invalidate([.Display, .IntrinsicContentSize]) }
}

var [changeObserved] verticalSpace: CGFloat {
   didChange { invalidate([.Display, .IntrinsicContentSize]) }
}

}

…where `invalidate` is taking some bitmask/option-set and then calling the appropriate view methods.

This isn’t terrible, it’s just nowhere near what it might be under this proposal.

I also think it’s perfectly reasonable to see the above and decide the likely complexity of a solution probably outweighs whatever gains it might bring; I’m just bringing it up in hopes there might be an easy way to have most of the cake and also eat most of the cake.

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] } }

+1 to allowing implicit single-expression return. Is there a reason this isn’t possible everywhere we can return a value? It seems to me like that is an orthogonal issue and a proposal to allow it everywhere might be a good idea.

···

On Jan 14, 2016, at 4:05 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 14, 2016, at 11:33 AM, plx via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

## ObjC Interaction

One thing I am not sure about is how this interacts with @objc annotations.

First, my assumption is that, as today, property behaviors and @objc-visibilty are essentially orthogonal (don’t really impact each other). This doesn’t seem to be stated explicitly anywhere, and it would be preserving the status quo, but it’s worth confirming just to be sure.

Secondly, right now one of the language’s minor warts is you can’t really get proper objective-c property names on some read-write properties without some doing.

You can either do this:

class Foo: NSObject {
@objc(isPermitted)
var permitted: Bool
}

…which gets you `isPermitted` (good) and `setIsPermitted:` (not ideal), or you can do this:

class Foo: NSObject {

@nonobjc // maximize chances of efficiency
private final var _permitted: Bool

var permitted: Bool {
   @objc(isPermitted) get { return _permitted }
   @objc(setPermitted:) set { _permitted = newValue }
}

}

…which gets the right Objective-C names but is quite clunky.

What you can’t do is this:

class Foo: NSObject {
var permitted: Bool {
  @objc(isPermitted) get, // just rename
  @objc(setPermitted:) set // just rename
}

…at least not to my knowledge; if there’s a trick I don’t know it.

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.

-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@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 13, 2016, at 5:12 PM, plx via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
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?

-Joe

···

On Jan 19, 2016, at 4:28 PM, John McCall <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:

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.

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.

-Joe

···

On Jan 20, 2016, at 9:48 AM, Matthew Johnson <musical.matthew@mac.com> wrote:

On Jan 20, 2016, at 11:39 AM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 20, 2016, at 9:31 AM, Matthew Johnson <musical.matthew@mac.com <mailto:musical.matthew@mac.com>> wrote:

Joe,

I’m wondering if you missed my comments. I had a few questions I haven’t seen answered yet so I’m bumping them.

-Matthew

The proposal makes it clear when an initializer is required but is a little bit less clear about when it may be left off. Is this correct?

var [baseProp] x // no initializer, ok as long as base doesn't have init req?
var [initializerReqt] y // no initializer, error because of the initializer requirement?

Sorry, I thought I explained this. The default stored property that gets instantiated for a base property can be initialized out-of-line, so the inline initializer expression is optional, yes.

Another thing that isn’t clear is what happens when a property with a behavior is set within the initializer of the containing type:

struct S {
  var [observed] s: String
  init(s: String) {
    // What happens here? Is the behavior’s “set” accessor called?
    // This may not always be desirable, as in the case of “observed"
    self.s = s
  }
}

Just like today, assignments within the initializer would bypass the behavior and directly initialize the storage.

Thanks for clarifying.

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.