[Proposal] Property behaviors

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.

-Joe

···

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.

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

···

On Mon, Dec 21, 2015 at 1:04 PM Matthew Johnson via swift-evolution < 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> 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

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

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.

If name lookup has visibility of the behaviors, it can handle them as a special case.

And what happens if that object type has a property named `lazy` already?

Good question. We can either overload the name and let type context sort it out, or consider an alternative syntax for accessing members.

-Joe

···

On Jan 13, 2016, at 2:37 PM, Tal Atlas <me@tal.by> 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`.

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

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

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.

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

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.

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.

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?

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.

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

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

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.

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.

I agree. It might be interesting for behaviors to specify their preferred visibility policy.

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

Interesting point. You can do that with protocol extensions.

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.

Yeah, I got a similar suggestion from Patrick Smith on Twitter. I kinda like it; it echoes the declaration syntax.

-Joe

···

On Jan 13, 2016, at 5:04 PM, Brent Royal-Gordon <brent@architechies.com> 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

···

On Jan 13, 2016, at 5:12 PM, plx via swift-evolution <swift-evolution@swift.org> wrote:

`var [lazy] foo = 123` maybe the accessor could be `foo.[lazy].clear()`

-Tal

···

On Wed, Jan 13, 2016 at 6:14 PM, Joe Groff <jgroff@apple.com> wrote:

> On Jan 13, 2016, at 2:37 PM, Tal Atlas <me@tal.by> wrote:
>
> 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.

If name lookup has visibility of the behaviors, it can handle them as a
special case.

> And what happens if that object type has a property named `lazy` already?

Good question. We can either overload the name and let type context sort
it out, or consider an alternative syntax for accessing members.

-Joe

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

···

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

I was wondering if you had considered/had comments an alternative approach where you declare an ordinary new value type which meets certain rules and ‘wraps’ the existing type, then define new syntax/features in Swift for having the wrapper (or possibly even multiple levels of wrappers) be more transparent for use.

You might want to take a look at the first version of the proposal. It defined behaviors using existing constructs with certain conventions, and most posters argued that it was a bit of a mess and needed syntax to clarify what was happening.

···

--
Brent Royal-Gordon
Architechies

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

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.

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.

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)

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

Thanks for your work on this.

Félix

···

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

Interesting. I suppose that answers my question - the desire to have container references and initialization data presented during storage/retrieval (so that they do not need to be duplicated inside the wrapper type) resulted in types being required to look more like plugins to the type system rather than simple wrappers.

-DW

···

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

I was wondering if you had considered/had comments an alternative approach where you declare an ordinary new value type which meets certain rules and ‘wraps’ the existing type, then define new syntax/features in Swift for having the wrapper (or possibly even multiple levels of wrappers) be more transparent for use.

You might want to take a look at the first version of the proposal. It defined behaviors using existing constructs with certain conventions, and most posters argued that it was a bit of a mess and needed syntax to clarify what was happening.

--
Brent Royal-Gordon
Architechies

Joe,

There seem to be many new syntactic features to support this, both inside and outside of a behavior. I was wondering if you had considered/had comments an alternative approach where you declare an ordinary new value type which meets certain rules and ‘wraps’ the existing type, then define new syntax/features in Swift for having the wrapper (or possibly even multiple levels of wrappers) be more transparent for use.

For instance, the existing ‘lazy’ keyword functionality might have been implemented by a type Lazy<T>

struct Lazy<T> {
    private var val:T?
    let supplier:()->T
    
    init(supplier:()->T) {
        self.supplier = supplier
    }

    var value:T {
        mutating get {
            if val == nil {
                val = supplier()
            }
            return val!
        }
        set {
            val = value
        }
    }
    mutating func clear() {
        val = nil
    }

}

With the following as an example of use (without any additional syntactic features)

var globally = "Test"
class Foo {
    var bar = Lazy<Int> {
        globally = "Side effect"
        return 1
    }
}

print(globally)
Foo().bar.value
print(globally)
Foo().bar.clear()

One could opt into a syntax to allow value to be hidden from view. In fact, I can hide the use of the Lazy struct today if I’m willing to write more code:

class Foo {
    private var internalbar = Lazy<Int> {
        return 1
    }
    var bar:Int {
        get {
            return internalbar.value
        }
        set {
            internalbar.value = newValue
        }
    }
}
print(Foo().bar)

Which actually has the benefit of being able to call the clear() method without new syntax, and being able to control access to clear separate from the getter/setter

Yes, this was explored by the previous iteration of the proposal. The exact design you propose is unacceptable for us, because it requires per-property instance storage for the "supplier" closure, which would be a massive regression from how `lazy` works today. To avoid this, the parameters of the property declaration need to be passed to the property implementation as well, so I had proposed using a subscript to handle it:

struct Lazy<T> {
  var value: T? = nil

  subscript(initialValue: () -> T) -> T { ... }
}

which is workable, but if we want this feature to have the flexibility to subsume all of our existing ad-hoc property features, the informal protocol between the behavior and the initialization and subscripting of the underlying type becomes fairly complex. Using types to encapsulate behaviors also introduces some runtime overhead we'd like to avoid if possible.

-Joe

···

On Jan 13, 2016, at 9:34 PM, David Waite <david@alkaline-solutions.com> wrote:

-DW

On Jan 13, 2016, at 3:07 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto: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

Good catch, thanks!

···

On Jan 14, 2016, at 9:49 AM, Ben Langmuir <blangmuir@apple.com> wrote:

Hey Joe,

Just a small bug report:
A deferred initializer is used only after the initialization of the behavior's state. A deferred initializer cannot be referenced until the behavior's storage is initialized. A property using the behavior can refer to self within its initializer expression, as one would expect a lazy property to be able to.

  var behavior deferredInit: Int {
    eager initializer: Int
Presumably “eager” should be “deferred” here.

On Jan 13, 2016, at 2:07 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto: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

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.

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

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

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

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

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

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

And ten seconds after posting, I realize this would be ambiguous with `inout` in cases like this:

  foo(&myvar.somefield)

so, nevermind.

···

Le 15 janv. 2016 à 8:19, Michel Fortin via swift-evolution <swift-evolution@swift.org> a écrit :

Maybe this should be the way to access functions inside behaviours:

  &myvar.resetable.reset()

By using `&myvar`, we indicate that we want access to the variable, not its value, similar to how it works with `inout`. This would also fit well with a future extension where you could access behaviours through `inout` arguments.

Also, I think it's important that behaviour functions be accessible skipping the behaviour name. This syntax works well for this:

  &myvar.reset()

--
Michel Fortin
https://michelf.ca

Believe me, I would prefer to keep the language impact small too. There are a couple of issues that make this difficult with property behaviors:

- First of all, Swift properties are *not* a simple getter-setter pair. Get and set is an inefficient protocol for value types, since any mutation of part of a value through get/set requires copying the entire value by 'get', mutating the temporary copy, then copying the entire modified value back by 'set'. If properties were limited to a get/set interface, every partial update of an array 'foo.arrayProperty[0] = 1' would force at minimum one full copy of the array buffer. It's better to think of a property as a mutable projection function, `inout T -> inout U`, and in fact I plan to propose allowing 'inout' returns as a way of abstracting over properties and other projections.

- If we had inout-projecting functions, it's true that the property implementation part of most behaviors could be modeled as functional transformations, e.g.:

// Turn an optional property into a 'lazy' non-optional property
func lazy<Container, Value>(@autoclosure initialValue initialValue: () -> Value, property: inout Value?) -> inout Value {
  func adapter() -> inout Value
    get {
      if let value = property() { return value }
      let initial = initialValue()
      property() = initial
      return initial
    }
    set {
      property() = newValue
    }
  }
  return adapter
}

However, there's more to a behavior than the property implementation itself. We also want behaviors to be able to encapsulate the backing storage for their properties, including their initialization. The compiler could perhaps infer backward from the signature of `lazy` above that, in order to produce a lazy property of type `Value`, it needs to back it with a property of type `Value?`, but it still doesn't know how or when `lazy` expects that storage to be initialized. Furthermore, we want behaviors to be able to attach behavior-specific operations to their properties, such as to clear a lazy property or reset a property to a private default value. While both of these issues can be addressed by explicitly exposing the backing storage as a separate property:

var fooStorage: Int? = nil
var foo: Int { return &lazy(initialValue: 1738, property: &fooStorage) }

that's quite a bit of boilerplate, and it also clutters the user interface for the type, since the storage now appears as a separate entity from the property itself. We want behaviors to be lightweight and easy to use, and on balance I think the best way to deliver that is with some specialized functionality.

-Joe

···

On Jan 16, 2016, at 4:39 AM, Tino Heth <2th@gmx.de> wrote:

I'm always in favor of removing special cases (like lazy and willSet/didSet), so the idea of property behaviors itself is very appealing to me.
There's one downside, though:
The proposal makes some keywords obsolete, but introduces a whole bunch of new stuff in exchange — and all of this only can be used with properties…

I hope you agree with me that there is merit in keeping the language small (it's called "Swift", not "Turkey" ;-), and that the proposal would improve if it's possible to slim it down.

Imho the first step to add property behaviors should be refining how properties are modeled in Swift:
In many languages, properties are represented as a pair of methods (setter and getter), and their absence has several downsides (functional programming works best with functions ;-).

As soon as there is a way to read and write properties in a functional way, their behaviors could be expressed in a universal manner:
Afaics, all major use cases for behaviors can be implemented as simple "decorators" — in fact, I could simulate most examples from the proposal in a tiny playground (a part of it is attached).
Of course, the simulation is ugly, but I am confident that some nice ideas would come up if properties were more accessible.

1 Like

Thank you for the thorough explanation — it is an interesting insight in Swift internals and helps to understand some decisions made.

- First of all, Swift properties are *not* a simple getter-setter pair.

Just out of curiosity: Are there more methods involved, or are the getters & setters just less simple than expected?

In the meantime, I've read the first published draft which was helpful as well:
Correct me if I'm wrong, but my understanding is that behaviors could be seen as property containers that are merged into the object they belong to, right? At least, this model makes the proposed meaning of self inside behaviors more intuitive.

Is there are reason why arrow brackets ("var<lazy> foo…") haven't been considered for declaration?
Both generics and behaviors predefine details at compile time, so I see more similarities than with all other possible types of brackets, which usually have a well-defined meaning.
I guess there is a possible source of confusion if the concept is extended to functions, but imho that could be resolved easily.
Btw:
Are there already ideas how to use behaviors in other places (functions, subscripts — maybe even types)?

How about behaviors that need parameters? The alternative "keyword"-declaration (I have seen the "by" in Kotlin, so I guess it's a Scala-invention ;-) offers an obvious answer here, but I'm not sure for the other variants.

Would it be possible to add behaviors in subclasses?

I guess most of my questions have already been discussed, but I couldn't find answers in the public archives...

Best regards,
Tino

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

···

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!

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

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

Syntax comments:

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

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

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

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

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

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

Good idea, I like this approach. However:

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

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

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

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

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

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

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

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

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

-Joe

···

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

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

I feel like there are two possible implementation models here: type instantiation and member instantiation. The type instantiation model has the advantage of allowing generic programming over the complete lazy type, but probably means we end up creating a lot of redundant stuff, some of which we’d have trouble eliminating. The member instantiation model is tricky and more limiting in terms of what you can do polymorphically directly with behavior types, but (1) I don’t think that’s crucial and (2) it does seem implementable.

The member-instantiation model looks like this:

Conceptually, a behavior declaration creates a generic behavior value type:
  struct [weak,lazy]<T> {
    ...
  }

That behavior declaration always contains an implicit “value” member:
  var value : T {
    ...
  }

Any accessors in the behavior body are considered to be accessors of the value member. Everything else is just a member of the behavior type.

An expression within the main behavior declaration is “instantiated” if it refers to any of the bound macro parameters (other than the type T). A function body within the main behavior declaration is instantiated if:
  - it contains an instantiated expression,
  - it is an initializer, and the initializer expression of a stored property contains an instantiated expression,
  - it uses a function within the main behavior declaration that is instantiated, or
  - it uses an accessor within the main behavior declaration that is instantiated.

An instantiated member or accessor of the behavior type may not be used outside of the behavior body except as directly applied to a particular behavior object.

We can still separately type-check and even generate SIL for instantiated members. Type-checking works by saying that the property name is an opaque string literal and the initializer is an opaque r-value of type T; the type-checker would have to do the latter coercion for each use, but that seems better semantically and for type inference anyway. When an instantiated function was used from an uninstantiated context, SILGen would just create SIL builtins that fetch the name / initializer; instantiating would clone the body and replace those operations. The type checker would require that instantiated functions can only be used from appropriate contexts, and the SIL verifier and IRGen would do the same.

The behavior type must be nullary-initializable (possibly with an instantiated initializer). At the use site, the behavior is always expanded as:
  var _foo_behavior = [weak,lazy]<T>()

I’m not quite sure how extensions should work with initializers, because what I described above naturally allows behaviors to be overloaded based on the presence/absence of the initializer. Either that’s disallowed, or you have to write:
  extension var [weak,lazy] _ : T = _ { … }
to clarify that (1) you mean the behavior that allows an initializer but (2) you can’t actually use the initializer expression in your implementation, because the member-instantiation model definitely doesn’t support doing that.

John.

···

On Jan 19, 2016, at 2:46 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 13, 2016, at 2:07 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto: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`.

Syntax comments:

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

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

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

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

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

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

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

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

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