[Proposal] Property behaviors

Swift’s semantics have a big (but largely invisible/unknown to developers) divide between initialization and reassignment. I agree that it makes sense to expose this different out of a behavior because they may want to do something interesting.

That said, the divide shouldn’t be “assigned in an containing initializer” vs “assigned to somewhere else”. You should be able to have a behavior on a local variable for example.

-Chris

···

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

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

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

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

+1 for disambiguating behavior vs. member access by a sigil. Using # is fine IMO.

-Thorsten

···

Am 18.01.2016 um 05:41 schrieb Curt Clifton via swift-evolution <swift-evolution@swift.org>:

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

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

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

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

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

// declaration
var #resettable foo: Int

// use
foo#resettable.reset()

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

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

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

Cheers,

Curt

Curt Clifton, PhD
Software Developer
The Omni Group

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

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

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

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

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

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

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

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

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

This is good feedback, thanks!

Notable points of confusion:

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

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

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

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

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

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

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

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

var behavior foo { ... }

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

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

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

Questions:

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

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

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

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

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

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

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

Concerns:

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

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

Comments:

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

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

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

Thanks, missed that.

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

Not a bad suggestion.

Thanks again for the feedback!

-Joe

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

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

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

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

-Joe

···

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

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

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

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

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

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

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

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

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

That’s a fair point :-)

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

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

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

Not sure what you mean. It can.

But you have this in your other message:

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

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

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

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

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

willSet or didSet wrapping a superclass setter is not at all uncommon, and the sort of things you wrap with didSet are usually things that want to know about reset() as well. I'm not sure any of your other examples of property methods have this problem, though—it might only be things that are set-like.

Jordan

···

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

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

Thanks, Chris. That's incredibly helpful.

Cheers,
Curt

···

On Jan 25, 2016, at 11:24 PM, Chris Lattner <clattner@apple.com> wrote:

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

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

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

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

Me too. Two caveats:

- The model in swift should be opt-in: you should be able to use borrowing references in performance sensitive code, and get guarantees about (e.g.) no ARC. You should be able to opt into making a struct move-only, and thus only work with those references, etc. However, it shouldn’t be a required part of the programming model that all swift programmers need to confront to learn the language.

