[Proposal Draft] Flexible memberwise initialization

I have completed a draft of the proposal I have been working on for flexible memberwise initialization. I am really looking forward to your input and will be refining the proposal based on our discussion.

I am including a current snapshot of the proposal in this message. I will keep the proposal up to date on Github at this link:

https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md

This is a really really interesting approach, I really like it. Detailed comments below, I’m skipping all the stuff I agree with or have no additional comments on:

Flexible Memberwise Initialization

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#replacing-the-current-memberwise-initializer&gt;Replacing the current memberwise initializer

struct S {
    let s: String
    let i: Int

    // user declares:
    memberwise init() {}

It never occurred to me to allow a body on a memberwise initializer, but you’re right, this is a great feature. I love how this makes memberwise init behavior a modifier on existing initializers.

I’d be inclined to drop the “()” on these, though. It appears to imply an empty parameter list is allowed, when it is not.

The reason the parentheses are present is that the initializer may accept arguments that are not synthesized by the compiler.

Chris suggested a placeholder for the synthesize arguments. I like that suggestion a lot.

With that modification this declaration becomes:

memberwise init(...) {}

Does that address your concern?

···

On Dec 21, 2015, at 8:03 PM, Dave Abrahams <dabrahams@apple.com> wrote:

On Dec 21, 2015, at 5:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Dec 21, 2015, at 11:32 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Properties with initial values

struct S {
    let s: String = "hello"
    let i: Int = 42

    // user declares:
    memberwise init() {}
    // compiler synthesizes:
    init(s: String = "hello", i: Int = 42) {
        self.s = s
        self.i = i
    }
}

In the case of let properties, I’m uncomfortable with this behavior and it contradicts our current init rules (the synthesized code isn’t legal). Please change the example to var properties, and then it’s can fit with the model :-).

That said, I think the interaction of explicit initializers and memberwise initializers begs discussion. It would be a much simpler model to only get memberwise parameters for properties without an explicit init. Have you considered this model, what are the tradeoffs with allowing vars to overwrite them? Allowing an explicit init to be overwritten by the memberwise initializer seems potentially really confusing, and since you allow explicit arguments on inits, this could always be handled manually if someone really really wanted it. For example, they could write:

memberwise init(s : String) {
  self.s = s
}

If they wanted to get the sugar of memberwise inits (presumably for other properties without an explicit init) but still allow one to be overwritten.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#partial-memberwise-initialization&gt;Partial memberwise initialization

struct S {
    let s: String
    let i: Int

    // user declares:
    memberwise init() {
        i = getTheValueForI()
    }
    // compiler synthesizes (suppressing memberwise initialization for properties assigned in the initializer body):
    init(s: String) {
        self.s = s
        // body of the user's initializer remains
        i = getTheValueForI()
    }
}

This doesn’t seem like the right behavior to me. The compiler shouldn’t be in the business of scanning the body of the init to decide what members are explicitly initialized. I’d suggest that the model simply be that the contents of the {} on a memberwise init get injected right after the memberwise initializations that are done. This mirrors how properties with default values work.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#lazy-properties-and-incompatible-behaviors&gt;lazy properties and incompatible behaviors

struct S {
    let s: String
    lazy var i: Int = InitialValueForI()

    // user declares:
    memberwise init() {
    }
    // compiler synthesizes:
    init(s: String) {
        self.s = s
        // compiler does not synthesize initialization for i
        // because it contains a behavior that is incompatible with
        // memberwise initialization
    }
}

Yes, this is likely to be subsumed into JoeG’s "behaviors” proposal. In the meantime, I’d suggest no behavior change for lazy properties.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#nomemberwise-properties&gt;@nomemberwise properties

struct S {
    let s: String
    @nomemberwise let i: Int

    // user declares:
    memberwise init(configuration: SomeTypeWithAnIntMember) {
        i = configuration.intMember
    }
    // compiler synthesizes:
    init(configuration: SomeTypeWithAnIntMember, s: String) {
        self.s = s
        i = configuration.intMember
    }
}

@nomemberwise is an interesting extension, but since it is a pure extension over the basic model, I’d suggest moving this into a “possible future extensions” section. The proposal doesn’t need this feature to stand on its own.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#delegating-and-convenience-initializers&gt;delegating and convenience initializers

struct S {
    let s: String = "hello"
    let i: Int = 42

    // user declares:
    memberwise init() {}
    // compiler synthesizes:
    init(s: String = "hello", i: Int = 42) {
        self.s = s
        self.i = i
    }

    // user declares:
    memberwise init(describable: CustomStringConvertible) {
        self.init(s: describable.description)
    }
    // compiler synthesizes (adding forwarded memberwise parameters):
    init(describable: CustomStringConvertible, i: Int = 42) {
        self.init(s: describable.description, i: i)
    }
}

This example is introducing two things: convenience inits, but also parameter arguments. For the sake of the proposal, I’d suggest splitting the parameter arguments out to its own discussion. It isn’t clear to me whether the memberwise initializers should come before explicit arguments or after, and it isn’t clear if we should require the developer to put something in the code to indicate that they exist. For example, I could imagine a syntax like this:

memberwise init(…) {}
memberwise init(describable: CustomStringConvertible, ...) {

Where the … serves as a reminder that the init takes a bunch of synthesized arguments as well.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#subclass-initializers&gt;subclass initializers

class Base {
    let baseProperty: String

    // user declares:
    memberwise init() {}
    // compiler synthesizes:
    init(baseProperty: String) {
        self.baseProperty = baseProperty
    }
}

class Derived: Base {
    let derivedProperty: Int

    // user declares:
    memberwise init() {}
    // compiler synthesizes (adding forwarded memberwise parameters):
    init(baseProperty: String, derivedProperty: Int) {
        self.derivedProperry = derivedProperty
        super.init(baseProperty: baseProperty)
    }
}

This also seems unclear to me. We’re generally very concerned about tightly coupling derived classes to their bases (in an API evolution scenario, the two classes may be in different modules owned by different clients). Further, the base class may have multiple inits, and it wouldn’t be clear which one to get the arguments from.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#detailed-design&gt;Detailed design

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#syntax-changes&gt;Syntax changes

This proposal introduces two new syntactic elements: the memberwise initializer declaration modifier and the @nomemberwise property attribute.

As before, I’d suggest splitting @nomemberwise out to a “potential future extensions section”.

Algorithm

The steps described in this section will be followed by the compiler when it performs memberwise initialization synthesis. These steps supercede the synthesis of initialization for properties with initial values that exists today.

When the compiler performs memberwise initialization synthesis it will determine the set of properties that are eligible for synthesis that are not directly initialized in the body of the initializer. It will then synthesize parameters for them as well the initialization of them at the beginning of the initializer body.

I’d strongly suggest considering a model where properties that have an explicit initializer don’t get a memberwise init.

Have you considered whether computed properties make sense to loop into your model?

Typo "initialzier”.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#objective-c-class-import&gt;Objective\-C Class Import

Objective-C frameworks are extremely important to (most) Swift developers. In order to provide the call-site advantages of flexible memberwise initialization to Swift code using Cocoa frameworks this proposal recommends introducing a MEMBERWISE attribute that can be applied to Objective-C properties and initializers.

This is also an orthogonal extension on top of the base proposal. I’d suggest splitting it off to a “possible future extensions” section as well.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#impact-on-existing-code&gt;Impact on existing code

The changes described in this proposal are strictly additive and will have no impact on existing code.

One possible breaking change which may be desirable to include alongside this proposed solution is to elimintate the existing memberwise initializer for structs and require developers to specifically opt-in to its synthesis by writing memberwise init() {}. A mechanical transformation is possible to generate this declaration automatically if the existing memberwise initializer is removed.

I think that that would be very interesting to discuss, but I lean towards keeping our existing model for synthesizing a memberwise init if there is no other init in a struct (and we should do it for classes as well). Requiring someone to write "memberwise init() {}” is just boilerplate, and producing it as “internal” avoids most of the problems from being something undesirable being generated. That said, I see the argument that being more explicit is good.

Overall, I’m a huge fan of this proposal and the direction you’re going in.

-Chris

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

-Dave

That’s an interesting idea as well. A placeholder for the synthesized arguments is definitely an improvement to the proposal regardless of what syntax we end up with.

I’m open to plenty of bikeshedding on the details of syntax. :)

