[Proposal] Property behaviors

I’ve really been looking forward to this proposal and I like this idea in general. I may have comments about specific details after giving it more thought.

I have one significant concern that is worth mentioning immediately. I would consider it extremely unfortunate if a delayed property behavior was considered sufficient in support of multi-phase initialization. Using a property behavior for that purpose gives no guarantee that the value is actually initialized at some point during instance initialization. IMO this is a major flaw. A runtime error might occur when accessing a delayed property after initialization is supposed to be complete.

Delayed property behaviors may have appropriate use cases but IMO they are not an adequate substitute for something that provides stronger guarantees for the common case of multi-phase initialization.

I very strongly prefer to see direct language support for multi-phase initialization. The compiler could provide most of the initialization guarantees it does for regular let properties. It could enforce single assignment in the initializer body and could prevent the initializer body itself from reading the delayed property before assignment.

Even without any compiler support, I think implementing (delayed) as a behavior will be an improvement over the status quo. You get none of these guarantees with a `var T!` property either, and you also lose safety from some jerk resetting the property to `nil` or changing the property again after it's supposed to stop being mutated. Being a library feature also doesn't preclude a `delayed` behavior from offering diagnostics in common cases. One of our design goals for the SIL layer was to support dataflow-sensitive diagnostics like this; even though integers and arithmetic are implemented as library features, we run optimization passes that fold literal constant arithmetic down and raise errors when constant values aren't able to statically fit in their containing types. We could do something similar for a production-quality `(delayed)` implementation in the standard library.

Maybe I came across a bit too strongly. I absolutely agree that a delayed property behavior would be a huge improvement over current state. If the property behavior worked in conjunction with built-in initializer diagnostics that would address my concern. Maybe I overlooked how closely the compiler and parts of the library are working together. :)

The only guarantee that may not be possible is method calls to self during the second phase of initialization, but prior to assignment of all delayed properties (whether directly or by passing self to another instance) are potentially dangerous if they caused a read to a delayed property. The potential for error is significantly narrower with direct language support. As this is a very common use case (possibly the most common use case for delayed properties) I strongly believe it warrants direct language support.

Yeah, statically guaranteeing initialization phase order across method calls is tricky if you don't have type state or linear types, which until recently have been fairly esoteric features (though Rust is making the latter more prominent). I think implementing `delayed` as proposed still improves the static and dynamic safety of multi-phase initialization over what we have, and it doesn't shut the door to further refinement in the future.

I agree it is an improvement. I just don’t want it to be the end game.

Ideally it would come with basic initializer diagnostics that are easy to implement from the start. That would limit any issues to initialization time which make me very happy and be a dramatic improvement over current state.

Refinements to improve safety of phase two of initialization would be welcome but certainly not an immediate concern.

Matthew

···

On Dec 17, 2015, at 3:02 PM, Joe Groff <jgroff@apple.com> wrote:

On Dec 17, 2015, at 11:12 AM, Matthew Johnson <matthew@anandabits.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).

Russ

···

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

As for combining behaviours... Someone should have to do the work of combining them properly by creating a new behaviour type for the combined behaviour. To make this easier syntactically, we could have a behaviour combination function that would create a combined behaviour type. That function could be overloaded to support only behaviours that are compatible with each other.

Overall, this looks very good. But there is almost nothing said about derived classes and overriding. I very often override a property just so I can add a `didSet` clause, and if `didChange` was implemented I'd use it too. I'd very much like if those property behaviours could be compatible with overriding (where it makes sense). The way I see it, the behaviour should be able tell the compiler whether the overriding accessor should either replace or be prepended or appended to the overridden closure from the base class, or if overriding is simply not allowed.

Yeah, composition of behaviors and overloading are two areas I'm still working through. Composition is tricky, since it's not scalable to require overloads for all possible M!/N! combinations of behaviors. Any decoupled composition behavior is going to have ordering problems too—lazy-before-synchronized and synchronized-before-lazy would both be accepted, but one would be broken (unless you could blacklist problematic combinations, which also seems like an exponential-order problem).

-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 thorough feedback, Kevin! There's a lot to work through here, but here are some quick responses:

I'm a bit concerned about passing in the Container like this. For class types it's probably fine, but for value types, it means we're passing a copy of the value in to the property, which just seems really weird (both because it's a copy, and because that copy includes a copy of the property).

