[Proposal] Property behaviors

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?

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

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.

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?

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?

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.

Thank you

···

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

I've revised my proposal again based on more feedback. Syntax changes include:

- adopting John McCall's declaration-follows-use syntax for behavior declarations. I think this looks nice and provides a reasonable framework for binding all the fiddly bits of a property, such as its type, name, and initializer.
- 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.

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 name binding as future extensions. Joe Pamer raised some design and technical challenges around how type inference should work with behaviors too, which I think deserve focused discussion, so I'm sidestepping those issues by starting out saying properties with behaviors always need an explicit type. Here's the updated proposal:

Property behaviors · GitHub

And for reference, previous iterations:

Property behavior declarations · GitHub
Swift property behaviors · GitHub

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

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.

-Thorsten

···

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

This is an excellent update. Simplifying the proposal provides a strong starting point without foreclosing solutions to some of the thorny cases addressed in the original proposal. It will be good to have experience with the simpler behaviors when designing more powerful features later. (For example, I wonder if we'll discover that something like "behavior combinators" are the right way to solve behavior composition.)

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?

Regarding the foo.[resettable].reset() syntax, I agree that it's an improvement over the original proposal, both because it disambiguates with member access and because it provides better alignment of declaration and use. I'm still not thrilled with it. As noted, it's easy to confuse the new syntax with subscripting. It also occurs to me that reusing common punctuation for an uncommon* language feature might lead to confusion. Will people tend to make what they feel are educated guesses about what a behavior reference means and get in trouble? (*Apart from Kotlin, do other languages have property behaviors?)

So, I continue to prefer a sigil at both declaration and use. On the other hand, I don't know what sigil we'd use. It seems we're likely to spend the '#' sigil on selector, #sourceLocation, and friends; '@' is already spoken for; and I suppose that :speaking_head: would be right out. :-)

I see that the grammar for property-behavior-decl includes the ability to bind the property name, though the first bullet in the section "Bindings within Behavior Declarations" says that this would be a future extension. At the least, these should be reconciled. If the reconciliation favors punting this aspect, I'd like to see it mentioned in the Future Directions section.

That said, I'd love to see binding of the property name in the first iteration of behaviors. On the off chance that an example usage would be helpful, here's how we could begin implementing something like Omni's OAAppearance using a behavior:

public protocol PropertyListBackable {
    var backingPropertyList: [String: AnyObject] { get }
    func plistValueForKey<T>(key: String) -> T
}

public behavior var [plistBacked] propertyName: Value where Self: PropertyListBackable {
  get {
    return self.plistValueForKey(propertyName)
  }
}

extension PropertyListBackable {
    func plistValueForKey<T>(key: String) -> T {
        let plist = self.backingPropertyList
        let maybeResult = plist[key]
        guard let result = maybeResult as? T else {
            fatalError("Misconfigured property list")
        }
        return result
    }
}

class Appearance: PropertyListBackable {
    let backingPropertyList: [String: AnyObject]
    var [plistBacked] distance: Double    

    /// Returns the contents of property list resource <ClassName>.plist, where ClassName is the name of the receiver type.
    class func propertyListForType() -> [String: AnyObject] {
        let typeName = String(self)
        let maybePlistURL = NSBundle.mainBundle().URLForResource(typeName, withExtension: ".plist")
        guard let plistURL = maybePlistURL else {
            fatalError("Missing property list: \(typeName).plist")
        }
        
        let maybeResult = NSDictionary(contentsOfURL: plistURL)
        guard let result = maybeResult as? [String: AnyObject] else {
            fatalError("Malformed property list file: \(typeName).plist")
        }

        return result
    }
    
    init() {
        let plist = self.dynamicType.propertyListForType()
        backingPropertyList = plist
    }
}

class MyCoolAppAppearance: Appearance {
    var [plistBacked] faveSigil: String
}

let appearance = Appearance()
let distance = appearance.distance
print("distance: \(distance)") // distance: 3.0
        
let mcaAppearance = MyCoolAppAppearance()
let sigil = mcaAppearance.faveSigil
print("sigil: \(sigil)") // sigil: ❤️

Cheers,

Curt

···

-------------------------
Curt Clifton, PhD
Software Developer
The Omni Group
www.curtclifton.net

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

I've revised my proposal again based on more feedback. Syntax changes include:

- adopting John McCall's declaration-follows-use syntax for behavior declarations. I think this looks nice and provides a reasonable framework for binding all the fiddly bits of a property, such as its type, name, and initializer.
- 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.

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 name binding as future extensions. Joe Pamer raised some design and technical challenges around how type inference should work with behaviors too, which I think deserve focused discussion, so I'm sidestepping those issues by starting out saying properties with behaviors always need an explicit type. Here's the updated proposal:

Property behaviors · GitHub

And for reference, previous iterations:

Property behavior declarations · GitHub
Swift property behaviors · GitHub

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

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.

Deferred questions along that line of thought:
- Can a behavior optionally have an initial value, and provide its own default otherwise?
- Can a behavior optionally have an initial value, and require phase-1 initialization in an instance initializer otherwise?
- Does a behavior with no initializer requirements not allow initial values?

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?

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

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

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

Jordan

···

On Jan 22, 2016, at 16:33, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I've revised my proposal again based on more feedback. Syntax changes include:

- adopting John McCall's declaration-follows-use syntax for behavior declarations. I think this looks nice and provides a reasonable framework for binding all the fiddly bits of a property, such as its type, name, and initializer.
- 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.

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 name binding as future extensions. Joe Pamer raised some design and technical challenges around how type inference should work with behaviors too, which I think deserve focused discussion, so I'm sidestepping those issues by starting out saying properties with behaviors always need an explicit type. Here's the updated proposal:

Property behaviors · GitHub

And for reference, previous iterations:

Property behavior declarations · GitHub
Swift property behaviors · GitHub

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

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.

On a semi-related note, we should say up-front that accessing behaviors on properties (e.g. with `foo.lazy`) _cannot_ be used to extract the behavior type as a value. The only thing you can do with this is calling a method on it or accessing a property. So no saying `let foo = data.lazy`. This limitation would be more obvious if we use an alternative behavior accessor syntax.

-Kevin Ballard

···

On Thu, Dec 17, 2015, at 07:00 PM, Félix Cloutier via swift-evolution wrote:

I find that Michel Fortin's idea for subscript-less behaviors is very cool, but it also has an impact on composition. It seems to me that any behavior that doesn't implement a subscript would need to be the outer-most behavior, and it could not easily compose with any other subscript-less behavior.

Félix

> Le 17 déc. 2015 à 20:23:46, Joe Groff via swift-evolution <swift-evolution@swift.org> a écrit :
>
>
>> On Dec 17, 2015, at 3:22 PM, Russ Bishop <xenadu@gmail.com> wrote:
>>
>> My first assumption is that behaviors separate broadly into a couple of categories:
>>
>> 1. Eager and uncorrelated. The logging example is a good one. A logging behavior probably doesn’t care about anything else, it just wants to run as early as possible to write the log output.
>> 2. Ordered or correlated. These have composability issues.
>> 3. Un-eager. didSet/willSet kind of behaviors that want to run after all behaviors of type #2 have run.
>>
>> I’m trying to think if there is a way to declare the kind of behavior you have and what that would mean for composability and overriding because the compiler would be free to run all behaviors of type #1 first (unordered), then a single #2 behavior, then all the #3 behaviors (unordered), reducing the problem to specifying how ordered behaviors… er… “behave”. Perhaps in that case you’ll just have to manually implement a behavior that calls the desired behaviors.
>>
>> For overriding, as long as there is only one ordered behavior involved, all the other behaviors can execute in the appropriate “phase” without issue (all inherited eager behaviors first, etc).
>
> This is a great analysis. Kevin made a similar observation. If we went in the direction of a dedicated `behavior` declaration, then it'd be reasonable to declare the behavior's kind up front, which would influence its composition behavior.
>
> -Joe
>
> _______________________________________________
> swift-evolution mailing list
> 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

It looks to me that the only benefit this has versus IOUs is you can use a `let` instead of a `var`. It's worth pointing out that this actually doesn't even replace IOUs for @IBOutlets because it's commonly useful to use optional-chaining on outlets for code that might run before the view is loaded (and while optional chaining is possible with behavior access, it's a lot more awkward).