···

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

This example is introducing two things: convenience inits, but also parameter arguments. For the sake of the proposal, I’d suggest splitting the parameter arguments out to its own discussion. It isn’t clear to me whether the memberwise initializers should come before explicit arguments or after, and it isn’t clear if we should require the developer to put something in the code to indicate that they exist. For example, I could imagine a syntax like this:

memberwise init(…) {}
memberwise init(describable: CustomStringConvertible, ...) {

Where the … serves as a reminder that the init takes a bunch of synthesized arguments as well.

Perhaps we should drop the `memberwise` keyword and say:

  init(members) {}
  init(describable: CustomStringConvertible, members) {

Or even:

  init(vars) {}
  init(describable: CustomStringConvertible, vars) {

--
Brent Royal-Gordon
Architechies

Also, I don’t think it generates good API signatures. Take this example:

struct S {
  let s: String
  let i: Int

  // user declares:
  memberwise init() {}
  // compiler synthesizes:
  init(s: String, i: Int) {
    self.s = s
    self.i = i
  }
}

That is not a very descriptive API.

Well, yeah. This is a toy example. Do you often write APIs with properties like `s` and `i`? Or, for that matter, structs named `S`?

I often write APIs where the internal member’s name is not what I want to use as the label for the public API.

It’s also not necessarily the case that your internal names are what you want exposed.

The proposal already states that a memberwise initializer only includes parameters for properties that are at least as visible as the initializer itself. So if you can see the `s` and `i` parameters, you can also see the `s` and `i` properties. It's not going to expose anything that isn't already visible.

This isn’t about access modifiers, it’s about the name chosen for internal variables vs. names chosen for API contracts.

-David

···

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

Thanks for responding Brent. I agree with all of your comments.

···

On Dec 22, 2015, at 12:39 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

Also, I don’t think it generates good API signatures. Take this example:

struct S {
  let s: String
  let i: Int

  // user declares:
  memberwise init() {}
  // compiler synthesizes:
  init(s: String, i: Int) {
    self.s = s
    self.i = i
  }
}

That is not a very descriptive API.

Well, yeah. This is a toy example. Do you often write APIs with properties like `s` and `i`? Or, for that matter, structs named `S`?

It’s also not necessarily the case that your internal names are what you want exposed.

The proposal already states that a memberwise initializer only includes parameters for properties that are at least as visible as the initializer itself. So if you can see the `s` and `i` parameters, you can also see the `s` and `i` properties. It's not going to expose anything that isn't already visible.

I would actually prefer the rule to simply be this: when an init() is modified by memberwise, the labelled parameters will be set. This lookup will try both the argument name and the parameter name, in that order, for reasons that become more clear with convenience inits described later.

So you would have this:

memberwise init(name s: String, value i: Int) {
   // autogenerated: self.s = s; self.i = i
}

That seems awfully inconvenient for a convenience feature.

--
Brent Royal-Gordon
Architechies

How are you going to deal with public properties creating a breaking change in the memberwise init()?

Example:

v1.0 of the API ships:

struct Person {
    let name: String
}

v1.1 of the API ships:

struct Person {
    let name: String
    let age: Int
}

When you use the implicit memberwise init(), the above change is now a breaking change. There are ways around it, but all seem fragile to me - for example, requiring a default value for new properties on the declaration site.

Similarly, the order of the members must remain the same now, changing them is a breaking change. This is ok for structs today because the generated init() is only available within a module so all of the places that use it are limited within your own codebase.

This is why I do not like like the implicit behavior and the @nomemberwise attribute on the declaration of the property; it tightly couples together things that are inherently fragile.

-David

···

On Dec 22, 2015, at 8:46 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Hi Chris,

I have given your feedback a lot of thought and have taken another run at this proposal from a slightly different angle. I believe it is a significant improvement.

I believe the programmer model is now very clear straightforward:

The set of properties that receive memberwise initialization parameters is determined by considering only the initializer declaration and the declarations for all properties that are at least as visible as the initializer (including any behaviors attached to the properties). The rules are as follows:

  • Their access level is at least as visible as the memberwise initializer.
  • They do not have a behavior which prohibits memberwise initialization.
  • The property is not annotated with the @nomemberwise attribute.
  • The property is not included in the @nomemberwise attribute list attached of the initializer. If super is included in the @nomemberwise

The parameters are synthesized in the parameter list in the location of the ... placeholder. They are ordered as follows:

  • All properties without default values precede properties with default values.
  • Within each group, superclass properties precede subclass properties.
  • Finally, follow declaration order

The new model has also dramatically simplified the implementation details. No more need for the compiler to scan the initializer body!

There are still some details present that you provided feedback on. My reply from last night is still valid discussion around those issues. Please reply inline to that message if possible.

I’m sure there are still plenty of details to discuss and work through, but I hope you will agree that these changes are a step in the right direction.

https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md

Matthew

Hi Chris,

I have given your feedback a lot of thought and have taken another run at this proposal from a slightly different angle. I believe it is a significant improvement.

Hi Matthew,

I continue to really like the approach and direction. Here’s an attempt to respond to both of your responses, I hope this comes across somewhat coherent:

I hope you’re willing to entertain on some discussion on some aspects of the proposal that you are not immediately sold on. :)

Yes, absolutely.

In the case of let properties, I’m uncomfortable with this behavior and it contradicts our current init rules (the synthesized code isn’t legal). Please change the example to var properties, and then it’s can fit with the model :-).

I understand that this is not legal under the current init rules because the compiler currently produces a synthesized initialization of the properties to their initial values at the beginning of the initializer. I actually don’t like this behavior because it doesn’t allow an initializer body to initialize a let property with an "initial value”.

I’m pretty sure I’m not alone in this. People want to use immutable properties with “default values” (I believe this is what most Swift developers are calling what the Swift team refers to as “initial values”) without a requirement that all instances actually have that value. It was actually pretty surprising to me, and I’m sure to others as well, to discover this limitation. I actually thought it was a limitation of the current implementation rather than something that was intentionally designed. I’m surprised to hear otherwise.

I completely agree with your desire to support this, and I’m sure that if we did, that a ton of people would use it and love it. However, I really don’t think this is a good idea.

There are two major problems:

Problem 1: supporting this would *prevent* us from allowing memberwise initializers to be public. A really important feature of the memberwise design is that it is “just sugar” and that people can write the initializer out long hand if needed (e.g. if they want to add or reorder members without breaking API). With your proposed design, I could write:

public class X {
  let a = 42
  public memberwise init(...) {}
}

and use it with: X(a: 17). However, there is no way in swift to write that initializer out longhand. This is a critical problem to me.

Problem 2: This can cause very surprising performance issues, because it forces the let property to be stored. With the previous example, it is a goal for us to be able to compile:

public class X {
  let a = 42
}

into the equivalent of:

public class X {
  var a : Int { return 42 }
}

because people like to use local lets as manifest constants (avoiding “magic numbers”). With your proposal, we’d lose this capability, and we’d have to store them any time there is a memberwise initializer.

Neither of these problems apply to vars, which is why I think we can support vars in this model, but not lets.

That said, I think the interaction of explicit initializers and memberwise initializers begs discussion. It would be a much simpler model to only get memberwise parameters for properties without an explicit init. Have you considered this model, what are the tradeoffs with allowing vars to overwrite them? Allowing an explicit init to be overwritten by the memberwise initializer seems potentially really confusing, and since you allow explicit arguments on inits, this could always be handled manually if someone really really wanted it. For example, they could write:

memberwise init(s : String) {
  self.s = s
}

If they wanted to get the sugar of memberwise inits (presumably for other properties without an explicit init) but still allow one to be overwritten.

Personally, I think there is a lot of value in allowing memberwise initialization for properties that do contain an initial value. Imagine a hypothetical Swift version of Cocoa Touch. UILabel might have initial values for text, font, textColor, etc but still want to allow clients to provide memberwise initialization arguments to override the default value. I think there are many cases like this both in UI code and elsewhere.

I think I confused the issue. If we have to support properties that have a default value, then the model I’m advocating for is that this:

class C {
  let x : Int
  var y : Int = foo()

  memberwise init(...) {}
}

compile into:

init(x : Int, y : Int = foo()) {
  self.x = x
  self.y = y
}

Pertinent points of this are that lets without a default value would still turn into arguments, and that any side effects of the var initializer would be squished. Another potential model is to compile it to:

init(x : Int, y : Int) {
  self.x = x
  self.y = foo()
}

which is guaranteed to run the side effect, but requires y to be specified. I do not think it is a good model to compile it to:

init(x : Int, y : Int? = nil) {
  self.x = x
  self.y = y ?? foo()
}

because that would allow passing in an Int? as the argument. The final model (which I know you don’t like) is for memberwise initializers to *only* apply to properties without a default value.

This doesn’t seem like the right behavior to me. The compiler shouldn’t be in the business of scanning the body of the init to decide what members are explicitly initialized. I’d suggest that the model simply be that the contents of the {} on a memberwise init get injected right after the memberwise initializations that are done. This mirrors how properties with default values work.

The model does inject the synthesized memberwise initialization just prior to the body of the initializer.

As written the proposal does scan the body of the init in order to determine which properties receive memberwise initialization. The idea here is that it provides additional flexibility as it allows specific initializers to “opt-out” of memberwise initialization synthesis for some properties while receiving it for others.

This is a very problematic model for me, because it can lead to serious surprises in behavior, and in the case of lets, shares the problems above with not allowing one to define the long-hand form explicitly.

I don’t think the proposal changes lazy properties.

I agree, I was saying that I like that :)