- This is certainly out of scope for swift 3 :-( OTOH, if someone were motivated to start exploring a concrete design in the space, it would be very very interesting. One of the reasons that inout is where it is in the grammar is to allow other kinds of named parameter modifiers along the lines of Rust’s.

-Chris

···

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

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

In essence, synchronization is an access policy for a variable. It's somewhat similar to `let` granting you only access to the getter, or the `Delayed` property behaviour that lets you use the setter only once.

Synchronization is an access policy stipulating that you should only use the getter and setter of the variable while the current thread has locked the associated mutex. While you could enforce that at runtime with fatalErrors in the setter and getter whenever the mutex is not locked (similar to `Delayed`), it's simply more convenient to enforce it at compile time by requiring a closure.

I acknowledge a type such as Synchronized<T> will work fine for that too (assuming non-copyablility). I just think the modeling is slightly off. Synchronization is an access policy, not a data type, and it should be modeled in a similar manner to the other access policies in the language.

Language support for concurrency is out of scope for Swift 3, so it's perhaps premature to think about that now... but I can't help but think it'll have to work this way anyway the day Swift introduces a safe concurrency model.

···

Le 18 déc. 2015 à 18:56, Kevin Ballard <kevin@sb.org> a écrit :

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

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

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

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

Really interesting thread, and great work on the proposal so far, Joe. I have some additional thoughts but I wanted to chime in with one thing first:

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

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

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

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

Agreed. For instance, it makes it much easier for tooling to know something is a behavior if it can just look for a keyword, rather than having to infer it from use, or some function declaration pattern or something.

-Colin

···

On Dec 22, 2015, at 12:08 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

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

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

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

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

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

- Can behaviors be extended?
- Can behaviors be resilient? One nice thing about a fragile behavior is that we can inline its storage, if any, directly into its containing type without having to instantiate metadata for a nominal type, as we would for a struct-based property implementation. A resilient behavior, however, would end up needing more or less the same metadata to encapsulate the layout of the behavior's state behind the resilience domain, weakening that benefit.
- Should behaviors be able to control their default visibility policy? As Brent and others pointed out, most behaviors are implementation details, but the few that make sense as API generally always want to be API, such as `resettable` or `KVOable`.

-Joe

···

On Dec 21, 2015, at 9:08 PM, Chris Lattner <clattner@apple.com> wrote:

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

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

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

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

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

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

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

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

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

-Joe

···

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

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

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

I think that might be acceptable for lazy, but doing it twice for resettable seems painful. So how about a compromise?

- There are only eager initializers.
- All behaviors have access to the initializer at any time by using `initializer`.
- The implicit stored property uses the initializer to initialize itself.

So `resettable` becomes a non-base behavior which uses `initializer`:

  var behavior resettable<Value>: Value {
    base var value: Value
    
    func reset() {
      value = initializer
    }
    get {
      return value
    }
    set {
      value = newValue
    }
  }
  
  class Foo {
    var [resettable] bar = 5
  }

Which means it can be properly stacked with observation behaviors. Meanwhile, `lazy` is a base behavior with an accessor:

  var behavior lazy<Value>: Value {
    var value: Value?
    
    accessor lazy()
    
    get {
      if let value = value {
        return value
      }
      let newValue = lazy()
      value = newValue
      return newValue
    }
    set {
      value = newValue
    }
  }
  
  class Foo {
    var [lazy] bar {
      lazy {
        return 5
      }
    }
  }

Hmm. Any chance we can get rid of the `[lazy]` when we see `lazy` in the accessor list? Such a feature might also allow us to de-magic `willSet` and `didSet`.

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

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

That makes sense, but I think access to the initializer should probably be broader, as I suggested above. That is, only the innermost behavior initializes the storage, but all behaviors have access to the initial value for whatever use they might want to make of it.

(Although `lazy` would need to be able to say "this takes no initializer". Hmm...)

One more thing: should there be a way to pass arbitrary data, rather than initializers or accessors, into a behavior?

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

If we were going to do that, I'd prefer to see accessors get the one-expression-return behavior of a closure:

  key { "id" }

This would also make using an accessor for `lazy` more palatable!

···

--
Brent Royal-Gordon
Architechies

Ok, so here’s what I came up with. It partially works. Also, I thought it might be easier to supply a list of behaviors instead of using function composition. Both ways are included, since I’d already written the one using function composition when I thought of the other way.

Oh, and to be clear, I’m NOT saying this is the way I think we should do this… I’m just exploring how for we can take it without adding any new language features.

Here’s the “library” part:
//: Playground - noun: a place where people can play

import Cocoa

protocol Wrapper {
    typealias T
    var value: T {get set}
}

// "assignment" operator, since we can't overload the real one
// (yes, I know "≠" means "not equals"... it's easy to type, and
// this is just for a proof of concept)
infix operator ≠ {}
func ≠ <U: Wrapper, V: Wrapper where U.T == V.T> (inout lhs: U, rhs: V) { lhs.value = rhs.value }
func ≠ <V: Wrapper> (inout lhs: V.T, rhs: V) { lhs = rhs.value }
func ≠ <V: Wrapper> (inout lhs: V, rhs: V.T) { lhs.value = rhs }

// function composition. Nothing special about "•"... it's just about as close as
// I could get to the "⊙" my math textbooks used without pulling up the symbols palette
infix operator • {}
func • <T, U, V> (f1: U -> V, f2: T -> U) -> (T -> V) { return { f1(f2($0)) } }

// function currying. Nothing special about "<|>"... is was just what someone else used
infix operator <|> {precedence 200}
func <|> <T, U, V> (f: (T,U)->V, b: U) -> (T->V) { return { f($0, b) } }

// Seemed the easiest way to handle “lazy”.
enum MaybeLazy<T> {
    case Yep(()->T)
    case Nah(T)
    
    init(_ value: ()->T) { self = .Yep(value) }
    init(_ value: T) { self = .Nah(value) }
    
    var value: T {
        get {
            switch self {
            case .Nah(let t): return t
            case .Yep(let tc): return tc()
            }
        }
        set {
            self = .Nah(newValue)
        }
    }
}

// Where the sufficiently advanced technology happens
struct Observable<T> : Wrapper, CustomStringConvertible {
    // I'm not actually sure this typealias needs to exist. Sometimes
    // the playground gets... stubborn... about what is or isn't an error,
    // and there's no way to clean/build. Plus I lose track of where the Ts go.
    typealias FType = (T,T)->(T,T)
    
    // The "do nothing" closure
    static func _passThrough(x:(oldValue: T, newValue: T)) -> (T,T) { return x }
    
    // "Function"
    var f: FType! = nil
    // "Element Function". More on this later.
    var ef: Any? = nil
    var _value:MaybeLazy<T>
    var value: T {
        get { return _value.value }
        set { _value.value = (f != nil) ? f(_value.value, newValue).1 : newValue }
    }
    
    // inits for function composition
    init(_ value: T, _ f: (oldValue: T, newValue: T)->(T, T) = Observable._passThrough) {
        self._value = .Nah(value)
        self.f = f
    }
    init(lazy: ()->T, _ f: (oldValue: T, newValue: T)->(T, T) = Observable._passThrough) {
        self._value = .Yep(lazy)
        self.f = f
    }
    
    // inits for variadic list of functions
    init(_ value: T, behaviors: ((oldValue: T, newValue: T)->(T, T))...) {
        self._value = .Nah(value)
        switch behaviors.count {
        case 0: self.f = Observable._passThrough
        case 1: self.f = behaviors[0]
        case _:
            var c = behaviors.last!
            for i in (0 ..< (behaviors.count - 1)).reverse() {
                c = c • behaviors[i]
            }
            self.f = c
        }
    }
    init(lazy: ()->T, behaviors: ((oldValue: T, newValue: T)->(T, T))...) {
        self._value = .Yep(lazy)
        switch behaviors.count {
        case 0: self.f = Observable._passThrough
        case 1: self.f = behaviors[0]
        case _:
            var c = behaviors.last!
            for i in (0 ..< (behaviors.count - 1)).reverse() {
                c = c • behaviors[i]
            }
            self.f = c
        }
    }
    
    var description: String { return "\(value)" }
}

// the behavior functions to be passed in
func didSet <T> (x: (oldValue: T, newValue: T), _ f: ((T,T))->((T,T))) -> (T,T) {
    f(x)
    return (x)
}
func didChange<T: Equatable> (x: (oldValue: T, newValue: T), _ f: ((T,T))->((T,T))) -> (T,T) {
    if x.oldValue != x.newValue {
        f(x)
    }
    return (x)
}

And here’s how you’d use it (except with “=“ instead of “≠”, of course):
var exampleOfCompositionSyntax = Observable(3,
    didSet <|> {
        print("didSet")
        return ($0, $1)
    } • didChange <|> {
        print("didChange")
        return ($0,$1)
    }
)
exampleOfCompositionSyntax ≠ 3 // the closure passed to didSet is called, but not didChange
exampleOfCompositionSyntax ≠ 4 // both are called, and it now evaluates to 4

var exampleOfVariadicSyntax = Observable(3, behaviors:
    didSet <|> {
        print("didSet")
        return ($0, $1)
    },
    didChange <|> {
        print("didChange")
        return ($0,$1)
    }
)
exampleOfVariadicSyntax ≠ 3 // the closure passed to didSet is called, but not didChange
exampleOfVariadicSyntax ≠ 4 // both are called, and it now evaluates to 4

var nowWithMoreLaziness = Observable(lazy: {return 4})
var ibar = 0 // here’s one glitchy bit… ibar has to be declared first since “=“ can’t be overloaded
ibar ≠ nowWithMoreLaziness // ibar evaluates to 4

Trying to extending the behavior to collections is where things kinda fall apart. This code doesn’t generate any errors, but it causes Xcode to repeatedly “lose communication with the playground service":
extension Observable where T: MutableCollectionType {
    // This extension is where we support per-element behavior. "ef" is really
    // of type "CType", but we couldn't declare it that way because we didn't
    // know that T was a MutableCollectionType until now.
    typealias CType = (T.Generator.Element,T.Generator.Element,T.Index)->(T.Generator.Element,T.Generator.Element,T.Index)
    
    // The "do nothing" closure
    static func _ePassThrough(x:CType) -> CType { return x }
    
    init(_ value: T, elementalbehaviors: CType...) {
        self._value = .Nah(value)
        switch elementalbehaviors.count {
        case 0: self.ef = Observable._ePassThrough
        case 1: self.ef = elementalbehaviors[0]
        case _:
            var c = elementalbehaviors.last!
            for i in (0 ..< (elementalbehaviors.count - 1)).reverse() {
                c = c • elementalbehaviors[i]
            }
            self.ef = c
        }
    }
    subscript(i: T.Index) -> T.Generator.Element {
        get { return value[i] }
        set { value[i] = (ef != nil) ? (ef! as! CType)(value[i], newValue, i).1 : newValue }
        
    }
}

So is it a bug in my code, or a bug in Playgrounds that’s causing the problem? Dunno, and I’m too tired to find out tonight. Obviously, if any of you want to play with it, go ahead… that’s why I posted the code :-)