There are several advantages over IUO that I see. The behavior can ensure that a mutable delayed var never gets reset to nil, and can dynamically enforce that an immutable delayed var/let isn't reset after it's been initialized. It also communicates intent; as I mentioned to Matthew in another subthread,

Another benefit is that this allows elimination of an entire use-case for IOUs. This makes it more appetizing and possible to push IOUs further off into a corner. I’m personally interested in the idea of IOUs evolving into something that isn’t a first class type - instead they could only exist in argument and return types. Eliminating a common reason that people use them for properties would help with that.

+1 to this. Anything that replaces potentially unsafe patterns with safer patterns is a step in the right direction. That's why I think it's so important to have compiler support for delayed properties one way or another.

Matthew

···

Sent from my iPad

On Dec 17, 2015, at 7:05 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 17, 2015, at 4:36 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I think I agree with you. On a Synchronized<T>, all you have to do is implement a get<U>(access: T -> U) -> U to get almost the same syntax that Michel suggested.

(Can you do object { /* closure */ } or even object { /* closure */ } if you have a subscript that only accepts a closure? That could be interesting...)

On a semi-related note to your semi-related note, I'm not sure how I feel about the "foo.lazy" syntax because of the name resolution problems. I'm also not a fan of "`foo.lazy`"; I don't know any language that forces you to use the identifier escape to access built-in features.