@nomemberwise is an interesting extension, but since it is a pure extension over the basic model, I’d suggest moving this into a “possible future extensions” section. The proposal doesn’t need this feature to stand on its own.

Allowing type authors to restrict memberwise initialization to a subset of properties is an important aspect of “flexible” memberwise initialization IMO. In my mind, this is about allowing the author of a type to segment properties that are “user configurable” from properties that are implementation details.

I understand, but it is a pure extension to the basic proposal. The proposal is complex enough as it is, so inessential parts should be split out for discussion and implementation. I’m not saying that we shouldn’t do @nomemberwise in (e.g.) the swift 3 timeframe, I’m saying that it should be a separate discussion informed by the design and implementation process of the base proposal.

This also seems unclear to me. We’re generally very concerned about tightly coupling derived classes to their bases (in an API evolution scenario, the two classes may be in different modules owned by different clients). Further, the base class may have multiple inits, and it wouldn’t be clear which one to get the arguments from.

I know there are a lot of complexities here. It is certainly possible I am missing some showstoppers, especially related to resilience, etc.

User code would of course need to provide sufficient parameters to disambiguate the call to the base class initializer.

The goal in this section is to enable memberwise initialization to be used in a class hierarchy. I think UIKit is a good case to consider for the sake of discussion. In a hypothetical Swift version of UIKit we would want to allow memberwise initialization of appearance attributes regardless of where they are declared in the class hierarchy. (I would hope a designed-for-Swift UIKit would not rely so heavily on inheritance, but nevertheless I think it makes good case study for discussing flexible memberwise initialization).

The same mechanism that handles inheritance should also be able to handle delegating and convenience initializers. The idea is to write a delegating or convenience initializer that wraps the non-memberwise portion of a memberwise initializer while still allowing the memberwise initialization to “flow through” and be visible to callers of the delagating / convenience initializer.

I attempted to outline a basic strategy for handling propagation of memeberwise initialization through the inheritance hierarchy as well as other cases of initializer delegation in the detailed design. It is definitely not complete, almost certainly has flaws and omissions, etc. I’m hoping we can flesh out the details through community discussion.

I hope you will agree that it is important to support inheritable memberwise initialization and that we do need to address this in some way.

I don’t agree. memberwise is a sugar feature intended to handle the most common scenarios. One of the major reasons we don’t support memberwise inits in classes today is that we have no ability to know what superclass init to call (and root classes aren’t interesting enough to provide a solution that only works for them).