True, it's fundamentally problematic for property behaviors to try to reach back up through containing value types. It's already been noted that passing all the parameters all the time to both the behavior function and subscript implementation is a potential code size problem as well. Allowing the operations to only take the parameters they're interested in would help here.

Also the only example you gave that actually uses the container is Synchronized, but even there it's not great, because it means every synchronized property in the class all share the same lock. But that's not how Obj-C atomic properties work, and there's really no benefit at all to locking the entire class when accessing a single property because it doesn't provide any guarantees about access to multiple properties (as the lock is unlocked in between each access).

FWIW, the way Obj-C atomic properties work is for scalars it uses atomic unordered loads/stores (which is even weaker than memory_order_relaxed, all it guarantees is that every load sees a value that was written at some point, i.e. no half-written values). For scalars it calls functions objc_copyStruct(), which uses a bank of 128 spinlocks and picks two of them based on the hash of the src/dst addresses (there's a comment saying the API was designed wrong, hence the need for 2 spinlocks; ideally it would only use one lock based on the address of the property because the other address is a local stack value). For objects it calls objc_getProperty() / objc_setProperty() which uses a separate bank of 128 spinlocks (and picks one based on the address of the ivar). The getter retains the object with the spinlock held and then autoreleases it outside of the spinlock. The setter just uses the spinlock to protect writing to the ivar, doing any retains/releases outside of it. I haven't tested but it appears that Obj-C++ properties containing C++ objects uses yet another bank of 128 spinlocks, using the spinlock around the C++ copy operation.

Ultimately, the point here is that the only interesting synchronization that can be done at the property level is unordered atomic access, and for any properties that can't actually use an atomic load/store (either because they're aggregates or because they're reference-counted objects) you really do want to use a spinlock to minimize the cost. But adding a spinlock to every single property is a lot of wasted space (especially because safe spinlocks on iOS require a full word), which is why the Obj-C runtime uses those banks of spinlocks.

It seems like all of this could still be captured by a behavior, if you gave synchronized enough overloads for all the special cases.

In any case, I guess what I'm saying is we should ditch the Container argument. It's basically only usable for classes, and even then it's kind of strange for a property to actually care about its container.

var `foo.lazy` = lazy(var: Int.self, initializer: { 1738 })

This actually won't work to replace existing lazy properties. It's legal today to write

    lazy var x: Int = self.y + 1

This works because the initializer expression isn't actually run until the property is accessed. But if the initializer is passed to the behavior function, then it can't possibly reference `self` as that runs before stage-1 initialization.

Referencing `self` in `lazy` initializers is hit-or-miss today. This seems like another problem that could be solved by making the behavior function take only the parameters it's interested in. If lazy() doesn't accept an initializer, but Lazy's subscript operator does, then you know the initializer isn't consumed during init and can safely reference `self`.

A property behavior can model "delayed" initialization behavior, where the DI rules for var and let properties are enforced dynamically rather than at compile time

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,

let (delayed) x: Int
...
self.x.delayed.initialize(x)
...

Allowing `let` here is actually a violation of Swift's otherwise-strict rules about `let`. Specifically, Delayed here is a struct, but initializing it requires it to be mutable. So `let (delayed) x: Int` can't actually ever be initialized. You could make it a class, but that's a fairly absurd performance penalty for something that provides basically the same behavior as IOUs. You do remark later in detailed design about how the backing storage is always `var`, which solves this at a technical level, but it still appears to the user as though they're mutating a `let` property and that's strictly illegal today.

I think the right resolution here is just to remove the `letIn` constructor and use `var` for these properties. The behavior itself (e.g. delayed) can document write-once behavior if it wants to. Heck, that behavior was only enforcing write-once in a custom initialize() method anyway, so nothing about the API would actually change.

True, I'm on the fence as to whether allowing `let`s to be implemented with user code is a good idea without an effects model. Taking away the `let` functionality and having a separate `immutableDelayed` behavior is a more conservative design for sure.

Resettable properties

The implementation here is a bit weird. If the property is nil, it invokes the initializer expression, every single time it's accessed. And the underlying value is optional. This is really implemented basically like a lazy property that doesn't automatically initialize itself.

Instead I'd expect a resettable property to have eager-initialization, and to just eagerly re-initialize the property whenever it's reset. This way the underlying storage isn't Optional, the initializer expression is invoked at more predictable times, and it only invokes the initializer once per reset.

The problem with this change is the initializer expression needs to be provided to the behavior when reset() is invoked rather than when the getter/setter is called.

Yeah, the implementation here is admittedly contorted around the fact reset() can't receive the initializer expression directly, and we don't want to store it anywhere in the property.

Property Observers need to somehow support the behavior of letting accessors reassign to the property without causing an infinite loop. They also need to support subclassing such that the observers are called in the correct order in the nested classes (and again, with reassignment, such that the reassigned value is visible to the future observers without starting the observer chain over again).

Yeah, overriding is an interesting problem that needs to be solved. An observer can at least reliably poke through the property by assigning to the `observable` behavior's property, which is more reliable than our current model.

Speaking of that, how do behaviors interact with computed properties? A lazy computed property doesn't make sense (which is why the language doesn't allow it). But an NSCopying computed property is fine (the computed getter would be handed the copied value).