In general, I think that accessing the behavior object should be restricted to the private scope. IMHO, if we want to restrict usage on behavior objects, the most obvious thing to do is to use a call-like syntax (since calls aren't assignable).

I'm thinking of something like `<lazy>(foo)` and `var<lazy> foo: Int` (for symmetry) to access the behavior object. I don't think that any expression can start with a < right now, and this would allow an independent namespace for behaviors. I don't have a strong opinion on that though.

Additionally, if behaviors become types instead of keywords, perhaps even within their own namespace they should follow regular type naming conventions. This could reduce namespace friction (you could always refer to them through their qualified name if they were shadowed).

···

Le 17 déc. 2015 à 22:47:48, 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.

On a semi-related note, we should say up-front that accessing behaviors on properties (e.g. with `foo.lazy`) _cannot_ be used to extract the behavior type as a value. The only thing you can do with this is calling a method on it or accessing a property. So no saying `let foo = data.lazy`. This limitation would be more obvious if we use an alternative behavior accessor syntax.

-Kevin Ballard

On Thu, Dec 17, 2015, at 07:00 PM, Félix Cloutier via swift-evolution wrote:

I find that Michel Fortin's idea for subscript-less behaviors is very cool, but it also has an impact on composition. It seems to me that any behavior that doesn't implement a subscript would need to be the outer-most behavior, and it could not easily compose with any other subscript-less behavior.

Félix

Le 17 déc. 2015 à 20:23:46, Joe Groff via swift-evolution <swift-evolution@swift.org> a écrit :

On Dec 17, 2015, at 3:22 PM, Russ Bishop <xenadu@gmail.com> wrote:

My first assumption is that behaviors separate broadly into a couple of categories:

1. Eager and uncorrelated. The logging example is a good one. A logging behavior probably doesn’t care about anything else, it just wants to run as early as possible to write the log output.
2. Ordered or correlated. These have composability issues.
3. Un-eager. didSet/willSet kind of behaviors that want to run after all behaviors of type #2 have run.

I’m trying to think if there is a way to declare the kind of behavior you have and what that would mean for composability and overriding because the compiler would be free to run all behaviors of type #1 first (unordered), then a single #2 behavior, then all the #3 behaviors (unordered), reducing the problem to specifying how ordered behaviors… er… “behave”. Perhaps in that case you’ll just have to manually implement a behavior that calls the desired behaviors.

For overriding, as long as there is only one ordered behavior involved, all the other behaviors can execute in the appropriate “phase” without issue (all inherited eager behaviors first, etc).

This is a great analysis. Kevin made a similar observation. If we went in the direction of a dedicated `behavior` declaration, then it'd be reasonable to declare the behavior's kind up front, which would influence its composition behavior.

-Joe

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

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

We definitely can't have behaviors storing closures. Any closure that references `self` would then become a retain cycle.

If we could somehow enforce `unowned` capture of `self`, that wouldn’t be a problem.

And of course for value types the closure would have captured an outdated copy of self.

Though this would be.

Perhaps the initializer closure should take `self` as a parameter, and so should any behavior method (at least as an option). So you would write `reset<Container>(inout container: Container)`, but you would call `propertyName.lazy.reset()`.

···

--
Brent Royal-Gordon
Architechies

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.

If it turns out it does not work with the final design of behaviours, it won't be too bad I guess. But I think it'd be a better fit as a behaviour than as a type.

···

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.

--
Michel Fortin
michel.fortin@michelf.ca
https://michelf.ca

@Joe could you elaborate on the access pattern for these properties? Are you getting back a boxed object every time or is there some sort of magic going on so the caller thinks its getting the value inside the box but still has some way to access methods on the box (say reset for the lazy property)

There's no boxing, the behavior effectively just inserts itself in the property chain. It would still act as if you had defined a backing property yourself:

var _backingProperty: Behavior<T>
var property: T {
  get {
    return _backingProperty.getBehavior()
  }
  set {
    _backingProperty.setBehavior(newValue)
  }
}

It's true that, if the behavior is implemented in terms of get/set accessors, that this may introduce temporary copies while drilling down into the property. We have other accessor patterns we allow that can avoid this inefficiency that we would use in production implementations of things like `lazy` and `delayed`.

-Joe

···

On Dec 21, 2015, at 10:28 AM, Tal Atlas <me@tal.by> wrote:

On Mon, Dec 21, 2015 at 1:04 PM Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I really like the direction this is heading Joe! I agree it feels a lot nicer. It seems like the right long-term solution to me.

Making behaviors an explicit construct in the language may lead to possibilities in the future that we cannot today which would not exist with the ad-hoc approach.

On Dec 21, 2015, at 11:23 AM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I played around a bit with the idea of a special behavior declaration. I think it feels a lot nicer, though it also feels like a much bigger language change if we go this route. Inside the declaration, you need to specify:
- what accessors the behavior supports, to be implemented by properties using the behavior,
- if the behavior controls storage, what that storage is, and what initialization logic it requires,
- if the behavior requires an initializer, and whether that initializer is used eagerly at property initialization or deferred to later, and
- what operations the behavior offers, if any.

Here's a quick sketch of how a behavior declaration could look. As a strawman, I'll use 'var behavior' as the introducer for a property behavior (leaving the door open to 'func behavior', 'struct behavior', etc. in the possible future). If you were going to reinvent computed properties from whole cloth, that might look like this:

var behavior computed<T> {
  // A computed property requires a `get` and `set` accessor.
  accessor get() -> T
  accessor set(newValue: T)

  // Accessors for the property
  get { return get() }
  set { set(newValue) }
}

lazy might look something like this:

var behavior lazy<T> {
  // lazy requires an initializer expression, but it isn't
  // used until after object initialization.
  deferred initializer: T

  // The optional storage for the property.
  var value: T?

  // Initialize the storage to nil.
  init() {
    value = nil
  }

  // Accessors for the property.
  mutating get {
    if let value = value {
      return value
    }
    // `initializer` is implicitly bound to the initializer expr as a
    // `@noescape () -> T` within the behavior's members.
    let initialValue = initializer()
    value = initialValue
    return initialValue
  }

  set {
    value = newValue
  }

  // clear() operation for the behavior.
  mutating func clear() {
    value = nil
  }
}

Some behaviors like `lazy` and `resettable` want to take control of the storage to manage their semantics, but many behaviors are adapters independent of how the underlying behavior behaves. These kinds of behavior are easy to compose with other behaviors and to override base class properties with. You could use inheritance-like syntax to indicate a "wrapping" behavior like this, and commandeer `super` to refer to the underlying property. For instance, `synchronized`:

var behavior synchronized<T>: T {
  get {
    return sync { return super }
  }
  set {
    return sync { return super }
  }
}

or `observing` didSet/willSet:

var behavior observing<T>: T {
  accessor willSet(oldValue: T, newValue: T) { }
  accessor didSet(oldValue: T, newValue: T) { }

  get { return super }
  set {
    let oldValue = super
    willSet(oldValue, newValue)
    super = newValue
    didSet(oldValue, newValue)
  }
}

If you want to refer back to the containing `self`, we could support that too, and by treating behavior functions specially we should be able to maintain coherent semantics for backreferencing value types as well. Implementing `synchronized` with a per-object lock could look like this:

var behavior synchronizedByObject<T>: T where Self: Synchronizable {
  get {
    return self.withLock { return super }
  }
  set {
    return self.withLock { return super }
  }
}

(though the juxtaposed meanings of `super` and `self` here are weird together…we'd probably want a better implicit binding name for the underlying property.)

-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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

I've been thinking a little more about the usage. If you don't want to do the @ thing, suppose you instead use a behavior by specifying it in the block:

  var foo: Bar {
    lazy { Bar() } // This property will be lazy, and initialized/re-iniitalized with this block
    synchronized // This property will be synchronized (you could provide a block to give a queue, but the default's fine here)
    logged // Calls are logged
  }

You might only be able to specify one closure per behavior, so willSet and didSet would have to be different behaviors, but I don't think that's necessarily a bad thing—it'd be nice if `observable` could be saved for arbitrary third-party observation (that is, a KVO replacement).

It's even possible that you could implement `get` and `set` themselves as behaviors! That would be pretty neat.

···

--
Brent Royal-Gordon
Architechies

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.

-Dave

···

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.

Yeah. There are behaviors where exporting the functionality is important, so I think we need to preserve the ability to make them public. In addition to `KVOable`, `resettable` also meets this criterion—there's no point in declaring a resettable property if clients can't reset it. Maybe visibility is a trait of the behavior, though, and not necessarily of the property declarations using it, since something like 'KVOable' is almost always something you want to publish, whereas `lazy` is almost always an implementation detail. However, I could see people wanting to also publish things like `lazy`, `copying`, or `synchronized` as documentation of semantics, even if they aren't directly useful as API.

-Joe

···

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

I actually think that behavior instances should probably be private, and there should be no way to change that. Behaviors are inherently an implementation detail, and you should not be exposing them directly to users of your type. Instead, you should expose just the operations you expect to be needed through your own calls, which you can reimplement as needed.

I'm leaning in this direction too. The idea of exposing the behavior itself as a value seems wrong for almost any client. "foo.prop.reset()" is slightly nicer than "foo.resetProp()", but not if it conflicts with a 'reset' that's already on the property type. "foo.prop@behavior.reset()" certainly isn't an improvement.

I've been thinking about this more, and I think there's at least one exception: if you introduce a behavior that allows for arbitrary observers to watch a property (basically a KVO replacement), you want a standard way to access that functionality, rather than having to declare cover methods for everything. So maybe the visibility of the behavior should be controllable, but by the behavior itself, not the property it's applied to.

Notable points of confusion:

it's confusing to me that `self` is the containing type and the behavior name is the "behavior's self".

Others have noted this too. Would it be less confusing if one had to explicitly name the "container" as a member, e.g.:

var behavior synchronized {
  container var parent: Synchronizable
  base var value: Value

  get {
    return parent.withLock { value }
  }
  set {
    parent.withLock { value = newValue }
  }
}

An `init` parameter covers use cases where the initializer expression is used only during initialization, but doesn't let you use the initializer after initialization, which is necessary for `lazy`, `resettable`, and other use cases. Even with @autoclosure, it seems to me that, without `initializer`, we'd need to allocate per-property storage for the initializer expression to use it later, which is something I'd like to avoid.

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

One benefit of making the container binding explicit is that we can avoid referencing the container `inout` in behavior implementations if it isn't used (and we're willing to say that a behavior can't resiliently adopt a container requirement if it wasn't originally written with one).

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

The initializer expression and accessors should manifest themselves as methods on the containing type. There shouldn't be any implicit per-instance storage overhead to using behaviors, and the abstraction should be optimizable by inlining and generic specialization in optimized builds.

I see (after reading it) that `var behavior foo<Value>: Value` means that foo "applies to"/"wraps" Value, but I find it confusing to use a syntax more typically associated with "extends" or "implements" or "is a".

Would it be less confusing if the type of the property were implicit? In discussion with Brent, I suggested a model where you say:

var behavior foo { ... }

and if you want to constrain the types of properties that can instantiate the behavior, you use a where clause:

var behavior foo where Value: NSCopying { ... }

which optimizes the common case (no constraint), and might be easier to read.

Questions:

Can a behavior have generic parameters that can't be inferred? Could I write, say, [fooable<Int>]?

No, the generic parameters are only used to generalize the property type.

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

What is the tradeoff between `eager` and `deferred`? Is it "only" that `deferred` side effects happen at the mercy of the behavior?
If so, isn't it a problem that behaviors aren't intrinsically explicit about whether they defer initialization? I can see that causing very subtle bugs.

The tradeoff is that an 'eager' initialization can be used in `init`, but that means that an initializer expression can't refer to `self`, because `self` is not fully initialized. This is how initializer expressions always work today:

struct X {
  var x = 0, y = 1
  var z = x + y // Error
}

A deferred initialization can only be evaluated *after* init, but because of that, it can refer to `self`, which people would like to be able to do with `lazy` (but currently can't):

struct X {
  var x = 0, y = 1
  lazy var z = x + y // Theoretically OK
}

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

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

Concerns:

It looks like if you had a [resettable, observable] property, calling resettable.reset() would change the value from under `observable`'s feet.

True. An unfortunate consequence of these things being user-defined is that there will always be "wrong" orderings of them. I'm not sure how much we can do about that.

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

It occurs to me that a bound `initializer` is in a sense a lot like a sugared accessor. Both `lazy` and `resettable` could be designed in terms of accessor requirements, at the cost of some sugar, and some redundancy in the case of `resettable`:

var behavior lazy {
  var value: Value?
  accessor initialValue() -> Value

  mutating get {
    if let value = value { return value }
    let val = initialValue()
    value = val
    return val
  }
}

var [lazy] x: Int {
  initialValue { return 679 }
}

var behavior resettable {
  base var value: Value
  accessor resetValue() -> Value

  mutating func reset() {
    value = resetValue
  }
}

var [resettable] y: Int = 1738 {
  resetValue { return 1738 }
}

If we could live with that, we could avoid the complexity of the deferred/eager initializer, and `resettable` could also be made composable by a base property.

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

Not sure what you mean, exactly. Seems like that would warn on a lot of legitimate use cases.

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

Another oversight; thanks!

-Joe

···

On Jan 13, 2016, at 8:44 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

Le 13 janv. 2016 à 23:08:24, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> a écrit :

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.

-Chris

···

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.

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.

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?

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.

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

···

--
Brent Royal-Gordon
Architechies

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

## 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 Jan 14, 2016, at 11:33 AM, plx via swift-evolution <swift-evolution@swift.org> wrote:

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

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

As long as we're bike shedding syntax, I'll add my two cents.

I'm not a fan of typed-based disambiguation of behavior vs. member references, as in foo.resettable.reset(). Behaviors are a very different thing than members. The proposed behavior declaration syntax makes this very clear. I think the reference syntax should do the same.

I also dislike the foo.[resettable].reset() alternative. This is primarily an aesthetic judgment—it looks like punctuation soup—but is also easy to read as the subscriptions foo[resettable] on a glance.

The double-dot approach is maybe somewhat better, but I worry that it's confusing with the range operators. (That may just be because of my experience in other languages that use '..'. Swift's '...' and '..<' are probably sufficiently different.)

It seems to me that a sigil for both declaration and access would be clearer:

// declaration
var #resettable foo: Int

// use
foo#resettable.reset()

// behavior composition
var #(lazy, resettable) bar: Int

This makes declaration and use correspond, keeps the syntax lightweight in the common case of a single behavior, and uses an ordered, tuple-like syntax for behavior composition.

Thanks for the updated proposal, Joe. I'm very excited to see where it goes.

Cheers,

Curt

Curt Clifton, PhD
Software Developer
The Omni Group

···

On Jan 14, 2016, at 10:43 AM, Wallacy via swift-evolution <swift-evolution@swift.org> wrote:

I really liked this idea:
x..resettablet.reset()
I can see that when typing the second dot, the autocomplete offering me all behaviors available. ;)