- Dave Sweeris

···

On Jan 14, 2016, at 17:16, Dave via swift-evolution <swift-evolution@swift.org> wrote:

I might play with it a bit tonight after dinner.

One more “how will this work?” question: optionals.

Specifically, consider something like this:

// annoyingly-long protocol:
protocol ControlExchanging {
  typealias Context
  func willTakeControlFrom(other: Self?, context: Context)
  func didTakeControlFrom(other: Self?, context: Context)

  func takeControl(context: Context)
  func cedeControl(context: Context)
  
  func willCedeControlTo(other: Self?, context: Context)
  func didCedeControlTo(other: Self?, context: Context)
}

var behavior exchangeState<Value:ControlExchanging where Self:Value.Context> : Value {
  var value: Value
  // here:
  set {
    let oldValue = value
    // boilerplate-choreography begins:
    newValue.willTakeControlFrom(oldValue, context: self)
    oldValue.willCedeControlTo(newValue, context: self)
    oldValue.cedeControl(self)
    value = newValue
    newValue.takeControl(self)
    oldValue.didCedeControlTo(newValue, context: self)
    newValue.didTakeControlFrom(oldValue, context: self)
  }
}

// numerous extraneous details omitted:
class GenericSwitchboard<Delegate:ControlExchanging were Delegate.Context == Self> {