A behavior decides what kinds of property it supports by overload resolution. If you accept an `initializer`, you act like a stored property. If you accept `get` and `set` bodies, you can act like a computed property. You can also accept your own custom set of accessors if that's necessary.

The backing property has internal visibility by default

In most cases I'd recommend private by default. Just because I have an internal property doesn't mean the underlying implementation detail should be internal. In 100% of the cases where I've written a computed property backed by a second stored property (typically named with a _ prefix), the stored property is always private, because nobody has any business looking at it except for the class/struct it belongs to.

Although actually, having said that, there's at least one behavior (resettable) that only makes sense if it's just as visible as the property itself (e.g. so it should be public on a public property).

And come to think of it, just because the class designer didn't anticipate a desire to access the underlying storage of a lazy property (e.g. to check if it's been initialized yet) doesn't mean the user of the property doesn't have a reason to get at that.

So I'm actually now leaning to making it default to the same accessibility as the property itself (e.g. public, if the property is public). Any behaviors that have internal implementation details that should never be exposed (e.g. memoized should never expose its box, but maybe it should expose an accessor to check if it's initialized) can mark those properties/methods as internal or private and that accessibility modifier would be obeyed. Which is to say, the behavior itself should always be accessible on a property, but implementation details of the behavior are subject to the normal accessibility rules there.

I don't think we can default to anything more than internal. Public behaviors become an API liability you can never resiliently change, and we generally design to ensure that API publication is a conscious design decision.

A behavior declaration

This has promise as well. By using a declaration like this, you can have basically a DSL (using contextual keywords) to specify things like whether it's lazy-initialized, decorators, and transformers. Same benefits as the protocol family (e.g. good compiler checking of the behavior definition before it's even used anywhere), allows for code code-completion too, and it doesn't litter the global function namespace with behavior names.

The more I think about this, the more I think it's a good idea. Especially because it won't litter the global function namespace with behavior names. Behavior constructors should not be callable by the user, and behaviors may be named things we would love to use as function names anyway (if a behavior implements some functionality that is useful to be exposed to the user anyway, it can vend a type like your proposal has and people can just instantiate that type directly).

Agreed, I'm inclined to think a behavior decl is the way to go too.

-Joe

···

On Dec 17, 2015, at 3:44 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

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

···

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

Exactly. It's much better than having a `fatalError("Don't access directly")` in the subscript getter.

···

Le 17 déc. 2015 à 16:36, Joe Groff <jgroff@apple.com> a écrit :

On Dec 17, 2015, at 1:14 PM, Michel Fortin <michel.fortin@michelf.ca> wrote:

About synchronized access, it seems to me that it'd be advantageous if implementing subscript in a behaviour was optional. This way you can force access of a property through a synchronized block which will help avoid races:

  var (synchronized) data: (x: Int, y: Int, z: Int)

  func test() {
    data.synchronize { (data) in
      data.x += 1
      data.y += data.x
      data.z += data.y
    }
  }

Interesting. IIUC, if a behavior didn't supply a subscript operation, then the property could *only* be accessed through its behavior interface?

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

This language feature seemed complicated to me at first glance. Then I read through Kevin’s treatment, and now it seems tremendously complicated. :-)