And thinking a little, may the `pipe` can be used to declaration :

var |lazy| number = 1243
The Square brackets is good, but feels like array.

And for multiple behaviors, we can use the ">" to infer the "direction" of the "composability":

var |lazy>observed| observedLazy = expensiveExpression() {
  didSet { print("\(oldValue) => \(observedLazy)") }
}

Em qui, 14 de jan de 2016 às 02:08, Joe Groff via swift-evolution <swift-evolution@swift.org> escreveu:

On Jan 13, 2016, at 7:13 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

I started by reading the examples and I was very confused. This suggests to me that if you've never seen a var behavior before, you are going to wonder what the hell is going on. :-)

This is good feedback, thanks!

Notable points of confusion:

it's confusing to me that `self` is the containing type and the behavior name is the "behavior's self".

Others have noted this too. Would it be less confusing if one had to explicitly name the "container" as a member, e.g.:

var behavior synchronized {
  container var parent: Synchronizable
  base var value: Value

  get {
    return parent.withLock { value }
  }
  set {
    parent.withLock { value = newValue }
  }
}

The `initializer` special field feels absolutely magic. Has anything else been considered, like an init param that has either a Value or an autoclosure returning one? (If we don't want to capture self, aren't we in for problems capturing self from accessors anyway?)

An `init` parameter covers use cases where the initializer expression is used only during initialization, but doesn't let you use the initializer after initialization, which is necessary for `lazy`, `resettable`, and other use cases. Even with @autoclosure, it seems to me that, without `initializer`, we'd need to allocate per-property storage for the initializer expression to use it later, which is something I'd like to avoid.

