[Proposal] Property behaviors

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

-DW

···

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

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

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

Here's the revised proposal:

Property behavior declarations · GitHub

For reference, here's the previous iteration:

Swift property behaviors · GitHub

Thanks for taking a look!

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

Very Good,
I'm still pondering some peculiarities in the proposal, but would like to
highlight:

Declaration syntax:

@lazy<Value>: Value {
//.....//
}

And keep:

var [lazy] foo = 1738

···

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

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

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

Here's the revised proposal:

Property behavior declarations · GitHub

For reference, here's the previous iteration:

Swift property behaviors · GitHub

Thanks for taking a look!

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

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

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

If more than one behaviour implement a reset function, or if one of the behaviours is called `reset`, then you have to disambiguate using the behaviour name as a prefix.

···

--
Michel Fortin
https://michelf.ca

Hi Joe,

My overall impression is that this is moving in the right direction and getting close. It is going to be a great feature! The declaration syntax makes is much more clear and also more scalable.

Many good questions have already come up so I’ll just focus on things that haven’t been discussed yet.

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

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

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

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

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

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

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

The behavior might look like this:

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

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

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

-Matthew

···

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

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

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

Here's the revised proposal:

Property behavior declarations · GitHub

For reference, here's the previous iteration:

Swift property behaviors · GitHub

Thanks for taking a look!

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

Hi all,

This looks like a GREAT feature. I’ve read it a few times and I am still assimilating the concepts involved and the syntax.

I remember in the NeXT days when we had Enterprise Objects Framework, which is similar to Core Data. Model objects were called Enterprise Objects or EOs for short (equivalent to managed objects in Core Data). These objects had properties that represented a to-one relationship or perhaps a to-many relationship. For example, a Blog object could have an author property that pointed to a Person object.

In Enterprise Objects the object graph was loaded on demand. So for example, you can fetch a Blog object from the database that references a Person object via its author property. Accessing aBlog.author does not incur a fetch of the Person object. However, accessing aBlog.author.name does incur a fetch to bring the corresponding Person object into memory in order to get from it the value for the name property.

This magic relied on stand-in objects, also known as fault objects (i.e. EOFault) and Objective-C’s selector forwarding amongst other things. So for example, if you accessed aBlog.author it would return an EOFault as a stand-in for the Person object.

If you then messaged the fault object with name, then the fault is given a chance by the Objective-C runtime to forward the name message, to which the stand-in responds to by fetching the data for the corresponding Person and turn itself magically into a Person object and send itself the name message. At a high level that is how the magic happened. It can be described as elegant, magical or perhaps clever.

I remember there were many that wanted Enterprise Objects on java but it was argued that java did not have what it took. Then NeXT ported Enterprise Objects Framework to java relying on willRead() calls inside property getter methods to check to see if the data was in memory and fetch it if necessary.

Now, if I am understanding this property behavior proposal correctly, it seems to me that this mechanism of on-demand loading of an object graph could be implemented using property behaviors. Am I right in saying that property behaviors could be used for something like this?

It’s been fascinating reading about this topic and all the different property behaviors. I would imagine there would be a bunch of well known property behaviors implemented in the library somewhere and most developer will be using property behaviors rather than authoring them.

···

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

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

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

Here's the revised proposal:

Property behavior declarations · GitHub

For reference, here's the previous iteration:

Swift property behaviors · GitHub

Thanks for taking a look!

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

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

Best regards,
Tino

//: Properties are modelled as a getter/setter pair
struct Property<T> {
  var get: Void -> T
  var set: (T) -> Void
}

class Test {
  // A field to store the actual data
  var mValue: Int?

  // This is our "pseudo-property": Just imaging that `self.value.get` maps to `self.value`
  var value = Property(get: { return 0 }, set: { Int in } )

  init() {
    // This is necessary because we need `self` to fake the getter/setter
    value = Property(get: { return self.mValue! }, set: { self.mValue = $0 })
  }
}

/*:
Behaviors are modelled as a transformation:
The take a set of functions and return another set the is extended according to the behavior.

A real implementation would gain more clarity from the (deprecated...) currying-syntax
*/
func didSetBehavior<T>(property: Property<T>, didSet: (T) -> Void) -> Property<T> {
  func setter(value: T) {
    property.set(value)
    didSet(value)
  }
  return Property(get: property.get, set: setter)
}

class DidSet: Test {
//: Imagine we have no superclass, and `value` is declared like

/*:
  var[didSetBehavior {
    print("New value is \($0)")
  }] value: Int
*/
  override init() {
    super.init()
    value = didSetBehavior(value) {
      print("New value is \($0)")
    }
  }
}