Property behaviors seem to be moving in the direction of formalizing the language’s property model, in that they interact with the property’s interface (accessors and more), storage, inheritance, delegation (logging/willSet/didSet), polymorphism (covariance), and probably more. Since the scope is so large, maybe this proposal can be split into parts?

What kind of language features would be needed to implement the (language-implemented) behaviors that we have now, ignoring the compiler’s blessing of syntax to make the implementation transparent?

The obvious way to think of behaviors is structs that contain the property’s storage, with interface methods providing access. If we tried to implement everything in Joe’s proposal directly with generic structs, what problems would be hit?

The first problem is the initializer closure: capturing `self` is a bad idea, but if the initializer just took `self` as an argument (if safe to do so), then the initializer could simply be stored in the “property box” with the downside of increased storage. Making this type-safe seems to require that box type also be generic over the type of the containing object, which is interesting. Lazy<Value>, MemoizationBox<Value>, Delayed<Value>, and Resettable<Value> can be straightforwardly implemented this way.

Synchronized<Value> can be as well: if `self` is available to be passed to the initializer, then it is available to grab a lock from. Of course here we start hitting the composition problems: you can write either Lazy<Synchronized<Value>> or Synchronized<Lazy<Value>>. Like Kevin said, what you really want is dispatch_once-like behavior: a single control value which means “uninitialized”, “being initialized”, or “initialized”.

Observed<Value> is another hint of a problem: if I understand correctly (I am really a Swift newbie), willSet and didSet can be overridden. Modeling that by composing a struct seems very difficult. A list of closures? Or closures which call the next one in the chain?

Also, you potentially want to be able to override the *whole property*, which is also a problem since it involves storage changes. And what you may really want is to override the whole property except for the observers.

I guess I’m implying that it should be possible to implement something with similar semantics and performance characteristics to property behaviors in a library, neglecting all special syntax. That prospect alone seems very intimidating. I wonder if there is a set of language features that would allow a library implementation, while still allowing properties to be thought of as a single “thing”? Even if those features are never implemented, it might make it clearer what specific magic is required for property behaviors to function. A non-exhaustive list:

1) overriding storage in a subclass while still vending the original interface to people using the object via a super type or a protocol
2) methods in the composed type that are overridable in the container type (willSet/didSet)
3) initializer method/closure that neither captures `self` nor takes up space in the instance when stored (i.e. acts like a method of the container type)
4) the proposed safety when composing multiple boxes in potentially incompatible orders

I think exploring what those features would look like in isolation might be illuminating. Of course, they may turn out to be overly generic and only useful in the context of properties.

-John

Really grat and interesting!

I have a little question, would it allow to declare behaviours that accept only set and not gets? I've found myself some times wanting a "setter only" modifier for properties. It can be done with methods (see watchkit for example) but with this proposal seems like it would be nice to have it.

And to point a small concern, as Matthew mentioned, It seems like some of this behaviors could reduce the actual safety of immutability with lets, specially the delayed initialization. Obviously this is better than not having any solution but would be nice to have it in the stdlib going in hand with some compiler checks.

Looks pretty need writing "arbitrary" (in a specific form, informal protocol) code that the compiler uses to generate this. Looking forward for more freedom in this camp.

Cheers,
Alex

···

Sent from my iPad

On 17 Dec 2015, at 18:26, David Owens II via swift-evolution <swift-evolution@swift.org> wrote:

The functionality looks like it is something that is definitely required to reduce the boiler-plate that Swift currently requires. However, this seems like a specific instance of a more general problem. For example, I run into a similar problem with working with other constructs, such as enums, where I want to provide specific “behaviors” for them that is just a bunch of boiler-plate code.

It seems like this proposal could be a starting place to start flush out what a macro/preprocessor/boiler-plate-reducer would look like in Swift. As such, I would like to see a syntax that could be extended beyond properties. Maybe this system is limited in scope to only allow this generation in specific contexts, like this property behavior, especially to scope this proposal down.

The short of it: I like the idea and it seems expandable to future concerns if syntax like attributes are used. And like you mentioned, these could be generate errors when used in the wrong contexts, such as lacking @behavior_function, or its equivalent.

-David

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

A behavior declaration

This has promise as well. By using a declaration like this, you can have basically a DSL (using contextual keywords) to specify things like whether it's lazy-initialized, decorators, and transformers. Same benefits as the protocol family (e.g. good compiler checking of the behavior definition before it's even used anywhere), allows for code code-completion too, and it doesn't litter the global function namespace with behavior names.