I see (after reading it) that `var behavior foo<Value>: Value` means that foo "applies to"/"wraps" Value, but I find it confusing to use a syntax more typically associated with "extends" or "implements" or "is a".

Would it be less confusing if the type of the property were implicit? In discussion with Brent, I suggested a model where you say:

var behavior foo { ... }

and if you want to constrain the types of properties that can instantiate the behavior, you use a where clause:

var behavior foo where Value: NSCopying { ... }

which optimizes the common case (no constraint), and might be easier to read.

Questions:

Can a behavior have generic parameters that can't be inferred? Could I write, say, [fooable<Int>]?

No, the generic parameters are only used to generalize the property type.

What is the tradeoff between `eager` and `deferred`? Is it "only" that `deferred` side effects happen at the mercy of the behavior?
If so, isn't it a problem that behaviors aren't intrinsically explicit about whether they defer initialization? I can see that causing very subtle bugs.

The tradeoff is that an 'eager' initialization can be used in `init`, but that means that an initializer expression can't refer to `self`, because `self` is not fully initialized. This is how initializer expressions always work today:

struct X {
  var x = 0, y = 1
  var z = x + y // Error
}

A deferred initialization can only be evaluated *after* init, but because of that, it can refer to `self`, which people would like to be able to do with `lazy` (but currently can't):

struct X {
  var x = 0, y = 1
  lazy var z = x + y // Theoretically OK
}

Concerns:

It looks like if you had a [resettable, observable] property, calling resettable.reset() would change the value from under `observable`'s feet.

True. An unfortunate consequence of these things being user-defined is that there will always be "wrong" orderings of them. I'm not sure how much we can do about that.

Comments:

While it might be true that square brackets work better with other declarations that could eventually have behaviors, "var behavior" doesn't really lend itself to that kind of extensibility. Are we steering towards "func behavior", "class behavior", etc? Is it a problem if we are?

Possibly. Note that square brackets are necessary even only for `var`, because you can declare a destructuring binding `var (x, y) = tuple`.

I'd like to point out that the memoization example is a let variable with a behavior, which is explicitly forbidden by the current proposal.

Thanks, missed that.

Finally, I would like to throw the idea of "foo..resettable" to access foo's resettable behavior (or foo..reset() doing optionally-qualified lookup on foo's behavior methods).