  private(set) weak var [exchangeControl] delegate: Delegate? = nil

}

…which presumably won’t actually work unless I’ve either:

- added an additional implementation that’s typed-as `Value?`
- added a conditional-conformance for `ControlExchanging` to `Optional` (easy, but boilerplate)

….both of which are workable, neither of which feels optimal (and for the latter, consider also that in many cases such conformances may generally be undesirable).

Is there a trick/detail I’m missing here?

Good point. Longer behavior compositions are something to consider. If behaviors have dedicated declarations instead of being applications of plain types or functions, it also becomes more reasonable to let them be used as attributes by themselves:

@mainThread @resettable @logged @changeObserved
public var foo: Int { ... }

I shied away from that design originally because it would be problematic to pollute the attribute namespace with potentially every function and/or type. That's less of a problem with this design.

I still like the aesthetics of your design better (until maybe when things get too long).

It might be a good general policy for proposals with syntax changes to always include at least one “heavy-duty/extreme” example.

Another argument in favor of a different positioning: it makes these kinds of things easier to write:

@resettable @changeObserved
if DEBUG
@mainThread @onlyAfterViewLoaded @logged
#endif

…(which may or may not be a good thing).

Would it be possible to move the "behavior list” prior to decl modifiers without needing to use attributes? The square brackets might not work in that case but maybe something else?

I like the positioning but don’t like all the @ sigils and the fact that the behaviors would be potentially intermixed with other attributes. I also like that the square brackets in property declaration syntax makes it very clear when behaviors are involved.

···

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

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

## Request: Declared-Property-Name

If possible, a property analogous to `self` / `parent` / etc. getting at a behavior’s concrete “declared-name” would be amazing, although I know it’s not quite that easy, either (see @objc questions later).

That's definitely an interesting extension to consider. I think it can be separated from the core proposal, though.

Agreed it is an extension; consider it in the spirit of “if a bunch of other such things get added, perhaps this should be too”.

## Semantics: Overrides + Behaviors

I would expect each override's [changeObserved] behavior to wrap the previous override, as happens if you override with `didSet`/`willSet` today. You raise the interesting question of what happens with the behavior names, since you do end up with two same-named behaviors applied at different times. That potentially requires deeper qualification by type; if we use Tal's suggested `.[behavior]` qualification syntax, you could refer to `.[BaseView.changeObserved]` and `.[RefinedView.changeObserved]` if you needed to.