The more I think about this, the more I think it's a good idea. Especially because it won't litter the global function namespace with behavior names. Behavior constructors should not be callable by the user, and behaviors may be named things we would love to use as function names anyway (if a behavior implements some functionality that is useful to be exposed to the user anyway, it can vend a type like your proposal has and people can just instantiate that type directly).

+1 to these comments. I think this option should at least be fleshed out further and receive serious consideration.

+1 for this proposal. I’m not clear on the syntax and need to take some time exploring it though.

As I read the code in the proposal, it once again blurred the lines for me between property and method. It often seems to me, at an intuitive / teaching level, a property is just semantic sugar for a particular method, combined with some secret sauce to manage the backing storage for the variable.

Someone (Doug?) mentioned getting some documentation out that would clarify the usage of property vs. method. I’d really like to see that for comparison against my own evolving heuristics. It would also be useful in discussing this proposal.

Hi everyone. Chris stole my thunder already—yeah, I've been working on a design for allowing properties to be extended with user-defined delegates^W behaviors. Here's a draft proposal that I'd like to open up for broader discussion. Thanks for taking a look!

-Joe

Swift property behaviors · GitHub

… snip …

Defining behavior requirements using a protocol
It’s reasonable to ask why the behavior interface proposed here is ad-hoc rather than modeled as a formal protocol. It’s my feeling that a protocol would be too constraining:

Different behaviors need the flexibility to require different sets of property attributes. Some kinds of property support initializers; some kinds of property have special accessors; some kinds of property support many different configurations. Allowing overloading (and adding new functionality via extensions and overloading) is important expressivity.
Different behaviors place different constraints on what containers are allowed to contain properties using the behavior, meaning that subscript needs the freedom to impose different generic constraints on its varIn/ letIn parameter for different behaviors.

Would a set of related protocols (given some suppositions above that we could identify categories of behaviors that have similar needs) be another option?

Instead of relying entirely on an informal protocol, we could add a new declaration to the language to declare a behavior, something like this:

behavior lazy<T> {
  func lazy(...) -> Lazy { ... }
  struct Lazy { var value: T; ... }
}

I do like the idea of a behavior declaration. I find this to be relatively easy to model mentally. It fits with Swift’s general use of the type system to achieve both power and safety.

When do properties with behaviors get included in the memberwise initializer of structs or classes, if ever? Can properties with behaviors be initialized from init rather than with inline initializers?

There’s a separate discussion that mentioned allowing better control of which initializers are generated or synthesized for a given struct. There’s also been mention of a “derived” feature for adding conformance without needing to supply a separate implementation. This question seems related to me - it would be ideal if Swift had a coherent way to declare something that did not need definition because it can be generated by the compiler. In this case, to declare that a property is part of memberwise initialization. `behavior lazy<T>: memberwise {` ?

You might be talking about the initialization discussion I was involved in a week or so ago. I'm working on a proposal that would allow for more flexible control over synthesized memberwise initialization. I'm hoping to have a draft ready soon.

Is your example here part of a behavior declaration for lazy which states that properties with the lazy behavior may be memberwise initialized? That's what it looks like to me. I think syntax like that would make sense. There are some behaviors which would need to opt out. Somewhat ironically, I think lazy is one of them as the whole point of it is that it is not initialized immediately, but rather on first access.

···

Sent from my iPad

On Dec 18, 2015, at 7:05 PM, Stephen Christopher via swift-evolution <swift-evolution@swift.org> wrote:

Observable behaviors would not want to be precluded from initialization, but would also not want to be fired on initialization - at least that’s my first reaction. Is that true for all behaviors - that they would not want to be fired if the property is set as part of the parent type’s initialization, but only on later changes?

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

When do properties with behaviors get included in the memberwise

initializer of structs or classes, if ever? Can properties with behaviors
be initialized from init rather than with inline initializers?

There’s a separate discussion that mentioned allowing better control of
which initializers are generated or synthesized for a given struct. There’s
also been mention of a “derived” feature for adding conformance without
needing to supply a separate implementation. This question seems related to
me - it would be ideal if Swift had a coherent way to declare something
that did not need definition because it can be generated by the compiler.
In this case, to declare that a property is part of memberwise
initialization. `behavior lazy<T>: memberwise {` ?