Given that your proposal allows user code to be added to the synthesized init, I’d really strongly prefer that this be a compile time error, because super.init was never invoked (ok ok, if the superclass has an "init()” as its only DI then yes, we can synthesize it by default like we do today).

class Derived: Base {
    let derivedProperty: Int

    // user declares:
    memberwise init() {}
    // compiler synthesizes (adding forwarded memberwise parameters):
    init(baseProperty: String, derivedProperty: Int) {
        self.derivedProperry = derivedProperty
        super.init(baseProperty: baseProperty)
    }
}

Instead, the user should have to write:

memberwise init(baseProperty : Int, ...) {
  super.init(baseProperty: baseProperty)
}

This doesn’t reduce the utility of this feature, and it preserves separability of class from superclass.

Thank you again for pushing this forward!

-Chris

···

On Dec 22, 2015, at 8:46 AM, Matthew Johnson <matthew@anandabits.com> wrote:

How are you going to deal with public properties creating a breaking change in the memberwise init()?

Example:

v1.0 of the API ships:

struct Person {
    let name: String
}

v1.1 of the API ships:

struct Person {
    let name: String
    let age: Int
}

When you use the implicit memberwise init(), the above change is now a breaking change. There are ways around it, but all seem fragile to me - for example, requiring a default value for new properties on the declaration site.

Adding a new member to a struct is itself a breaking change. I don’t see how memberwise initialization makes this worse.

You could even use memberwise initialization in v1.0 of your API and then update your implementation to be manual in v2.0 if necessary. Nothing in the proposal prohibits you from taking full manual control if resilient API evolution requires that.

One option I considered and think is still worth considering is requiring an @memberwise attribute on properties to explicitly opt-in to memberwise initialization. This is discussed in the proposal under alternatives considered. You can find the rationale for my decision there. I do think it is valid to debate which is a better default.

Similarly, the order of the members must remain the same now, changing them is a breaking change. This is ok for structs today because the generated init() is only available within a module so all of the places that use it are limited within your own codebase.

The concern about order does have some validity. If declaration order does not provide sufficient control the proposal could include something which allows a different order to be explicitly specified for memberwise initialization.

Also, keep in mind that nothing in this proposal is *required*. You can always decline to use the feature.

This is why I do not like like the implicit behavior and the @nomemberwise attribute on the declaration of the property; it tightly couples together things that are inherently fragile.

Do you have any suggestions on how to improve the proposal without giving up on its underlying goal of solving the M x N complexity problem (M members x N initializers)?

If you haven’t taken a look at the updated proposal please do so. I’m sure there will still be things you are not totally comfortable with but you might like it better than yesterday's draft.

···

On Dec 22, 2015, at 11:26 AM, David Owens II <david@owensd.io> wrote:

-David

On Dec 22, 2015, at 8:46 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi Chris,

I have given your feedback a lot of thought and have taken another run at this proposal from a slightly different angle. I believe it is a significant improvement.

I believe the programmer model is now very clear straightforward:

The set of properties that receive memberwise initialization parameters is determined by considering only the initializer declaration and the declarations for all properties that are at least as visible as the initializer (including any behaviors attached to the properties). The rules are as follows:

  • Their access level is at least as visible as the memberwise initializer.
  • They do not have a behavior which prohibits memberwise initialization.
  • The property is not annotated with the @nomemberwise attribute.
  • The property is not included in the @nomemberwise attribute list attached of the initializer. If super is included in the @nomemberwise

The parameters are synthesized in the parameter list in the location of the ... placeholder. They are ordered as follows:

  • All properties without default values precede properties with default values.
  • Within each group, superclass properties precede subclass properties.
  • Finally, follow declaration order

The new model has also dramatically simplified the implementation details. No more need for the compiler to scan the initializer body!

There are still some details present that you provided feedback on. My reply from last night is still valid discussion around those issues. Please reply inline to that message if possible.

I’m sure there are still plenty of details to discuss and work through, but I hope you will agree that these changes are a step in the right direction.

https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md

Matthew

When using the memberwise initializer those properties are always assigned a value twice. First the initial value and then the value provided by the initializer. The first value is constructed and assigned unnecessarily and in corner cases this might even be problematical, e.g. if creating a value of a certain kind has a side effect like incrementing an instance counter or logging something.

-Thorsten

···

Am 22.12.2015 um 05:43 schrieb Matthew Johnson via swift-evolution <swift-evolution@swift.org>:

What do you think the downsides are of synthesizing memberwise initialization for properties with an “initial value”?

I deny that, and even if it is true, there is a price to pay — and that is more than the lines of code that are required…

Personally, I think it is a lot more readable to put members separate lines, but if you don’t like doing that:

You are focusing on the least important thing here — I basically meant to say that the lower line count is no real benefit, but that there are advantages that aren't as easy to spot.

What are those advantages precisely?

1. It interacts well with access control

Better than Kotlin? Please prove this.

Please look at the examples I have in the Access Control section of the proposal. I spent some time reading the Kotlin docs and it isn’t clear to me that Kotlin can do this. But maybe it can. I don’t know Kotlin well. It sounds like you do, so if it can, please show how this is done in Kotlin.

I actually don't know Kotlin that well — and that contributes to my evaluation of their solution: It is intuitive, and I did not have to read a huge article to understand it.
What I can see in your example is that the proposed syntax allows me to trigger compiler errors that will never happen with Kotlin (and errors that cannot happen are the ones I like the most).

Can you point out what potential compiler errors you are concerned about? Are you referring to the access control example where the compiler does not synthesize initialization of a private member in an internal memberwise initializer and thus requires manual initialization of the private member?

2. Partial memberwise initialization is possible

The same with Kotlin — and imho at least as easy

That isn’t clear from the Kotlin docs. It may well be that they are just missing examples. Please post some samples showing how this is handled.

see below; of course, the sample won't use memberwise initialization, but archive the same in a (imho) better way

3. It allows memberwise initializers to accept non-memberwise parameters to initialize private state

I think this can be achieved with less effort using function-like class declaration (afair Joe already gave an example)

I don’t see either of the examples Joe posted doing this. Here is an example showing what I mean:

struct S {
    let s: String
    private let i: Int

    // user declares:
    memberwise init(other: S, ...) {
        i = other.i
    }
    // compiler synthesizes (suppressing memberwise initialization for properties with lower visibility):
    init(other: S, s: String) {
        /* synthesized */ self.s = s

        // body of the user's initializer remains
        i = other.i
    }
}

Instead of several examples, I'll use just a single one to illustrate a bunch of points — and I'm leaving out comments on purpose, because I hope to see others participating with their interpretation:

public class Customer(title: String, private var birthday: NSDate?, public address: String = "n/a"): Person {

  protected let statusPoints = - birthday?.timeIntervalSinceNow() ?? 0.0

  let constantWithHardToExpressValue: Int

  lazy var age: Int = dateCalculationIsHard(birthday)

  init {
    constantWithHardToExpressValue = Int(statusPoints) + 1
    super.init(title: title)
  }
  
  public init(titlePrefix: String, titleSuffixObject: Any) {
    init(title: titlePrefix + titleSuffixObject.description, birthday: NSDate())
  }
}

So: Dear reader, please do my job and explain the "pseudo"-source above ;-) — or ask questions if you are just puzzled by it.

This must be what you are suggesting for Swift because the example uses NSDate? Thank you for providing a concrete example to look at.

Is it correct that this class would have the following members in addition to the ones declared in the body? You used access control modifiers in that parameter list. Would you allow any annotations that are valid for properties that can be initialized (i.e. not lazy, but maybe an observable behavior if Property Behaviors are accepted)?

let title: String // internal
private var birthday: NSDate?
public let address: String = “n/a”

It actually looks like you pass `title` to super so maybe that isn’t expected to synthesize a member? If that is the case how does the compiler determine which parameters should receive member synthesis? If not, I assume super would not have a `title` member and is doing something else with that argument. Is that what you intend?

Is it also correct that all of the following forms would be valid at call sites?

Customer(title: “a title”, birthday: nil)
Customer(title: “a title”, birthday: nil, address: “an address”)
Customer(titlePrefix: “a prefix”, titleSuffixObject: Foo())

If so, does the initializer used in the first two call examples (the designated / memberwise initializer?) receive the same access control as the type itself since it cannot be specified directly?

Is it also correct that the init block that calls super would be invoked regardless of which initializer form is used? When would this block run? Immediately after the memberwise initializer is called? Is it the “body” of the memberwise initializer? It would need to run prior to any use of self in the body of `public init(titlePrefix: String, titleSuffixObject: Any)` in order to follow Swift's rules of definitive initialization.

Is the `public init(titlePrefix: String, titleSuffixObject: Any)` initializer actually a convenience initializer? Is it required to call the designated / memberwise initializer before it does anything else? What rules need to be followed to ensure definitive initialization happens? Is this rule applicable to all additional initializers the author writes? If it is a convenience initializer maybe it just follows the current rules for those in Swift?

Maybe the memberwise parameters plus the body is the single and only designated initializer for the type? Is that what you’re suggesting? What if the init body needs additional non-memberwise parameters? Is it allowed to specify those? If so how is that accomplished? How is the parameter ordering in the final parameter list determined? Would you use the `…` placeholder for the memberwise parameters like the current proposal does?

What happens if another initializer wants to initialize `constantWithHardToExpressValue` to a value different than that in the factored out init block?