Not a bad suggestion.

Thanks again for the feedback!

-Joe

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

And how about:

instead that:

var behavior Foo: Int {
  // behavior body
}

that:

struct Foo<Int>: behavior{
  // behavior body
}

Or something like that....

···

Em ter, 19 de jan de 2016 às 02:03, Joe Groff via swift-evolution < swift-evolution@swift.org> escreveu:

On Jan 18, 2016, at 7:57 PM, davesweeris@mac.com wrote:

Can we use “#” or something other than “.”, both to make it clear we’re
referencing a behavior rather than a property, and so that we can avoid
naming collisions when x has a property “lazy” *and* is declared with the
lazy behavior?
x.lazy.clear() // a property named “lazy” which has a clear() method
x#lazy.clear() // the behavior named “lazy”
I kinda like “#” (or whatever) for declaring them, too:
var x #(lazy) = 6

I’m a definite +1 on the concepts behind your proposal, but there are
really two distinct things going on here: behaviors that mess with the
type, and those that don’t. I mean, if you define your Lazy “behavior” like
this:
struct Lazy<T> {
    private let closure: () -> T
    private var storage: T? = nil
    var value: T {
mutating get {
    if storage == nil { storage = closure() }
            return storage!
}
mutating set {
    storage = newValue
}
    }
    init(_ closure: () -> T) {
        self.closure = closure
    }
}