You might be talking about the initialization discussion I was involved in
a week or so ago. I'm working on a proposal that would allow for more
flexible control over synthesized memberwise initialization. I'm hoping to
have a draft ready soon.

Great! Looking forward to reading it.

Is your example here part of a behavior declaration for lazy which states
that properties with the lazy behavior may be memberwise initialized?
That's what it looks like to me. I think syntax like that would make
sense. There are some behaviors which would need to opt out. Somewhat
ironically, I think lazy is one of them as the whole point of it is that it
is not initialized immediately, but rather on first access.

Yes, that was the idea behind that syntax. Hah, lazy was a terrible

example, you’re right. Distracted emailing never ends well.

It could be either opt-in (as my example hinted). Opt-out might be a bit
harder to express, and I’m not sure if opt-in is the right default.

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

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

It's unclear to me whether you can write behaviour extensions with this new model. I'm not sure if that's really needed, but with the struct-based model, you could.

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

Instead of simply writing `: T`, you could use a parameter list to give a name to that `super`-property:

  var behavior synchronizedByObject<T>(var parentProperty: T) where Self: Synchronizable {
    get {
      return self.withLock { return parentProperty }
    }
    set {
      return self.withLock { parentProperty = newValue }
    }
  }

···

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

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

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.

Jordan

···

On Dec 17, 2015, at 23:50 , Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> 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 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.

Jordan

···

On Dec 21, 2015, at 9:23 , Joe Groff via swift-evolution <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
https://lists.swift.org/mailman/listinfo/swift-evolution

I have been thinking further about the compiler diagnostics for `delayed`. It might be interesting to consider making various compiler diagnostics available to any behavior rather than having a special case in the compiler for `delayed`.

Here are some examples:

* By default properties with a behavior must be initialized in phase one just like normal properties.
* Behaviors can opt-in to a relaxed requirement that the property must be initialized *somewhere* in the initializer, but not necessarily phase one. Delayed would opt-in to this.
* Behaviors can opt-in to a requirement that the property *cannot* be set outside of an initializer. Delayed would opt-in to this.
* Behaviors can opt-in to a requirement that the property *cannot* be set anywhere. A variation of lazy might opt-in to this. (clear would still work as it is part of the implementation of lazy)

Allowing behaviors to specify diagnostic behavior like this would probably be *possible* in the ad-hoc proposal. However, it would probably be a lot more clear and elegant if we adopt the “behavior declaration” idea.

Matthew

···