What if the type needs to support more than one designated initializer? Is that possible? Would all designated initializers be required to receive the same set of memberwise parameters? If that isn’t what is required how would the author implement the necessary initializers?

There a lot of details to work through here. If we are do that we would could analyze the differences of the two approaches. I think that is a fair request. There are definitely some significant differences between them. I will hold off on commenting further until we nail down exactly what it is you would like to see and how it would work.

4. More than one memberwise initializer is possible

Kotlin has no need for memberwise initializers at all, and I see this as a big advantage

Please explain how it is an advantage. How does Kotlin handle a case where you have some private state that needs to be initialized internally to protect invariants, but also some members which users can initialize (such as appearance attributes on a UI widget)?

For me, something that is not necessary is always an advantage — because you can simply remove it and have a result that is more elegant and compact.
Why would you want to add a feature that is not needed? It's just more work for those who actually build it, and it's more work for those who have to learn how to use it.

I don’t understand how this is a response to my question. It is very common for a UI widget to have both internal state that is necessary to its implementation but not visible to users, as well as appearance properties that are directly configurable by its users. In any case, lets focus on the previous example you gave as I believe it will address the question.

5. Memberwise initialization of properties with declaration modifiers, behaviors / delegates is possible

https://kotlinlang.org/docs/reference/delegated-properties.html
(afaik this is not only possible, it's handled by the current compiler for a long time)

Yes, I know Kotlin has this feature. It isn’t clear from the docs how initialization of such properties is handled (for example an Observable property). Maybe you can provide some examples of how this works.

No, I can't (well, I accidentally did it partly...) — but I don't see why I should prove the features of a system that actually exists (and where everyone can easily check the behavior):
I think it's not that presumptuous to assume that the Kotlin-compiler has no flaws which are so fundamental.

You are suggesting a design we should use for a feature in Swift. I don’t think it is unreasonable to ask how that design would address specific use cases in Swift. It is not enough to point at another language with a similar feature. It is also not enough to just assume the Kotlin model is acceptable without being able to answer how it works in specific use cases.

Afaics there not much room left for the promised additional flexibility… and the problem with default values for constants just doesn't exist in Kotlin at all.

This is currently a problem in Swift. I am confident that it can be solved one way or another. I don’t think a solution should be tied to the “type initializer parameter list” syntax.

As I said before:
There is no need to copy, but there is also no need to discard a working solution without further explanation.

I am taking a nontrivial amount of time to work through this with you and am keeping an open mind during that discussion, but it will require specific details to change my mind. Lets focus on the example you gave and get the details of what you want to see worked out.

If you can clearly demonstrate how Kotlin is superior in a specific area I will give that great consideration. I want this proposal to be the best it can be. However, you’re going to need to do more than just link to the docs which I have already looked at.

I see it from the other direction:
You have a "theory" (the proposal) and claim it is sound; Kotlin, on the other hand, is real working code!

I am trying to solve a problem for Swift. I am willing to continue working through the Kotlin approach with you so we can understand the details of how it would fit in with Swift. That will allow a fair comparison of the differences between the approaches.

The good news is that as far as I can tell the things you like about what Kotlin is doing are not mutually exclusive with this proposal at all. Think of it this way - this proposal provides a flexible and orthogonal foundation for memberwise initialization. If desired, a future enhancement could easily be developed to provide additional syntactic sugar on top of it. The example Joe posted shows how that might work.

If this proposal is accepted and you want to pursue a proposal for that additional layer of syntactic sugar to get closer to Kotlin syntax I encourage you to do that. The new syntax should be evaluated independently as its own proposal. I would be happy to help show how your desired syntax could be transformed into existing syntax (including the memberwise initialization syntax if this proposal is accepted).

I guess you really want to see your proposals accepted — and I understand that, as I'm quite sure that you put a huge amount of work into them.

I want to see the problems solved in the best way possible. That is why I am investing time in the proposals and also why I am investing time in exploring the alternative you believe is better. If there is a better approach or a way to improve my proposal I would prefer to identify that now rather than have it accepted as-is.

But a "let's just take my solution and maybe integrate yours laterl"-attitude imho is not the right way:
If it is used to silence opposers without actually supporting them later, it is wily; and if it is a honest offer, we'll end up with a language that is extrem complicated because it tries to please everyone (and orthogonality would suffer as well).

I was trying to show that the two features are not strictly mutually exclusive as one can be expressed in terms of the other. If you don’t like that approach and think it makes the language too complex I understand that position. I am definitely not trying to just silence you.

Let’s work together to understand in detail the differences in these approaches and their respective advantages and disadvantages. The first step in doing that is getting the design of what you would like to see nailed down further.

Matthew

···

On Jan 1, 2016, at 6:57 AM, Tino Heth <2th@gmx.de> wrote:

What are those advantages precisely?

subjective: Easier to understand
objective: Saves keywords/syntax ("…", "memberwise", "@nomemberwise", "required", "convenience", "@default"…)

What I can see in your example is that the proposed syntax allows me to trigger compiler errors that will never happen with Kotlin (and errors that cannot happen are the ones I like the most).

Can you point out what potential compiler errors you are concerned about? Are you referring to the access control example where the compiler does not synthesize initialization of a private member in an internal memberwise initializer and thus requires manual initialization of the private member?

Exactly.

public class Customer(title: String, private var birthday: NSDate?, public address: String = "n/a"): Person {

  protected let statusPoints = - birthday?.timeIntervalSinceNow() ?? 0.0

  let constantWithHardToExpressValue: Int

  lazy var age: Int = dateCalculationIsHard(birthday)

  init {
    constantWithHardToExpressValue = Int(statusPoints) + 1
    super.init(title: title)
  }
  
  public init(titlePrefix: String, titleSuffixObject: Any) {
    init(title: titlePrefix + titleSuffixObject.description, birthday: NSDate())
  }
}

So: Dear reader, please do my job and explain the "pseudo"-source above ;-) — or ask questions if you are just puzzled by it.

This must be what you are suggesting for Swift because the example uses NSDate?

Yes indeed — I'm quite sure Kotlin couldn't compile that ;-).

Is it correct that this class would have the following members in addition to the ones declared in the body?

Yes (but see below)

You used access control modifiers in that parameter list. Would you allow any annotations that are valid for properties that can be initialized (i.e. not lazy, but maybe an observable behavior if Property Behaviors are accepted)?

let title: String // internal
private var birthday: NSDate?
public let address: String = “n/a”

It actually looks like you pass `title` to super so maybe that isn’t expected to synthesize a member?

Yes

If that is the case how does the compiler determine which parameters should receive member synthesis?

It could be inferred, but actually I'd prefer explicit rules (those could be "let x: Int" -> constant, "var…" variable, "x: Int" nothing is synthesized, the parameter is used for the parent initializer or to compute the value of a member that isn't visible)

If not, I assume super would not have a `title` member and is doing something else with that argument. Is that what you intend?

Yes

Is it also correct that all of the following forms would be valid at call sites?

Customer(title: “a title”, birthday: nil)
Customer(title: “a title”, birthday: nil, address: “an address”)
Customer(titlePrefix: “a prefix”, titleSuffixObject: Foo())

Yes

If so, does the initializer used in the first two call examples (the designated / memberwise initializer?) receive the same access control as the type itself since it cannot be specified directly?