func willSetBehavior<T>(property: Property<T>)(allow: (old: T, new: T) -> Bool) -> Property<T> {
  func setter(new: T) {
    if allow(old: property.get(), new: new) {
      property.set(new)
    }
  }
  return Property(get: property.get, set: setter)
}

class WillSet: Test {
  override init() {
    super.init()
    self.value = willSetBehavior(value)(allow: { return $0 != $1 } )
  }
}

class WillSetDidSet: WillSet {
  override init() {
    super.init()
    self.value = didSetBehavior(value) {
      print("New value is \($0)")
    }
  }
}

func copyBehavior<T: NSCopying>(property: Property<T>) -> Property<T> {
  func performCopy(value: T) {
    let copy = value.copyWithZone(nil) as! T
    property.set(copy)
  }
  return Property(get: property.get, set: performCopy)
}

class Copy {
  var mValue = NSString()
  var value = Property(get: { return NSString() }, set: { NSString in return } )

  init() {
    value = Property(get: { return self.mValue }, set: { self.mValue = $0 })
    self.value = copyBehavior(value)
  }

}

let copyTest = Copy()
print(copyTest.value.get())
var mutableString = NSMutableString(string: "Mutable")
copyTest.value.set(mutableString)
mutableString.appendString(" string")
print(copyTest.value.get())

func decorate<In, Out, Discard>(function: (In) -> Out, decoration: (In) -> Discard) -> (In) -> Out {
  func result(input: In) -> Out {
    decoration(input)
    return function(input)
  }
  return result
}

class FuncTest {
  var f = { (input: String) in
    print(input)
  }

  init() {
    self.f = decorate(f) { input in
      print("Our input is \(input)")
    }
  }
}

FuncTest().f("duplicated")

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!

Thoughts?

- Dave Sweeris

···

On Jan 13, 2016, at 08:43, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

Yes. I'm working on a revision I hope to post soon.

-Joe

On Jan 13, 2016, at 7:36 AM, Wallacy <wallacyf@gmail.com <mailto:wallacyf@gmail.com>> wrote:

Just to clarify a little, this proposal is (will be) addressed to 3.0?

Em qui, 17 de dez de 2015 às 15:41, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> escreveu:
Hi everyone. Chris stole my thunder already—yeah, I've been working on a design for allowing properties to be extended with user-defined delegates^W behaviors. Here's a draft proposal that I'd like to open up for broader discussion. Thanks for taking a look!

-Joe

Swift property behaviors · GitHub