The behavior is what I’d expect, but it seems that that behavior will be an occasional source of subtle bugs in scenarios like this:

class Simple : Synchronizing {
   var [changeObserved,synchronized] delicate: Delicate
}

class Fancy : Simple {
  override var [changeObserved] delicate: Delicate {
    willSet { /* consider timing vis-a-vis base's `synchronized` */ }
    didSet { /* consider timing vis-a-vis base's `synchronized` */ }
  }
}

…but I’m note sure there’s an easy way to make property behaviors flexible-enough to solve this problem purely at the property-behavior level; especially when it is easy in this sort of circumstance (and arguably better) to just write appropriate hooks into your class, and override from there.

## Semantics: Redundancy/“Static” Parameterization

It seems to me you could factor at least some of this boilerplate into a behavior, reducing it to:

var [invalidate] foo: String { invalidates { return [.Display, .ContentSize] } }

Brent mentioned in the other thread about modeling `lazy` with an accessor some possibilities to whittle this down further, by allowing for implicit single-expression return in accessors, and inferring behaviors from accessor names:

var foo: String { invalidates { [.Display, .ContentSize] } }

That’s a really nice trick and makes this a proposal a lot more feature-complete for me than I had realized, particularly if the implicit-return suggestion is taken.

## ObjC Interaction

You could potentially declare another @objc property that forwards to the stored property, though that's even less ideal of course:

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

Behaviors definitely exacerbate this issue, since you have more interesting properties without native get/set accessors that you may want to control the ObjC interface for. The private @objc trick above at least still works.

That’s another really nice way to do it. But, I’ll take this reply to mean an improvement to @objc-renaming is independent of this proposal, which is fine.

Overall this revision of the proposal is looking really nice.

-Joe

On the one hand, this proposal doesn’t seem to change this situation.

On the other hand, if it can be changed, this seems like a reasonable time/place to do it.

That’s it for the moment.

With this proposal it seems like a really nice feature to have.

On Jan 13, 2016, at 8:04 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

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

Quick Q1: is it the case that `var behavior redrawing<Value where Self:UIView> : Value { … } ` would work or no? I’d assume so, but I don’t think there are any examples with a non-protocol constraint on `Self`, making it hard to tell at a glance.

Yeah, you should be able to use arbitrary generic constraints.

Quick Q2: is there anything you can do in this scenario:

// w/in some behavior:
mutating func silentlySet(value: Value) {
value = value // <- probably not going to work
self.value = value // <- not right either, `self` is the behavior’s owner, right?
}

…other than change the argument name to avoid conflict?

I thought I mentioned this in the proposal—you could use `behaviorName.value` to qualify a reference to the behavior's members within the behavior.

Remark: it definitely feels a bit odd to be using both `Self` and `self` to mean something that’s neither consistent with the rest of the language nor, really, to mean `Self` (or `self`).

I get not wanting new keywords, but this feels like it could be an economy too far; perhaps I’m misunderstanding some aspect of how it’s meant to work.

I'm not totally comfortable with it either. It at least corresponds to the notion of `self` you'd get if you'd coded a property by hand within its enclosing type, so the meaning might be useful for refactoring code out of concrete property implementations into behavior templates.

-Joe

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

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

One more “how will this work?” question: optionals.

Specifically, consider something like this:

// annoyingly-long protocol:
protocol ControlExchanging {
  typealias Context
  func willTakeControlFrom(other: Self?, context: Context)
  func didTakeControlFrom(other: Self?, context: Context)

  func takeControl(context: Context)
  func cedeControl(context: Context)
  
  func willCedeControlTo(other: Self?, context: Context)
  func didCedeControlTo(other: Self?, context: Context)
}

var behavior exchangeState<Value:ControlExchanging where Self:Value.Context> : Value {
  var value: Value
  // here:
  set {
    let oldValue = value
    // boilerplate-choreography begins:
    newValue.willTakeControlFrom(oldValue, context: self)
    oldValue.willCedeControlTo(newValue, context: self)
    oldValue.cedeControl(self)
    value = newValue
    newValue.takeControl(self)
    oldValue.didCedeControlTo(newValue, context: self)
    newValue.didTakeControlFrom(oldValue, context: self)
  }
}