Yes and no:
Kotlin allows you to limit the visibility of the initializer:
class Customer private init(...

Is it also correct that the init block that calls super would be invoked regardless of which initializer form is used? When would this block run? Immediately after the memberwise initializer is called? Is it the “body” of the memberwise initializer?

Yes, that is the idea

It would need to run prior to any use of self in the body of `public init(titlePrefix: String, titleSuffixObject: Any)` in order to follow Swift's rules of definitive initialization.

True, the same as it is now

Is the `public init(titlePrefix: String, titleSuffixObject: Any)` initializer actually a convenience initializer?

Yes

Is it required to call the designated / memberwise initializer before it does anything else? What rules need to be followed to ensure definitive initialization happens? Is this rule applicable to all additional initializers the author writes? If it is a convenience initializer maybe it just follows the current rules for those in Swift?

Yes, everything in this hypothetic model behaves like it does now

Maybe the memberwise parameters plus the body is the single and only designated initializer for the type? Is that what you’re suggesting?

It would be possible to allow additional designated initializers, but imho this should at least be discouraged.

What if the init body needs additional non-memberwise parameters? Is it allowed to specify those?

Yes

If so how is that accomplished?

See above ("which parameters receive member synthesis")

How is the parameter ordering in the final parameter list determined?

Like in a regular function call.

Would you use the `…` placeholder for the memberwise parameters like the current proposal does?

No, no need for the dots

What happens if another initializer wants to initialize `constantWithHardToExpressValue` to a value different than that in the factored out init block?

You would have to include the member in the parameter list instead of the class body

What if the type needs to support more than one designated initializer? Is that possible?

Yes (see above)

Would all designated initializers be required to receive the same set of memberwise parameters? If that isn’t what is required how would the author implement the necessary initializers?

That would be complicated, and is one reason for discouraging multiple designated initializers (the other reason is the hassle when subclassing)

You are suggesting a design we should use for a feature in Swift. I don’t think it is unreasonable to ask how that design would address specific use cases in Swift. It is not enough to point at another language with a similar feature. It is also not enough to just assume the Kotlin model is acceptable without being able to answer how it works in specific use cases.

I didn't create a real competing proposal — I just pointed out to an existing solution that addresses the problems successfully.
It's hard to define how a hypothetic feature will interact with other hypothetic features...

I am taking a nontrivial amount of time to work through this with you and am keeping an open mind during that discussion, but it will require specific details to change my mind.

I really don't expect to convince you to discard your results — our standpoints and opinions are just very different (in other questions even more than in this one…), and consensus is very unlikely.
As said before, I have much respect for the labour associated with a proposal, and I hope you see my objections not only as an annoyance, but rather as a opportunity to sharpen your arguments.
(I personally prefer sound opposition over apathy most of the time).

There a lot of details to work through here. If we are do that we would could analyze the differences of the two approaches. I think that is a fair request. There are definitely some significant differences between them. I will hold off on commenting further until we nail down exactly what it is you would like to see and how it would work.
I am trying to solve a problem for Swift. I am willing to continue working through the Kotlin approach with you so we can understand the details of how it would fit in with Swift. That will allow a fair comparison of the differences between the approaches.
I want to see the problems solved in the best way possible. That is why I am investing time in the proposals and also why I am investing time in exploring the alternative you believe is better. If there is a better approach or a way to improve my proposal I would prefer to identify that now rather than have it accepted as-is.
I was trying to show that the two features are not strictly mutually exclusive as one can be expressed in terms of the other. If you don’t like that approach and think it makes the language too complex I understand that position. I am definitely not trying to just silence you.

Let’s work together to understand in detail the differences in these approaches and their respective advantages and disadvantages. The first step in doing that is getting the design of what you would like to see nailed down further.

Relax - no one else seems to care, so apparently I'm just a single lunatic without any support and a ticking end-of-holydays countdown (serious work won't leave much time for fighting lost battles ;-)
So taking a neutral standpoint, the best advice I could give is to basically ignore myself (taking back my own standpoint as a friend of clear words, I'd appreciate a non-implicit reaction ;-)
I still think initialization is already a heavy topic that shouldn't get even more complicated and probably won't change my mind — but I can get my head around anything that has been brought up, and considering the community reaction, the majority will do so at least as well (and those who don't follow the discussion on the list simply will have to deal the results, as it has been before).

Best regards,
Tino

Yes you can get close, but:

1. Its weird that you can only do this in an extension.

This is the way the current implicit initializer works. It is not synthesized if you define any initializers in the body of the type. There are good reasons it works this way and the current proposal does not change those rules.

2. Its not quite the same as the proposal the current member-wise initialiser does not make the init arguments optional (the arguments themselves do not have defaults), i.e. with your example `let defaultOriginRect = Rect(size: Size(width: 5.0, height: 5.0))` fails whereas it would work for the proposal (this could also be true if the existing struct memberwise init and the new `memberwise init(..)` where changed to provide init argument defaults).

The implicit memberwise initializer currently in the language does not provide defaults for parameters. This proposal changes that behavior and provides defaults if the the member is a `var` and has an initial value.

Unfortunately I was not able to find a solution to allow synthesized parameters for `let` members to have default values. This is because the current semantics for `let` members do not allow the member to be initialized to anything other than the initial value if one is provided. I am hoping a solution to this will be identified in the future and have suggested one possible mechanism `@default` in the future enhancements section.

3. Only ‘really' works for structs, the compiler doesn’t write a member-wise initialiser for classes (just a default initializer).

That is true about the current behavior of the language but is not true with regards to the current proposal.

4. Still need the compiler to provide both default and member-wise initialisers, whereas this proposal would allow the existing default and member-wise initialisers to be deprecated and just the new member-wise initialiser would remain which would simplify the language and make it clear what was happening (this could also be true if a `memberwise init(..)` where added and existing compiler written inits removed).

This proposal does not change anything with regard to the default initializer.

···

On Jan 4, 2016, at 5:48 PM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

On 5 Jan 2016, at 10:16 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

struct Rect { var origin: Point = Point(), size: Size = Size() }
extension Rect {
       init(center: Point, size: Size) {
           let originX = center.x - (size.width / 2)
           let originY = center.y - (size.height / 2)
           self.init(origin: Point(x: originX, y: originY), size: size)
       }
}

The “type parameter list” syntax is sugar that could be implemented as a layer on top of the current proposal or could be implemented orthogonally.

Hi Howard,
I've heard this argument before, so I'll repeat my answer as well:
Both offers don't make sense, it's either one way or the other (or something completely different).

I don’t think it’s clear whether both make sense or not. They are not mutually exclusive and are aimed at solving related, but different problems. If we adopt the current proposal, the Kotlin / Scala syntax *might* make sense for some use cases. It would depend upon whether the current proposal is considered too verbose in enough cases or not. This may or may not turn out to be the case.

The reason my proposal looks the way it does is because there are specific goals it intends to achieve and problems it is intended to solve. These goals and problems are not adequately addressed by the Kotlin / Scala syntax.

If diversity starts here, why not have "const" and "val" beside "let", or allow "fn" and "lambda"?

@Matthew: Please, if you support something, then say so - using clear words, not phrases like "could be implemented”.

This phrasing is plenty clear IMO. I am stating a possibility. I do not have a position on whether or not it is a good idea at the moment.

I have made some modifications to the proposal over the last few days. These changes have been partly motivated by considering the conversation we have had. You may wish to give it another look. If so, please look here: https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/0018-flexible-memberwise-initialization.md\. There is currently an open PR for the latest change so it is not in the main Swift evolution repo yet.

The most recent change includes a discussion of why it does not use the Scala / Kotlin syntax. You may not like the choice but I hope you can at least understand the rationale (even if you disagree with it).

···

On Jan 5, 2016, at 12:12 PM, Tino Heth <2th@gmx.de> wrote:

I was guessing that the current proposal does not change anything re.
default and current member wise initializers and so in addition to
suggesting Scala syntax I was also suggesting the transformation shown, or
its equivalent. The advantage of having a member wise init that has default
arguments and argument labels are considerable:

1. Allows lets as well as vars
2. Allows partial custom initialization
3. Eliminates need for other mechanisms, i.e. default and existing member
wise initialization

These facilities could be added to `memberwise init(...)` as well. In
particular, if a member wise init was present then an initialized property
could have a label, e.g.:

     class C {
         let example name: Type = initial
         memberwise init(...)
     }

Would become the equivalent of:

     class C {
         let name: Type
         init(example name: Type = initial) {
             self.name = name
         }
      }

The Scala syntax is just a shorter alternative, ideally there be a
discussion of the pros and cons of the two syntax that included the
possibility of the wider set of objectives as outlined in the numbered
points above.

···

On Tuesday, 5 January 2016, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 4, 2016, at 5:48 PM, Howard Lovatt <howard.lovatt@gmail.com > <javascript:_e(%7B%7D,'cvml','howard.lovatt@gmail.com');>> wrote:

Yes you can get close, but:

1. Its weird that you can only do this in an extension.

This is the way the current implicit initializer works. It is not
synthesized if you define any initializers in the body of the type. There
are good reasons it works this way and the current proposal does not change
those rules.

2. Its not quite the same as the proposal the current member-wise
initialiser does not make the init arguments optional (the arguments
themselves do not have defaults), i.e. with your example `let
defaultOriginRect = Rect(size: Size(width: 5.0, height: 5.0))` fails
whereas it would work for the proposal (this could also be true if the
existing struct memberwise init and the new `memberwise init(..)` where
changed to provide init argument defaults).

The implicit memberwise initializer currently in the language does not
provide defaults for parameters. This proposal changes that behavior and
provides defaults if the the member is a `var` and has an initial value.

Unfortunately I was not able to find a solution to allow synthesized
parameters for `let` members to have default values. This is because the
current semantics for `let` members do not allow the member to be
initialized to anything other than the initial value if one is provided. I
am hoping a solution to this will be identified in the future and have
suggested one possible mechanism `@default` in the future enhancements
section.

3. Only ‘really' works for structs, the compiler doesn’t write a
member-wise initialiser for classes (just a default initializer).

That is true about the current behavior of the language but is not true
with regards to the current proposal.

4. Still need the compiler to provide both default and member-wise
initialisers, whereas this proposal would allow the existing default and
member-wise initialisers to be deprecated and just the new member-wise
initialiser would remain which would simplify the language and make it
clear what was happening (this could also be true if a `memberwise
init(..)` where added and existing compiler written inits removed).

This proposal does not change anything with regard to the default
initializer.

On 5 Jan 2016, at 10:16 AM, Matthew Johnson <matthew@anandabits.com > <javascript:_e(%7B%7D,'cvml','matthew@anandabits.com');>> wrote:

struct Rect { var origin: Point = Point(), size: Size = Size() }
extension Rect {
       init(center: Point, size: Size) {

           let originX = center.x - (size.width / 2)

           let originY = center.y - (size.height / 2)

           self.init(origin: Point(x: originX, y: originY), size: size)

       }
}

--
  -- Howard.

I'm not sure what you mean by init visibility. I feel like all these arguments could also be made for 'let'; what makes a get-only var different from the initializer's perspective?

-Joe

···

On Jan 6, 2016, at 3:39 PM, Matthew Johnson <matthew@anandabits.com> wrote:

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

I find it surprising that you key the initialization of 'var's based on whether their setter is visible or not. Initialization is not the same as setting, and memberwise initializers within the definition have private access to the member anyway; this is why `let` properties can be initialized, after all. `private(set)` is also a way for an API to communicate that a property is read-only without promising that it is or will always be immutable, so I think it's important that a 'private(set) var' be as capable as a 'let' to the maximum degree possible.

-Joe

Hi Joe,

Thanks for bringing this topic up and moving the discussion to the list (it’s hard to get into details on Twitter).

I am definitely sympathetic to the points you raise. The problem is that there is no good solution to this without involving at least one of the future enhancements. Chris feels strongly that we need to focus on the core functionality for the initial proposal so we must choose a solution without them.

Using the `var` setter visibility is the least bad option in my mind. There are many times when a var might represent internal state that a user is allowed to read, but should never be allowed to specify, whether via initialization or otherwise. These will have `private(set)` visibility.

If we allow memberwise initialization to expose them it will be a useless feature for types that contain a var like this. There will not be any way to specify that they should not participate in memberwise initialization without at least one of the future enhancements. On the other hand, if you do wish to expose them via the initializer it is easy to add a parameter and initialize the `var` manually.

This is also a safer solution. The author of the type has specifically stated that users should not be setting the value of the `var`. Maybe that doesn’t apply to initialization, but it is not the right decision to assume that IMO, at least without the ability to specify `init` visibility independently if desired.

In the case of `let`, if we do not use the only access control modifier they are allowed to have they would not be able to participate in memberwise initialization at all.

I think the proposal makes the least bad choice we can make without expanding it to include the ability to specify init visibility (which I would support if the core team was willing to do that).

Here is an expanded proposal for the syntax for a Scala style memberwise
syntax and equivalent code, specification is via an example rather than
formal syntax since this is easier to follow. Note it is like Scala’s
syntax but it is ‘Swiftified” (in particular still retains `init` keyword).

    class Ex public init(
        superParam: sPType = sPInitial,
        private label privateParam: pPType = pPInitial,
        calculatedParam: cPType = cPInitial
    ): SuperType(superParam) {
        calculatedParam: cPType {
            get {…}
            set {…}
        }
    }

This gets translated to:

    class Ex: SuperType(superParam) { {
        private privateParam: pPType = pPInitial,
        public init(superParam: sPType = sPInitial, label privateParam:
pPType = pPInitial, calculatedParam: cPType = cPInitial) {
            // 1. Call super
            super.init(superParam)
            // 2. Initialize generated parameters and existing parameters
            self.privateParame = privateParam
            self.calculatedParam = calculatedParam
        }
        calculatedParam: cPType {
            get {…}
            set {…}
        }
    }

Because the syntax is so short it is part of this proposal to remove both
the current default and memberwise initialisers thus simplifying the
language overall (remove two features, add one) and at the same time gain
power, which is a rare situation and therefore I would suggest optimal.

It is also more powerful than the proposed `memberwise init(..)` and/or
existing automatic `inits` in the following ways:

1. Allows `lets` to have a default value.
2. Allows other properties including computed properties to have a default
value.
3. Allows any `super.init` to be called (not restricted to `super.init()`).
4. Allows control of which properties participate in the `init` (they are
listed in the brackets and are not in a super call and are not
an existing property).
5. Allows private properties to be initialised.
6. Allows properties including private properties to have a label instead
of their actual name and hence not expose internals (also allows migration
of implementation whilst retaining external interface).
7. Allows calls to the generated `init` that don’t specify all members,
i.e. for `struct Ex init(i: Int = 0, s: String = “") {}` the following are
allowed `Ex()`, `Ex(i: 1)`, `Ex(s: “A”)`, and `Ex(i: 2, s: “B”)`.
8. Allows visibility of automatically generated `init` to be controlled.
9. Supports property behaviours.
10. Does not require a new keyword.

The downsides of the proposal relative to `memberwise init(..)` and/or
existing automatic `inits` are:

1. That people would need to be careful when laying out their code
otherwise the first line could become long (a bit of pretty printing solves
this).
2. Existing structs/classes that have automatically generated inits would
need to be refactored, e.g. `CGRect` would become `struct CGRect init(var
origin: CGPoint, var size: CGSize) {}` (a migration tool would help here).

Other than the downsides listed above the proposal does everything the
current proposal and current implementation does and more (more also listed
above) and is simpler to both explain and implement.

The above more than addresses the reasons given in the current proposal for
not using the Scala syntax and demonstrates superiority in many areas.
However if it were the current proposal or nothing I would go with the
current proposal since something is better than nothing.

···

On Wednesday, 6 January 2016, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 5, 2016, at 12:12 PM, Tino Heth <2th@gmx.de > <javascript:_e(%7B%7D,'cvml','2th@gmx.de');>> wrote:

The “type parameter list” syntax is sugar that could be implemented as a
layer on top of the current proposal or could be implemented orthogonally.

Hi Howard,
I've heard this argument before, so I'll repeat my answer as well:
Both offers don't make sense, it's either one way or the other (or
something completely different).

I don’t think it’s clear whether both make sense or not. They are not
mutually exclusive and are aimed at solving related, but different
problems. If we adopt the current proposal, the Kotlin / Scala syntax
*might* make sense for some use cases. It would depend upon whether the
current proposal is considered too verbose in enough cases or not. This
may or may not turn out to be the case.

The reason my proposal looks the way it does is because there are specific
goals it intends to achieve and problems it is intended to solve. These
goals and problems are not adequately addressed by the Kotlin / Scala
syntax.

If diversity starts here, why not have "const" and "val" beside "let", or
allow "fn" and "lambda"?

@Matthew: Please, if you support something, then say so - using clear
words, not phrases like "could be implemented”.

This phrasing is plenty clear IMO. I am stating a possibility. I do not
have a position on whether or not it is a good idea at the moment.

I have made some modifications to the proposal over the last few days.
These changes have been partly motivated by considering the conversation we
have had. You may wish to give it another look. If so, please look here:
https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/0018-flexible-memberwise-initialization.md\.
There is currently an open PR for the latest change so it is not in the
main Swift evolution repo yet.

The most recent change includes a discussion of why it does not use the
Scala / Kotlin syntax. You may not like the choice but I hope you can at
least understand the rationale (even if you disagree with it).

--
  -- Howard.

The proposal already states that a memberwise initializer only includes parameters for properties that are at least as visible as the initializer itself. So if you can see the `s` and `i` parameters, you can also see the `s` and `i` properties. It's not going to expose anything that isn't already visible.

This isn’t about access modifiers, it’s about the name chosen for internal variables vs. names chosen for API contracts.

But if you have a bad name in your memberwise initializer, that bad name is *by definition* already part of your API contract as a property name.

Stated another way: Either the name is already externally visible as a property, and thus is not by any sensible definition an "internal" name, or the memberwise initializer will not publish it.

···

--
Brent Royal-Gordon
Architechies

Also, I don’t think it generates good API signatures. Take this example:

struct S {
  let s: String
  let i: Int

  // user declares:
  memberwise init() {}
  // compiler synthesizes:
  init(s: String, i: Int) {
    self.s = s
    self.i = i
  }
}

That is not a very descriptive API.

Well, yeah. This is a toy example. Do you often write APIs with properties like `s` and `i`? Or, for that matter, structs named `S`?

I often write APIs where the internal member’s name is not what I want to use as the label for the public API.

This proposal doesn’t take any control away from you in this scenario. As written it offers a tool (`@nomemberwise`) to block memberwise initialization for those members while allowing you to use it for members where the names do match.

It is intended to help with in the cases many of us have where it the API is most clear when the initialization parameter uses the same name as the member it is initializing. IMO this is the most clear contract you can offer callers almost all of the time when you are simply directly initializing the member. Do you have concrete examples where you think it is more clear to do something different?

···

On Dec 22, 2015, at 12:59 AM, David Owens II <david@owensd.io> wrote:

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

It’s also not necessarily the case that your internal names are what you want exposed.

The proposal already states that a memberwise initializer only includes parameters for properties that are at least as visible as the initializer itself. So if you can see the `s` and `i` parameters, you can also see the `s` and `i` properties. It's not going to expose anything that isn't already visible.

This isn’t about access modifiers, it’s about the name chosen for internal variables vs. names chosen for API contracts.

-David

Adding a new member to a struct is itself a breaking change. I don’t see how memberwise initialization makes this worse.

It may have been a bad example as you handle the case where they wouldn’t be a breaking change by promoting the default initialization to the memberwise init().

Also, keep in mind that nothing in this proposal is *required*. You can always decline to use the feature.

My point is the same with what’s going on in the “final as default” thread. Features that create public contracts that are hard to guarantee over the lifetime of a project, especially when those contracts weren’t being made explicitly, are hard to maintain over the life of the product.

I think it’s important to address what the fragility of the API is for versioning and how that’s being addressed, but that’s just my opinion.

Do you have any suggestions on how to improve the proposal without giving up on its underlying goal of solving the M x N complexity problem (M members x N initializers)?

No. In my opinion, this is the fragile part of the solution though. I get the desire to reduce that part of the boiler plate, but I don’t know how to solve that part of the problem without a bunch of annotations that end up being more work than just implementing the init() yourself.

-David

···

On Dec 22, 2015, at 9:43 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Adding a new member to a struct is itself a breaking change. I don’t see how memberwise initialization makes this worse.

It may have been a bad example as you handle the case where they wouldn’t be a breaking change by promoting the default initialization to the memberwise init().

There are already a number of features in Swift which make it “too easy” to accidentally break API/ABI of a framework. We plan to address this (in time) with an API diff’er tool which you can use to identify these problems for you. We would like to integrate this into the package manager as well, so it helps “enforce” proper semantic versioning.

In your previous email you wrote:

Example:

v1.0 of the API ships:

struct Person {
    let name: String
}

v1.1 of the API ships:

struct Person {
    let name: String
    let age: Int
}

This is a perfect example of this problem. I agree with you completely that it is “too easy” to make this mistake and not know that you’re breaking clients of your API. The only way to handle this in full generality is with a diff’er tool. Such a tool would tell you that the right solution to this is:

struct Person {
    let name: String
    let age: Int = 0 // or some other default value.
}

or to remove memberwise, and write out the initializer long hand.

A lot of folks on the Swift team care - a lot - about long term API evolution and features to make this possible. That’s the whole point of the resilience feature (aka “nonfragile everything” :) that we’re working on this year. However, framework development is somewhat specialized, and memberwise initializers make common issues in app development much simpler.

If we were overly concerned about memberwise initializers being an API evolution problem, then there is a simple solution: do not allow a *public* memberwise initializer. We effectively already have that approach today in Swift 2.

-Chris

···

On Dec 22, 2015, at 9:59 AM, David Owens II <david@owensd.io> wrote:

On Dec 22, 2015, at 9:43 AM, Matthew Johnson <matthew@anandabits.com> wrote:

This would be awesome to have, especially integrated in. The way we handle this internally is less than ideal, so have a standard way would be great.

-David

···

On Dec 22, 2015, at 10:15 AM, Chris Lattner <clattner@apple.com> wrote:

We would like to integrate this into the package manager as well, so it helps “enforce” proper semantic versioning.