Here are some of my thoughts from reading through the current version (https://gist.github.com/jckarter/50b838e7f036fe85eaa3\). Apologies if this duplicates what anyone else has said; the thread is kind of unmanageable :)

  // Behaviors can declare that properties using the behavior require
  // a `deferred initializer` expression. When deferred, the
  // initializer expression is assumed to be evaluated after
  // initialization of the containing value, which allows it to refer
  // to `self`. If declared, `initializer` is bound in accessors and
  // methods of the behavior.
  deferred initializer: Value

This seems like an important feature, but the syntax is strange to me. It looks like it would be declaring storage inside the behavior, but it's really specifying the type of something used in the containing object's property declaration.

I can think of a couple alternatives:

1. Rather than passing an initial value in the containing object like "var [lazy] prop = someInitialValue", pass it explicitly as a parameter to the behavior,
    like "var [ lazy({ return someInitialValue }) ] prop: Type"

    I think it might be generally useful for behaviors' initializers to take arguments; it'd handle more than just this case. For example, you could have a behavior called synchronized(maxConcurrentRequests: Int) which would allow arguments passed to affect the behavior's...behavior.

You can parameterize using accessors, which can also theoretically replace all uses of a bound initializer. For example:

var [lazy] prop: Type { initialValue { return someInitialValue } }

2. Make the "deferred"-ness a modifier/attribute on the behavior declaration, like "public @deferred var behavior lazy<Value>: Value { ... }", which would make the implicit initialValue inaccessible from the behavior's init(). The same with @eager.

x.lazy.clear() // Invokes `lazy`'s `clear` method

As I think others have mentioned, this is ambiguous if x itself has a property called "lazy". I'd be reasonably satisfied with any of the proposed solutions to this.

  base var value: Int

I don't think I like the fact that this needs to be explicitly declared. Do all behaviors have to use the same identifier for them to be composable? Could you use "super" to mean this, instead of explicitly declaring a base property?

The identifier 'value' is up to the declaration to decide. Some behaviors don't have a base; anything that needs to control the storage, such as 'lazy', can't really be composed this way. I thought about using 'super' for this, but a behavior would still need a way to declare whether it has a 'super' or not. It's also weird to bind both 'self' and 'super' with totally different meanings. A number of people have objected to the special treatment of 'self' I've proposed already.

Accessor requirements can be made optional by specifying a default implementation:
  mutating accessor willSet(newValue: Value) {
    // do nothing by default
  }

Up until this point, I was thinking of accessor declarations like protocol requirements, in that they have no implementation ("accessor foo()" like "func foo()"). I think the lack of implementation is what makes it clear that these are requirements, not things the behavior is implementing.

It's planned for the future to allow protocol declarations to contain default implementations in-line, and I see 'optional' protocol requirements as being only for ObjC compatibility and not something you should use in pure Swift designs.

So perhaps you could use the "optional" specifier to indicate that they aren't required, rather than allowing an implementation block in the behavior. "optional accessor foo()" would allow the behavior's implementation to use "foo?()".

var [foo] x: Int {
    bar(myArg) { print(myArg) } // `arg` explicitly bound to `myArg`
}

Why not require a type annotation for parameters here? I recognize this matches the current syntax of set(newValue), but it would be more flexible if this were more like a function declaration.

We could optionally accept type annotations, but the argument types should almost always be inferrable.

To preserve the shorthand for get-only computed properties, if the accessor declaration consists of code like a function body, that code is used as the implementation of a single accessor named "get".

This seems a bit vestigial. Maybe it could be allowed only when a computed property is declared without using any behaviors.

That would be reasonable.

A few more questions:

- Can a behavior's own properties/storage use other behaviors? Can a behavior be recursive?

Yes, and yes, as long as in doing so you don't produce infinite storage.

- What of deinitializers for behaviors? Would it be possible, for example, to make an observable behavior whose willSet/didSet run during init and deinit (which Swift's current property observers can't do)?

I don't think we want to change how behaviors work in init and deinit. It isn't possible to invoke methods before `self` is initialized, and it's dangerous to do so during `deinit`.

- Are accessor implementations allowed to access the "current" property value? Currently, inside "var foo { didSet { … } }" you can access the current value by referencing "foo".

Accessor implementations can access the behavior's storage, so they can either access their stored property containing the property value directly, or factor more complex accessor logic out into a helper method.

Overall this looks great. I'm looking forward to it. :-)

Thanks!

-Joe

···

On Jan 18, 2016, at 4:29 PM, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

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.

John.

···

On Jan 13, 2016, at 2:07 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:
Thanks everyone for the first round of feedback on my behaviors proposal. I've revised it with the following changes:

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

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

- adopting John McCall's declaration-follows-use syntax for behavior declarations. I think this looks nice and provides a reasonable framework for binding all the fiddly bits of a property, such as its type, name, and initializer.
- changing the proposed syntax for behavior member lookup to 'property.[behavior].member', as suggested by Tal Atlas and others. I think this has a nice symmetry with the `var [behavior]` declaration syntax, and doesn't occupy any new sigils. On the other had, Curt Clifton and others have raised the point that it could be mistaken for a subscript at a glance.

To reduce the complexity of the initial feature review, I've also removed many of the bells and whistles from this initial version for separate consideration. For now, I'm saying initializer requirements are always "eager" (can't refer to self) and always inline-initialized. This imposes some inconvenience on some use cases, but is an additive feature. I've also left behavior composition, extending behaviors, overloading behaviors, and name binding as future extensions. Joe Pamer raised some design and technical challenges around how type inference should work with behaviors too, which I think deserve focused discussion, so I'm sidestepping those issues by starting out saying properties with behaviors always need an explicit type. Here's the updated proposal:

And for reference, previous iterations:

-Joe

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

I should have also included a diagnostic for 'instance.x.delayed.initialize(x)' being called outside an initializer so we can catch the jerk who tries to do that at compile time. :)

Thanks for all of your hard work on this Joe. Property behaviors look really cool and I'm already looking forward to them. I didn't mean to downplay that in my initial post. I apologize if I did so by emphasizing a concern that wasn't addressed directly.

Strict compiler diagnostics for delayed would be really sweet icing on the cake.

Matthew

Sorry, trailed off here. As I mentioned to Matthew in another subthread, the implementation of `delayed` could interact with the SIL diagnostic passes so that you still get DI errors in obviously wrong cases, like using the property before you `initialize` it, initializing it more than once, etc.

-Joe

···

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

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

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

-Chris

···

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

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

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

Resettable properties

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

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

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

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

We definitely can't have behaviors storing closures. Any closure that
references `self` would then become a retain cycle. And of course for
value types the closure would have captured an outdated copy of self. I
think we should state upfront that any closure type given to a behavior
should be required to be @noescape.

Something I meant to suggest but forgot was the idea that maybe a
Behavior at runtime should be a struct that's initialized every time it
needs to do something, and takes init arguments for the initializer and
any relevant accessors (e.g. willSet, didSet). This is similar to how
you already have it optionally take that stuff, but if the struct is
initialized fresh on each access, with the initializer and accessors
being provided as @noescape bound closures (implemented by hidden
function declarations), then even things like reset() will have access
to all the info it needs to do whatever it wants. Although I'm not quite
sure offhand how to model reset() being able to mutate the property if
the behavior doesn't keep the property storage inline (which, as I
previously mentioned, has composability problems). Maybe each method
would actually take an inout parameter as the first argument that it can
use to read from/write to the underlying property?