// numerous extraneous details omitted:
class GenericSwitchboard<Delegate:ControlExchanging were Delegate.Context == Self> {

  private(set) weak var [exchangeControl] delegate: Delegate? = nil

}

…which presumably won’t actually work unless I’ve either:

- added an additional implementation that’s typed-as `Value?`
- added a conditional-conformance for `ControlExchanging` to `Optional` (easy, but boilerplate)

….both of which are workable, neither of which feels optimal (and for the latter, consider also that in many cases such conformances may generally be undesirable).

Is there a trick/detail I’m missing here?

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

Good point. Longer behavior compositions are something to consider. If behaviors have dedicated declarations instead of being applications of plain types or functions, it also becomes more reasonable to let them be used as attributes by themselves:

@mainThread @resettable @logged @changeObserved
public var foo: Int { ... }

I shied away from that design originally because it would be problematic to pollute the attribute namespace with potentially every function and/or type. That's less of a problem with this design.

I still like the aesthetics of your design better (until maybe when things get too long).

It might be a good general policy for proposals with syntax changes to always include at least one “heavy-duty/extreme” example.

Another argument in favor of a different positioning: it makes these kinds of things easier to write:

@resettable @changeObserved
if DEBUG
@mainThread @onlyAfterViewLoaded @logged
#endif

…(which may or may not be a good thing).

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

-Joe

···

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

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

It would be very handy to have access to the property's name inside the behavior. As proposed, we have Self and self for referring to the type and instance of the container and newValue for referring to the new value in the core getter. A predefined local variable "name" of type String, bound to the name of the decorated property, would be handy for implementing things like Brent's backedByJSON behavior:

   var behavior backedByJSON<Value: JSONRepresentable where Self: JSONObjectRepresentable>: Value {
       get {
           return Value(JSON: self.JSON[name])
       }
       set {
           self.JSON[name] = newValue.JSON
       }
   }

A "name" property could also be used for an object-level observation system that included the name of the changed property in published notifications. Such a system can be useful, for example, in constructing delta transaction for sending to a remote API. (E.g., we could notify a server that the due date of a task has changed rather than sending the whole task object or performing a diff.)

Adding this bit of magic would go a long way toward letting us implement something like OAAppearance[1] in Swift instead of importing runtime.h and dynamically reifying methods[2].

Cheers,

Curt

Curt Clifton, PhD
Software Developer
The Omni Group

[1] - https://github.com/omnigroup/OmniGroup/tree/master/Frameworks/OmniAppKit/Appearance
[2] - curtclifton.net

···

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

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

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

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

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

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

Syntax comments:

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

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

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

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

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

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

Good idea, I like this approach. However:

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

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

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

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

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

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

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

John.

···

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

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

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

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

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

-Joe

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

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

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

Yeah.

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

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

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

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

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

John.

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

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

-Chris

···

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

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

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

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

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

Joe,

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

-Matthew

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

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

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

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

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

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

Thanks for clarifying.

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

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

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

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

Sure, that’s an important use case. But I think the use case where you only need to delay initialization until phase 2 due to a back reference, etc is also important and the added safety of guaranteeing initialization *must* happen during phase 2 is highly desirable.

-Matthew

···

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

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

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

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

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

I must say that I really love your idea of property behaviours! I think a facility of this kind would be a great enhancement to Swift.

I probably will have some more comments after I study your proposal in more depth, but for now a very quick one. I am not overly fond of your syntax for accessing behaviour members. I would prefer something like foo(x).method() to x.[foo].method() that you suggest.

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

I’m not so sure about that… your suggestion can have some non-trivial impact on reflection and instance memory layout.

— Taras

···

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

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

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

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

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

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

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

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

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

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

That’s a fair point :-)

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

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

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

-Joe

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

I must say that I really love your idea of property behaviours! I think a facility of this kind would be a great enhancement to Swift.

I probably will have some more comments after I study your proposal in more depth, but for now a very quick one. I am not overly fond of your syntax for accessing behaviour members. I would prefer something like foo(x).method() to x.[foo].method() that you suggest.

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

I’m not so sure about that… your suggestion can have some non-trivial impact on reflection and instance memory layout.

— Taras

···

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

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

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

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

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

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

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

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

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

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

That’s a fair point :-)

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

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

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

-Joe

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