On Dec 21, 2015, at 11:23 AM, Joe Groff via swift-evolution <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
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`.

This iteration is a big improvement; the use of more syntax is a definite help. Some specific comments:

var behavior lazy<Value>: Value {

I'm somewhat concerned that this looks like you're declaring a variable called "behavior", but I do like that it keeps the opportunity open for us to add, say, `let` behaviors or `func` behaviors in a later version of Swift. `behavior var` doesn't read very fluidly, but it doesn't mislead you, either.

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?

  var behavior lazy {}
  var behavior NSCopying where Value: NSCopying {}

  private var value: Value? = nil

Should we actually force people to declare the `value` property? I can't think of many behaviors which won't need one. You can put the `base` directly on the behavior declaration (arguably it belongs there since the presence or absence of a base is definitely *not* an implementation detail), and if `: Value` is currently purposeless, well, now we have a reason for it: to declare the `value` property's type.

  var behavior lazy: Value? {
    deferred initializer: Value
     get {
      if let value = value {
        return value
      }
      
      let initialValue = initializer
      value = initialValue
      return initialValue
    }
    set {
      value = newValue
    }
  }

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

Neither inline initializers nor init declaration bodies may reference self, since they will be executed during the initializing of a property's containing value.

This makes sense, but is there some opportunity for a second-phase initialization that runs when `self` is accessible? I can imagine behaviors using this to register themselves using a method provided by a protocol on `Self`. (Although perhaps it would be better to focus on exposing behaviors through reflection.)

For the same reason, an init also may not refer to the base property, if any

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?

Inside a behavior declaration, self is implicitly bound to the value that contains the property instantiated using this behavior. For a freestanding property at global or local scope, this will be the empty tuple (), and for a static or class property, this will be the metatype. Within the behavior declaration, the type of self is abstract and represented by the implicit generic type parameter Self. Constraints can be placed on Self in the generic signature of the behavior, to make protocol members available on self:

This is handy, but I wonder if it's a good idea to make `self` refer to something else and then have an apparently anonymous context for all the variables and methods. It just has a really strange feel to it. What aspects of the behavior are captured by a closure? Can you safely dispatch_async() inside a `get`?

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

Behaviors are private by default, unless declared with a higher visibility.

I think this is the right move 90% of the time, but I also think that there are certain behaviors which should be public (or as-visible-as-the-property) by default. For instance, consider this one:

  public var behavior observable<Value> {
    base var value: Value
    
    var observers: [ObserverIdentifier: (instance: Self, oldValue: Value, newValue: Value) -> Void]
    
    public func addObserver(observer: (instance: Self, oldValue: Value, newValue: Value) -> Void) -> ObserverIdentifier {
      let identifier = ObserverIdentifier()
      observers[identifier] = observer
      return identifier
    }
    public func removeObserverWithIdentifier(identifier: ObserverIdentifier) {
      observers[identifier] = nil
    }
    
    get { return value }
    set {
      let oldValue = value
      value = newValue
      for observer in observers.values {
        observer(self, oldValue, newValue)
      }
    }
  }

It's very likely that a public property on a public type will want this behavior to be public too. On the other hand, it's also a relatively niche case and might not be worth complicating the model.

Behavior extensions can however constrain the generic parameters of the behavior, including Self, to make the members available only on a subset of property and container types.

It might also be useful to allow extensions to provide constrained default accessor implementations:

  public var behavior changeObserved<Value>: Value {
    base var value: Value
    
          accessor detectChange(oldValue: Value) -> Bool
    mutating accessor didChange(oldValue: Value) { }
  
    get {
      return value
    }
    set {
      let oldValue = value
      value = newValue
      if detectChange(oldValue) {
        didChange(oldValue)
      }
    }
  }

  extension changeObserved where Value: Equatable {
    accessor detectChange(oldValue: Value) -> Bool {
      return value != oldValue
    }
  }

The proposal suggests x.behaviorName for accessing the underlying backing property of var (behaviorName) x. The main disadvantage of this is that it complicates name lookup, which must be aware of the behavior in order to resolve the name, and is potentially ambiguous, since the behavior name could of course also be the name of a member of the property's type.

I suggest using `foo.bar[lazy]` as the property access syntax. This echoes the use of square brackets to declare properties, is less likely to look like it conflicts with a valid operation (most types have properties, but very few have subscriptors), and should never be ambiguous to the compiler unless you decide to make a variable with the name of a behavior and then subscript a data structure with it.

If you cared more about ambiguity than appearance, you could even reverse the order and make it `type.[lazy]bar`, but good lord is that ugly.

···

--
Brent Royal-Gordon
Architechies

For var [lazy] foo = 1738 how does the user or compiler know that foo, even
though it’s an Int, it has a property called `lazy` on it. And what happens
if that object type has a property named `lazy` already?

-Tal

···

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

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

Here's the revised proposal:

Property behavior declarations · GitHub

For reference, here's the previous iteration:

Swift property behaviors · GitHub

Thanks for taking a look!

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

I like it. A lot.

+1.

Charles

···

On 2016-01-13 17:07, Joe Groff via swift-evolution wrote:

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

Here's the revised proposal:

Property behavior declarations · GitHub

For reference, here's the previous iteration:

Swift property behaviors · GitHub

Thanks for taking a look!

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

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.

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?

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.

···

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

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

Here's the revised proposal:

Property behavior declarations · GitHub

For reference, here's the previous iteration:

Swift property behaviors · GitHub

Thanks for taking a look!

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

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

Notable points of confusion:

it's confusing to me that `self` is the containing type and the behavior name is the "behavior's self".
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?)
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".

Questions:

Can a behavior have generic parameters that can't be inferred? Could I write, say, [fooable<Int>]?
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.

Concerns:

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

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

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

Félix

···

Le 13 janv. 2016 à 17:07:06, Joe Groff via swift-evolution <swift-evolution@swift.org> a écrit :

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

Here's the revised proposal:

Property behavior declarations · GitHub

For reference, here's the previous iteration:

Swift property behaviors · GitHub

Thanks for taking a look!

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