Alternatively, properties could not even have a runtime struct
representation but just be collections of static functions. Each
function would take an inout parameter for the underlying property, and
then optional parameters for the various things the user can specify
(initializer and accessors). This would make more sense when using the
explicit `behavior` keyword because you'd just model this as functions
nested inside the behavior scope, with no type declaration anywhere.

The backing property has internal visibility by default

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

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

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

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

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

I'm inclined to say that some behaviors (like resettable and
synchronized) are conceptually part of the public API (assuming the
property is public), and some behaviors (like lazy and property
observers) aren't.

With the `behavior` keyword we could let each behavior decide for itself
what its default accessibility level should be. That is a bit unusual,
but otherwise everyone who uses `resettable` on a public property will
actually have to say `public resettable` for it to make sense, and
similarly `synchronized` should typically be visible to consumers of the
API (even though there's no actual API on the behavior itself to use)
because the threading behavior of an API is part of its public contract.
Of course you could always say `private synchronized` if you wanted to
override this.

-Kevin Ballard

···

On Thu, Dec 17, 2015, at 04:36 PM, Joe Groff wrote:

Really grat and interesting!

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

Set-only properties are problematic for our property model today. This proposal alone wouldn't provide them, though with Michel's proposed extension to allow a property to effectively have no accessors at all, you could implement a behavior that only exposed a set() operation on the property.

-Joe

···

On Dec 17, 2015, at 4:05 PM, Alejandro Martinez <alexito4@gmail.com> wrote:

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

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

Cheers,
Alex

Sent from my iPad

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

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

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

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

-David

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

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

Félix

···

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

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

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

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

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

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

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

-Joe

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

Great work Joe! I really like this.

I presume the standard library will come with the existing lazy behavior.

It would be nice to have access the sync function used by Swift 2 'lazy',
but for new behaviors. It may be that the usefulness/consistency of this is
contingent on lazy being implemented only with the standard library (maybe
dispatch?)

My only concern is that it's not currently obvious (without the
proposal) what the type requirements of a behavior are. It feels like it
could have a protocol but probably doesn't need one as it's a language
feature. A badly conforming behavior would really need good and accurate
error messages.

It would be important that these are debugable, and that lldb prints
something sensible when you print an instance using them.

I missed it if it was mentioned, but access to a behavior (i.e. foo.
resettable.reset()) should probably be private by default, and perhaps
allow other access modifiers.

···

On Friday, 18 December 2015, Michel Fortin via swift-evolution < swift-evolution@swift.org> wrote:

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

>> On Dec 17, 2015, at 1:14 PM, Michel Fortin <michel.fortin@michelf.ca > <javascript:;>> wrote:
>>
>> About synchronized access, it seems to me that it'd be advantageous if
implementing subscript in a behaviour was optional. This way you can force
access of a property through a synchronized block which will help avoid
races:
>>
>> var (synchronized) data: (x: Int, y: Int, z: Int)
>>
>> func test() {
>> data.synchronize { (data) in
>> data.x += 1
>> data.y += data.x
>> data.z += data.y
>> }
>> }
>
> Interesting. IIUC, if a behavior didn't supply a subscript operation,
then the property could *only* be accessed through its behavior interface?

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

--
Michel Fortin
michel.fortin@michelf.ca <javascript:;>
https://michelf.ca

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

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

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

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

Great! Looking forward to reading it.

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

Yes, that was the idea behind that syntax. Hah, lazy was a terrible example, you’re right. Distracted emailing never ends well.

I've had a couple incidences of distracted emailing on the list myself! I hope to learn from those mistakes and not repeat it again. :)

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

I think opt-in is the right default for behaviors as it conflicts with the semantics of some behaviors.

I don't think a property with a behavior that *allows* memberwise initialization should be required to support it just because the behavior does. Part of my proposal discusses properties which cannot be memberwise initialized because of access control restrictions or because they have an @nomemberwise attribute. This might not make a lot of sense without seeing it in the context of the proposal, but it should once I have the draft ready to share.

Matthew

···

Sent from my iPad
On Dec 18, 2015, at 8:11 PM, Stephen Christopher <schristopher@bignerdranch.com> wrote:

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

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

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

···

--
Brent Royal-Gordon
Architechies