then the only difference between
lazy var foo = {
    return 4
}
and
var foo = Lazy {
    return 4
}
is that in the former case, the property is implicitly accessed:
var bar = foo
foo = 10
and in the latter, we have to access it explicitly:
var bar = foo.value
foo.value = 10

If we expose the mechanism currently used by `T!` to convert between
itself and T, and allow structs, enums, classes, and maybe even protocols
to provide type accessors as well as property accessors, Lazy, Optional,
and ImplicitlyUnwrappedOptional could then be implemented like this:
struct Lazy<T> {
    // or whatever the magic syntax is
    mutating static get { () -> T in // $0 is an instance of Lazy<T>
if $0.value == nil { $0.value = $0.closure() }
return $0.value!
    }
    mutating static set { (newValue:T) in $0.value = newValue }

    private let closure: () -> T
    private var value: T? = nil
    init(_ closure: () -> T) {
        self.value = closure
    }
}
enum Optional<T> {
    // no need for a custom getter, since you have to manually unwrap
optionals
    mutating static set { (newValue: T) in $0 = .Some(newValue) } // $0
is an instance of Optional<T>
    mutating static set { () in $0 = .None }

    case None
    case Some(T)
    init() { self = .None }
    init(nilLiteral: ()) { self = .None }
    init(_ value: T) { self = .Some(value) }
}
enum ImplicitlyUnwrappedOptional<T> {
    mutating static get { () -> T in // $0 is an instance of
ImplicitlyUnwrappedOptional<T>
        switch $0 {
        case .None: fatalError("Error unwrapping
ImplicitlyUnwrappedOptional")
        case .Some(let value): return value
        }
    }
    mutating static set { (newValue: T) in $0 = .Some(newValue) }
    mutating static set { () in $0 = .None }

    case None
    case Some(T)
    init() { self = .None }
    init(nilLiteral: ()) { self = .None }
    init(_ value: T) { self = .Some(value) }
}

One of the stated goals of Swift 3 is to move stuff out of the core
language and into the standard library, right? Well, this would get rid of
all the “magic” behind Lazy, Optionals, and ImplicitlyUnwrappedOptionals
*and* it’d let us implement our own type-related custom behaviors. It’s a
win-win!

Getting rid of magic is definitely a goal, but I think we want to go in
the *opposite* direction. Implicit Optional promotion creates lots of
problems, and ImplicitlyUnwrappedOptional's behavior can be hard to
predict. We plan to reform the type system around both kinds of Optional
soon. Behaviors also give us an opportunity to eliminate at least one case
for IUO by modeling delayed initialization as a behavior. I'd prefer not to
introduce more messy implicit promotions.

-Joe

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