[Proposal Draft] Flexible memberwise initialization


(Matthew Johnson) #1

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

Flexible Memberwise Initialization

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-flexible-memberwise-initializers.md>
Author(s): Matthew Johnson <https://github.com/anandabits>
Status: Review
Review manager: TBD
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#introduction>Introduction

The Swift compiler is currently able to generate a memberwise initializer for us in some circumstances however there are currently many limitations to this. This proposal build on the idea of compiler generated memberwise initialization making it available to any initializer that opts in.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#motivation>Motivation

When designing initializers for a type we are currently faced with the unfortunate fact that the more flexibility we wish to offer users the more boilerplate we are required to write and maintain. We usually end up with more boilerplate and less flexibility than desired. There have been various strategies employed to mitigate this problem.

Sometimes properties that should be immutable are made mutable and a potentially unsafe ad-hoc two-phase initialization pattern is employed where an instance is initialized and then configured immediately afterwards. When properties that need to be mutable have a sensible default value they are simply default-initialized and the same post-initialization configuration strategy is employed when the default value is not correct for the intended use. This results in an instance which may pass through several incorrect states before it is correctly initialized for its intended use.

Flexible and concise initialization for both type authors and consumers will encourages using immutability where possible and removes the need for boilerplate from the concerns one must consider when designing the intializers for a type.

Quoting Chris Lattner <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151130/000518.html>:

The default memberwise initializer behavior of Swift has at least these deficiencies (IMO):
1) Defining a custom init in a struct disables the memberwise initializer, and there is no easy way to get it back.
2) Access control + the memberwise init often requires you to implement it yourself.
3) We don’t get memberwise inits for classes.
4) var properties with default initializers should have their parameter to the synthesized initializer defaulted.
5) lazy properties with memberwise initializers have problems (the memberwise init eagerly touches it).
Add to the list “all or nothing”. The compiler generates the entire initializer and does not help to eliminate boilerplate for any other initializers where it may be desirable to use memberwise intialization for a subset of members and initialize others manually.

It is common to have a type with a number of public members that are intended to be configured by clients, but also with some private state comprising implementation details of the type. This is especially prevalent in UI code which may expose many properties for configuring visual appearance, etc. Flexibile memberwise initialization can provide great benefit in these use cases, but it immediately becomes useless if it is "all or nothing".

We need a flexible solution that can synthesize memberwise initialization for some members while allowing the type auther full control over initialization of implementation details.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#proposed-solution>Proposed solution

I propose adding a memberwise declaration modifier for initializers which allows them to opt-in to synthesis of memberwise initialization and a @nomemberwise property attribute allowing them to opt-out of such synthesis.

This section of the document contains several examples of the solution in action. Specific details on how synthesis is performed are contained in the detailed design.

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

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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#properties-with-initial-values>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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#partial-memberwise-initialization>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()
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#access-control>access control

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

    // user declares:
    memberwise init() {
        // compiler error, i memberwise initialization cannot be synthesized
        // for i because it is less visible than the initializer itself
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#lazy-properties-and-incompatible-behaviors>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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#nomemberwise-properties>@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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#uninitialized-nomemberwise-properties>uninitialized @nomemberwise properties

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

    // user declares:
    memberwise init() {
        // compiler error, i is not initialized
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#delegating-and-convenience-initializers>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)
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#subclass-initializers>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)
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#detailed-design>Detailed design

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

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

Initializers will be able to opt-in to synthesized memberwise initialization with the memberwise declaration modifier. This modifier will cause the compiler to follow the procedure outlined later in the design to synthesize memberwise parameters as well as memberwise initialization code at the beginning of the initializer body.

Properties will be able to opt-out of memberwise initialization with the @nomemberwise attribute. When they do so they will not be eligible for memberwise initialization synthesis. Because of this they must be initialized directly with an initial value or initialized directly by every initializer for the type.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#overview>Overview

Throughout this design the term memberwise initialization parameter is used to refer to initializer parameters synthesized by the compiler as part of memberwise initialization synthesis.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#algorithm>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.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#terminology>Terminology

direct memberwise initialization parameters are parameters which are synthesized by the compiler and initialized directly in the body of the initializer.

forwarded memberwise initialization parameters are parameters which are synthesized by the compiler and provided to another initializer that is called in the body of the initializer.

synthesized memberwise initialization parameters or simply memberwise initialization parameters is the full set of parameters synthesized by the compiler which includes both direct and forwarded memberwise initialization parameters.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#designated-initializers-and-non-delegating-struct-initializers>Designated initializers and non-delegating struct initializers

Determine the set of properties elibile for memberwise initialization synthesis. This set is known as the set of direct memberwise initialization parameters. In order to be eligible for memberwise initialization synthesis a property must be at least as visible as the initializer itself, must not have the @nomemberwise attribute, and must not have a behavior that does not allow memberwise initialization. Currently lazy is an example of such a behavior that should prohibit memberwise initialization. If both this proposal and the Property Behaviors proposal are accepted we will need a way for behaviors to specify whether they are compatible with memberwise initialization or not.

If any of the properties in that set produced in step one are directly initialized in the body of the initializer or have a name identical to an external parameter name for the intitializer remove them from the set. If the initializer contains a parameter with an external label matching the name of a property that is eligible for memberwise intialization it must initialize that property directly.

When performing memberwise initialization for a subclass, inspect the call it makes to its superclass initialzier. Determine the set of synthesized memberwise initialization parameters that exist for the superclass initializer that is called. These parameters may participate in memberwise initialization parameter forwarding. The set is known as the set of forwarded memberwise initialization parameters.

If the subclass initializer provides arguments for any of the parameters identified in step three remove them from the set. Because a value is provided for them directly synthesized forwarding is not necessary.

If the superclass property corresponding to any of the remaining forwarded memberwise initialization parameters has a lower visibility than the initializer itself report a compilation error. These parameters must be supplied directly by the subclass initializer.

Divide each of the sets gathered in step one and step three into two subsets, one of properties that contain initial values and the other containing properties that do not contain initial values.

Synthesize memberwise initialization parameters at the end of the initializer parameter list, but immediately prior to a trailing function parameter if such a parameter exists. The synthesized parameters should have external labels matching the property name. Place the synthesized parameters in the following order:

forwarded memberwise initialization parameters that do not have an initial value in the same order they appear in the superclass initializer.
direct memberwise initialization parameters that do not have an initial value in the order in which their corresponding properties are declared.
forwarded memberwise initialization parameters that do have an initial value in the same order they appear in the superclass intitializer. Also synthesize a default value matching the initial value for these parameters.
direct memberwise initialization parameters that do have an initial value in the order in which their corresponding properties are declared.
Synthesize initialization of all direct memberwise initialization parameters at the beginning of the initializer body.

Synthesize the initialization of any properties which are ineligible for memberwise initialization, are not initialized elsewhere in the initializer body, and which do have an initial value provided in their declaration. This step is identical to the synthesis of initialization for properties that declare initial values that happens today, but applies to a more restricted set of properties: those which are not initialized directly and are not eligible for memberwise initialization synthesis (when the initializer contains the memberwise declaration modifier).

ASIDE: it would be desirable to suppress the synthesis of properties that declare an initial value if that property is initialized directly in the body of the initializer whether or not the initializer opts-in to memberwise initialization. This does not currently happen today, making it impossible to override an initial value for immutable properties with a different value in the body of an initializer.

Synthesize arguments to the superclass initializer for forwarded memberwise initialization parameters. The call to the superclass initializer in the memberwise initializer body must be updated to forward any forwarded memberwise initialization parameters that were synthesized by the compiler.

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

Convenience initializers for classes and delegating initializers use the same algorithm for forwarding memberwise initialization parameters as described in the previous steps. They do not include any direct memberwise initialization parameters and do not synthesize initialization of any stored properties in the body of the initializer.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#objective-c-class-import>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.

Mutable Objective-C properties can be marked with the MEMBERWISE attribute. Readonly Objective-C properties cannot be marked with the MEMBERWISE attribute. The MEMBERWISE attribute should only be used for properties that are initialized with a default value (not a value provided directly by the caller or computed in some way) in all of the class's initializers.

Objective-C initializers may also be marked with the MEMBERWISE attribute. When Swift imports an Objectiv-C initializer marked with this attribute it allows callers to provide memberwise values for the properties declared in the class that are marked with the MEMBERWISE attribute. At call sites for these initializers the compiler performs a transformation that results in the memberwise properties being set with the provided value immediately after initialization of the instance completes.

It may also be desirable to allow specific initializers to hide the memberwise parameter for specific properties if necessary. NO_MEMBERWISE(prop1, prop2)

It is important to observe that the mechanism for performing memberwise initialization of Objective-C classes (post-initialization setter calls) is implemented in a different way than native Swift memberwise initialization. As long as developers are careful in how they annotate Objective-C types this implementation difference should not result in any observable differences to callers.

The difference in implementation is necessary if we wish to use call-site memberwise initialization syntax in Swift when initializing instances of Cocoa classes. There have been several threads with ideas for better syntax for initializing members of Cocoa class instances. I believe memberwise initialization is the best way to do this as it allows full configuration of the instance in the initializer call.

Obviously supporting memberwise initialization with Cocoa classes would require Apple to add the MEMBERWISE attribute where appropriate. If this proposal is accepted with the Objective-C class import provision intact my hope is that this will happen as it has in other cases where annotations are necessary to improve Swift interoperability. If Apple does not intend to do so it may be desirable to remove the Objective-C interop portion of this proposal.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#impact-on-existing-code>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.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#alternatives-considered>Alternatives considered

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#require-stored-properties-to-opt-in-to-memberwise-initialization>Require stored properties to opt-in to memberwise initialization

This is a reasonable option and and I expect a healthy debate about which default is better. The decision to require opt-out was made for several reasons:

The memberwise initializer for structs does not currently require an annotation for properties to opt-in. Requiring an annotation for a mechanism designed to supercede that mechanism may be viewed as boilerplate.
Stored properties with public visibility are often intialized directly with a value provided by the caller.
Stored properties with less visibility than a memberwise initializer are not eligible for memberwise initialization. No annotation is required to indicate that.
I do think a strong argument can be made that it may be safer and more clear to require an @memberwise attribute on stored properties in order to opt-in to memberwise initialization. I am very interested in community input on this.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#allow-all-initializers-to-participate-in-memberwise-initialization>Allow all initializers to participate in memberwise initialization

This option was not seriously considered. It would impact existing code and it would provide no indication in the declaration of the initializer that the compiler will synthesize additional parameters and perform additional initialization of stored properties in the body of the initializer.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#require-initializers-to-opt-out-of-memberwise-initialization>Require initializers to opt-out of memberwise initialization

This option was also not seriously considered. It has the same problems as allowing all initializers to participate in memberwise initialization.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#require-initializers-to-explicitly-specify-memberwise-initialization-parameters>Require initializers to explicitly specify memberwise initialization parameters

The thread "helpers for initializing properties of the same name as parameters <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151130/000428.html>" discussed an idea for synthesizing property initialization in the body of the initializer while requiring the parameters to be declard explicitly.

struct Foo {
    let bar: String
    let bas: Int
    let baz: Double
    init(self.bar: String, self.bas: Int, bax: Int) {
          // self.bar = bar synthesized by the compiler
          // self.bas = bas synthesized by the compiler
        self.baz = Double(bax)
    }
}
The downside of this approach is that the boilerplate parameter declarations grow at the rate MxN (properties x initializers). It also does not address forwarding of memberwise initialization parameters which makes it useless for convenience and delegating initializers.

Proponents of this approach believe it provides additional clarity and control over the current proposal.

Under the current proposal full control is still available. It requires initializers to opt-in to memberwise initialization. When full control is necessary an initializer will simply not opt-in to memberwise initialization synthesis. The boilerplate saved in the examples on the list is relatively minimal and is tolerable in situations where full control of initialization is required.

I believe the memberwise declaration modifier on the initializer makes it clear that the compiler will synthesize additional parameters. Furthermore, IDEs and generated documentation will contain the full, synthesized signature of the initializer.


Revisit synthesized init(from decoder:) for structs with default property values
Initializer sugar
(Chris Lattner) #2

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

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>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>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>@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>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>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>Detailed design

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#syntax-changes>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>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>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

···

On Dec 21, 2015, at 11:32 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:


(Tino) #3

I already encountered a downside of the memberwise initializer - but that's already addressed (at least partly) as point 4) in Chris' list:
Let's say I have a struct with 10 members, and all of them have a default; the current initializer doesn't use the defaults, so you have the burden of filling all parameters on your own, even if you only want to customize a single parameter.
It would be nice to see that resolved, but imho the proposal looks quite long and complicated (but it's late here in Europe :wink:

Tino


(David Owens II) #4

I really like the idea and it’s something that’s extremely annoying to deal with today in Swift. My biggest concern is that the proposal, in its entirety, seems to really complicate the initialization rules.

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. It’s also not necessarily the case that your internal names are what you want exposed.

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
}

This provides you all of the freedom that you may need:
Order of the APIs is explicitly controlled
API names of the members are not exposed if not desired, especially helpful for non-public members
Default values are handled naturally

Optionally, you could keep the default simply with this:

memberwise init {}

This would use the property names as the argument labels.

Another example:

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

And the usage is much nicer:

let s = S(name: "Happy")

Here’s a subclassing example:

class Animal {
    let type: String
    let numberOfLegs: Int
    
    memberwise init(type: String, numberOfLegs: Int) {
        /* generated */ self.type = type
        /* generated */ self.numberOfLegs = numberOfLegs
    }
}

class Dog: Animal {
    let name: String
    
    memberwise private init(type: String = "dog", numberOfLegs: Int = 4, n name: String) {
        /* generated */ self.name = name
        /* generated */ super.init(type: type, numberOfLegs: numberOfLegs);
    }
    
    memberwise convenience init(name: String) {
        /* generated */ self.init(type: "dog", numberOfLegs: 4, n: name)
    }
}

let d = Dog(name: "Fido")

The biggest thing about convenience inits is that the generation of the init needs to pull the defaults from the designated init. Also, for super.init(), it’s a straight label/name lookup for the designated init() only. Anything more fancy must be written by the user.

You also disallow private init, but that doesn’t need to happen with explicit members. That’s the beauty of giving the public API a real contract with names.

struct S {
    let s: String
    private let i: Int
    
    memberwise init(name s: String, value i: Int = 12) {
        /* generated */ self.s = s
        /* generated */ self.i = i
    }
}

Also, partial initialization could be possible, much like you proposed originally:

struct S {
    let s: String
    private let i: Int
    
    memberwise init(name s: String) {
        /* generated */ self.s = s
        self.i = s.utf8.count
    }
}

My concern is that this starts to be more complicated and magical. I’d actually cut this from the proposal.

The last change I would make is that if any property has an assignment in the declaration, it cannot be memberwise initialized.

struct S {
    let s: String
    let i: Int = 10
    
    memberwise init(name s: String) {
        /* generated */ self.s = s
    }
}

In the above, i will always be 10.

-David

···

On Dec 21, 2015, at 11:32 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

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


(Tino) #5

This is some sort of a cross-post from another thread ["automatic protocol forwarding"] — for anyone who wants to follow, I recommend to read https://kotlinlang.org/docs/reference/classes.html
The idea of "function-like class declaration" has been introduced here by Joe Groff, but apparently, its benefits have been underestimated.

If you feel Kotlin’s approach is better please respond to the memberwise initialization thread with some examples written in both Kotlin and in Swift using the memberwise initialization feature I am proposing to demonstrate how and why you think it is better.

Here is Kotlin:
class Person constructor(firstName: String, lastName: String) {
}

That is a bad start — do you want to make Kotlin look worse than it is? You can just write "class Person(firstName: String, lastName: String)", the other syntax merely exists to allow designated constructors with special access restrictions.

Here is Swift under my proposal:
class Person {
    var firstName: String
    var lastName: String
    // compiler synthesizes memberwise init
}

However, my proposal gives you a lot of additional flexibility:

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

1. It interacts well with access control

Better than Kotlin? Please prove this.

2. Partial memberwise initialization is possible

The same with Kotlin — and imho at least as easy

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)

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

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)

And probably more. My approach was to design a solution that fits into the current Swift language and is orthogonal to other language features as much as possible.

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.

I neither state your proposal is bad, nor that we should simply copy an existing solution, but even as a big fan of Swift, I have to admit that Kotlin (which I probably will never use in real projects) clearly performs better in this area:
It's more concise, it doesn't need "required" nor "convenience", it offers an intuitive syntax for forwarding and it is easy to grasp.

I know that I'm not just challenging your own, personal ideas here, and have no illusions about the outcome of this bold move — but it is my honest opinion this proposal might (in total) be better than the status quo, but is inferior to Kotlin in every aspect (whereas the current scheme has the edge of familiarity on its side).

Best regards,
Tino


(Howard Lovatt) #6

An alternative would be to take a leaf out of Scala's book and use brackets following the type name. e.g:

   struct Rect(var origin: Point = Point(), var size: Size = Size()) {
       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)
       }
   }

Would be translated by the compiler into the equivalent of:

   struct Rect {
       var origin: Point // Beginning of declaration to end of type name
       var size: Size
       init(origin: Point = Point() /* Beginning of parameter name or label (if present) to end of declaration */, size: Size = Size()) {
           self.origin = origin
           self.size = size
       }
       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)
       }
   }

Advantages:

1. Allows argument labels
2. No new keyword
3. Shorter; so much so that both default initialisers and automatic member-wise initialisers for structs could be eliminated, which would nicely unify classes and structs and be clearer

Disadvantages:

1. Does not use init which is the norm in Swift
2. Can require some pretty printing because first line can be long, e.g.:

   struct Rect(
       var origin: Point = Point(),
       var size: Size = Size()
   ) {
       ...
   }

Further points:

1. Generic arguments go before the brackets, i.e. `struct Name<Generics>(Properties) {...}`.
2. Only applicable to stored properties.
3. Modifies `lazy`, `didSet`, and `willSet` would not currently be allowed but when Property Behavious (Swift 3) is accepted, then they would be allowed using the new Properties Behaviour syntax. (This sidesteps the problem of how to do didSet, and willSet, but note lazy could be done now but doesn't seem worthwhile.)
4. In addition to the syntax allowed in the brackets for an `init` the proposed new property declarations following the type name could also contain access level modifiers, `public`, `internal`, and `private` that are placed before `let` (optional, see below) or `var`.
5. `let` is optional and is the default for `init` argument lists and therefore the following is allowed `Rect(private origin: Point = Point(), puiblic var size: Size = Size()) {...}`, note `origin` is a `private let` and `size` a `public var`. If `var` is removed from `init` argument list declarations (Swift 3) it would be allowed in this context still.
6. The `init` written by the compiler cannot throw and is not failable.
7. The proposed new syntax is the same for and is equally valid for classes and structs and for classes the compiler written `init` is a designated initializer.
8. If the type implements a protocol then the compiler written `init` can satisfy that protocol, e.g. if a propocol `RectP` required `init(origin: Point, size: Size)` then `struct Rect(var origin: Point = Point(), var size: Size = Size()): RectP {...}` would be valid.

···

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

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

Flexible Memberwise Initialization

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-flexible-memberwise-initializers.md>
Author(s): Matthew Johnson <https://github.com/anandabits>
Status: Review
Review manager: TBD
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#introduction>Introduction

The Swift compiler is currently able to generate a memberwise initializer for us in some circumstances however there are currently many limitations to this. This proposal build on the idea of compiler generated memberwise initialization making it available to any initializer that opts in.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#motivation>Motivation

When designing initializers for a type we are currently faced with the unfortunate fact that the more flexibility we wish to offer users the more boilerplate we are required to write and maintain. We usually end up with more boilerplate and less flexibility than desired. There have been various strategies employed to mitigate this problem.

Sometimes properties that should be immutable are made mutable and a potentially unsafe ad-hoc two-phase initialization pattern is employed where an instance is initialized and then configured immediately afterwards. When properties that need to be mutable have a sensible default value they are simply default-initialized and the same post-initialization configuration strategy is employed when the default value is not correct for the intended use. This results in an instance which may pass through several incorrect states before it is correctly initialized for its intended use.

Flexible and concise initialization for both type authors and consumers will encourages using immutability where possible and removes the need for boilerplate from the concerns one must consider when designing the intializers for a type.

Quoting Chris Lattner <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151130/000518.html>:

The default memberwise initializer behavior of Swift has at least these deficiencies (IMO):
1) Defining a custom init in a struct disables the memberwise initializer, and there is no easy way to get it back.
2) Access control + the memberwise init often requires you to implement it yourself.
3) We don’t get memberwise inits for classes.
4) var properties with default initializers should have their parameter to the synthesized initializer defaulted.
5) lazy properties with memberwise initializers have problems (the memberwise init eagerly touches it).
Add to the list “all or nothing”. The compiler generates the entire initializer and does not help to eliminate boilerplate for any other initializers where it may be desirable to use memberwise intialization for a subset of members and initialize others manually.

It is common to have a type with a number of public members that are intended to be configured by clients, but also with some private state comprising implementation details of the type. This is especially prevalent in UI code which may expose many properties for configuring visual appearance, etc. Flexibile memberwise initialization can provide great benefit in these use cases, but it immediately becomes useless if it is "all or nothing".

We need a flexible solution that can synthesize memberwise initialization for some members while allowing the type auther full control over initialization of implementation details.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#proposed-solution>Proposed solution

I propose adding a memberwise declaration modifier for initializers which allows them to opt-in to synthesis of memberwise initialization and a @nomemberwise property attribute allowing them to opt-out of such synthesis.

This section of the document contains several examples of the solution in action. Specific details on how synthesis is performed are contained in the detailed design.

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

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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#properties-with-initial-values>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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#partial-memberwise-initialization>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()
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#access-control>access control

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

    // user declares:
    memberwise init() {
        // compiler error, i memberwise initialization cannot be synthesized
        // for i because it is less visible than the initializer itself
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#lazy-properties-and-incompatible-behaviors>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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#nomemberwise-properties>@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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#uninitialized-nomemberwise-properties>uninitialized @nomemberwise properties

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

    // user declares:
    memberwise init() {
        // compiler error, i is not initialized
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#delegating-and-convenience-initializers>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)
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#subclass-initializers>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)
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#detailed-design>Detailed design

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

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

Initializers will be able to opt-in to synthesized memberwise initialization with the memberwise declaration modifier. This modifier will cause the compiler to follow the procedure outlined later in the design to synthesize memberwise parameters as well as memberwise initialization code at the beginning of the initializer body.

Properties will be able to opt-out of memberwise initialization with the @nomemberwise attribute. When they do so they will not be eligible for memberwise initialization synthesis. Because of this they must be initialized directly with an initial value or initialized directly by every initializer for the type.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#overview>Overview

Throughout this design the term memberwise initialization parameter is used to refer to initializer parameters synthesized by the compiler as part of memberwise initialization synthesis.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#algorithm>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.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#terminology>Terminology

direct memberwise initialization parameters are parameters which are synthesized by the compiler and initialized directly in the body of the initializer.

forwarded memberwise initialization parameters are parameters which are synthesized by the compiler and provided to another initializer that is called in the body of the initializer.

synthesized memberwise initialization parameters or simply memberwise initialization parameters is the full set of parameters synthesized by the compiler which includes both direct and forwarded memberwise initialization parameters.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#designated-initializers-and-non-delegating-struct-initializers>Designated initializers and non-delegating struct initializers

Determine the set of properties elibile for memberwise initialization synthesis. This set is known as the set of direct memberwise initialization parameters. In order to be eligible for memberwise initialization synthesis a property must be at least as visible as the initializer itself, must not have the @nomemberwise attribute, and must not have a behavior that does not allow memberwise initialization. Currently lazy is an example of such a behavior that should prohibit memberwise initialization. If both this proposal and the Property Behaviors proposal are accepted we will need a way for behaviors to specify whether they are compatible with memberwise initialization or not.

If any of the properties in that set produced in step one are directly initialized in the body of the initializer or have a name identical to an external parameter name for the intitializer remove them from the set. If the initializer contains a parameter with an external label matching the name of a property that is eligible for memberwise intialization it must initialize that property directly.

When performing memberwise initialization for a subclass, inspect the call it makes to its superclass initialzier. Determine the set of synthesized memberwise initialization parameters that exist for the superclass initializer that is called. These parameters may participate in memberwise initialization parameter forwarding. The set is known as the set of forwarded memberwise initialization parameters.

If the subclass initializer provides arguments for any of the parameters identified in step three remove them from the set. Because a value is provided for them directly synthesized forwarding is not necessary.

If the superclass property corresponding to any of the remaining forwarded memberwise initialization parameters has a lower visibility than the initializer itself report a compilation error. These parameters must be supplied directly by the subclass initializer.

Divide each of the sets gathered in step one and step three into two subsets, one of properties that contain initial values and the other containing properties that do not contain initial values.

Synthesize memberwise initialization parameters at the end of the initializer parameter list, but immediately prior to a trailing function parameter if such a parameter exists. The synthesized parameters should have external labels matching the property name. Place the synthesized parameters in the following order:

forwarded memberwise initialization parameters that do not have an initial value in the same order they appear in the superclass initializer.
direct memberwise initialization parameters that do not have an initial value in the order in which their corresponding properties are declared.
forwarded memberwise initialization parameters that do have an initial value in the same order they appear in the superclass intitializer. Also synthesize a default value matching the initial value for these parameters.
direct memberwise initialization parameters that do have an initial value in the order in which their corresponding properties are declared.
Synthesize initialization of all direct memberwise initialization parameters at the beginning of the initializer body.

Synthesize the initialization of any properties which are ineligible for memberwise initialization, are not initialized elsewhere in the initializer body, and which do have an initial value provided in their declaration. This step is identical to the synthesis of initialization for properties that declare initial values that happens today, but applies to a more restricted set of properties: those which are not initialized directly and are not eligible for memberwise initialization synthesis (when the initializer contains the memberwise declaration modifier).

ASIDE: it would be desirable to suppress the synthesis of properties that declare an initial value if that property is initialized directly in the body of the initializer whether or not the initializer opts-in to memberwise initialization. This does not currently happen today, making it impossible to override an initial value for immutable properties with a different value in the body of an initializer.

Synthesize arguments to the superclass initializer for forwarded memberwise initialization parameters. The call to the superclass initializer in the memberwise initializer body must be updated to forward any forwarded memberwise initialization parameters that were synthesized by the compiler.

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

Convenience initializers for classes and delegating initializers use the same algorithm for forwarding memberwise initialization parameters as described in the previous steps. They do not include any direct memberwise initialization parameters and do not synthesize initialization of any stored properties in the body of the initializer.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#objective-c-class-import>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.

Mutable Objective-C properties can be marked with the MEMBERWISE attribute. Readonly Objective-C properties cannot be marked with the MEMBERWISE attribute. The MEMBERWISE attribute should only be used for properties that are initialized with a default value (not a value provided directly by the caller or computed in some way) in all of the class's initializers.

Objective-C initializers may also be marked with the MEMBERWISE attribute. When Swift imports an Objectiv-C initializer marked with this attribute it allows callers to provide memberwise values for the properties declared in the class that are marked with the MEMBERWISE attribute. At call sites for these initializers the compiler performs a transformation that results in the memberwise properties being set with the provided value immediately after initialization of the instance completes.

It may also be desirable to allow specific initializers to hide the memberwise parameter for specific properties if necessary. NO_MEMBERWISE(prop1, prop2)

It is important to observe that the mechanism for performing memberwise initialization of Objective-C classes (post-initialization setter calls) is implemented in a different way than native Swift memberwise initialization. As long as developers are careful in how they annotate Objective-C types this implementation difference should not result in any observable differences to callers.

The difference in implementation is necessary if we wish to use call-site memberwise initialization syntax in Swift when initializing instances of Cocoa classes. There have been several threads with ideas for better syntax for initializing members of Cocoa class instances. I believe memberwise initialization is the best way to do this as it allows full configuration of the instance in the initializer call.

Obviously supporting memberwise initialization with Cocoa classes would require Apple to add the MEMBERWISE attribute where appropriate. If this proposal is accepted with the Objective-C class import provision intact my hope is that this will happen as it has in other cases where annotations are necessary to improve Swift interoperability. If Apple does not intend to do so it may be desirable to remove the Objective-C interop portion of this proposal.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#impact-on-existing-code>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.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#alternatives-considered>Alternatives considered

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#require-stored-properties-to-opt-in-to-memberwise-initialization>Require stored properties to opt-in to memberwise initialization

This is a reasonable option and and I expect a healthy debate about which default is better. The decision to require opt-out was made for several reasons:

The memberwise initializer for structs does not currently require an annotation for properties to opt-in. Requiring an annotation for a mechanism designed to supercede that mechanism may be viewed as boilerplate.
Stored properties with public visibility are often intialized directly with a value provided by the caller.
Stored properties with less visibility than a memberwise initializer are not eligible for memberwise initialization. No annotation is required to indicate that.
I do think a strong argument can be made that it may be safer and more clear to require an @memberwise attribute on stored properties in order to opt-in to memberwise initialization. I am very interested in community input on this.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#allow-all-initializers-to-participate-in-memberwise-initialization>Allow all initializers to participate in memberwise initialization

This option was not seriously considered. It would impact existing code and it would provide no indication in the declaration of the initializer that the compiler will synthesize additional parameters and perform additional initialization of stored properties in the body of the initializer.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#require-initializers-to-opt-out-of-memberwise-initialization>Require initializers to opt-out of memberwise initialization

This option was also not seriously considered. It has the same problems as allowing all initializers to participate in memberwise initialization.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#require-initializers-to-explicitly-specify-memberwise-initialization-parameters>Require initializers to explicitly specify memberwise initialization parameters

The thread "helpers for initializing properties of the same name as parameters <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151130/000428.html>" discussed an idea for synthesizing property initialization in the body of the initializer while requiring the parameters to be declard explicitly.

struct Foo {
    let bar: String
    let bas: Int
    let baz: Double
    init(self.bar: String, self.bas: Int, bax: Int) {
          // self.bar = bar synthesized by the compiler
          // self.bas = bas synthesized by the compiler
        self.baz = Double(bax)
    }
}
The downside of this approach is that the boilerplate parameter declarations grow at the rate MxN (properties x initializers). It also does not address forwarding of memberwise initialization parameters which makes it useless for convenience and delegating initializers.

Proponents of this approach believe it provides additional clarity and control over the current proposal.

Under the current proposal full control is still available. It requires initializers to opt-in to memberwise initialization. When full control is necessary an initializer will simply not opt-in to memberwise initialization synthesis. The boilerplate saved in the examples on the list is relatively minimal and is tolerable in situations where full control of initialization is required.

I believe the memberwise declaration modifier on the initializer makes it clear that the compiler will synthesize additional parameters. Furthermore, IDEs and generated documentation will contain the full, synthesized signature of the initializer.

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


(Joe Groff) #7

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


(Matthew Johnson) #8

I already encountered a downside of the memberwise initializer - but that's already addressed (at least partly) as point 4) in Chris' list:
Let's say I have a struct with 10 members, and all of them have a default; the current initializer doesn't use the defaults, so you have the burden of filling all parameters on your own, even if you only want to customize a single parameter.
It would be nice to see that resolved, but imho the proposal looks quite long and complicated (but it's late here in Europe :wink:

Tino

The design of the feature is intended to be simple and intuitive so that it just "does the right thing". Its *use* is not intended to be complicated at all.

The case you mention of propagating initial values to default parameter values is one of the many use cases it covers.

I agree that the proposal is pretty long. The proposed feature interacts with many other language features including access control, inheritance, property behaviors, etc. All of the interactions need to be addressed.

I would be happy to discuss how the feature as proposed would behave in any specific use cases if you have questions about them.

···

Sent from my iPhone

On Dec 21, 2015, at 2:21 PM, Tino Heth <2th@gmx.de> wrote:


(Dave Abrahams) #9

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

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>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>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>@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>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>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>Detailed design

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#syntax-changes>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>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>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
https://lists.swift.org/mailman/listinfo/swift-evolution

-Dave

···

On Dec 21, 2015, at 5:47 PM, Chris Lattner via swift-evolution <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:


(Brent Royal-Gordon) #10

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


(Brent Royal-Gordon) #11

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


(Matthew Johnson) #12

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:

Hi Chris, thanks! I’m really excited to hear that you like it!

Replies to your comments are inline. I hope you’re willing to entertain on some discussion on some aspects of the proposal that you are not immediately sold on. :slight_smile: I am hoping we can find a way to address the goals they represent even if the eventual solution looks different than the current one.

The examples are definitely not a complete representation of what is described in the detailed design. I assume they provided sufficient context for your initial feedback.

Flexible Memberwise Initialization

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#replacing-the-current-memberwise-initializer>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.

Yep, the basis of the idea is that the initializer itself handles any non-trivial work and allows the compiler to generate the trivial boilerplate. The “flexibile” aspect of the idea is that the type / initializer author has enough control to tell the compiler what is trivial and what is not in a relatively concise manner.

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

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.

IMO it makes more sense that the “initial value” is a default and is only used when necessary - i.e. when the initializer body does not initialize the property directly. This does not contradict immutability of the property for instances of the type as it is still initialized and never modified afterwards. If we don’t allow initializers to provide a different value than the “initial value” default and we have a type where that is necessary for some initializers we are forced to omit the “initial value” and duplicate it in all of the initializers that actually need it (either as a parameter default value or as part of a property initialization statement in the body of the initializer). This seems inflexible and results in duplicated constants in our code which is never a good thing.

The proposal as written includes as part of the synthesis algorithm a modification to the current behavior so that initial value synthesis only happens if a member is not assigned in the post-synthesis initializer body. It also includes an aside which recommends making this change to the initialization rules applicable to all initializers, not just memberwise initializers. I consider this to be an improvement that falls in the general category of “flexible initialization”, memberwise or not.

With the modified initialization rules covered in the proposal the synthesized code in the example would be legal.

If you’re still uncomfortable with this behavior and with a change to the init rules allowing initializers to initialize let properties with a value different than the “initial value” specified in the declaration can you elaborate on the rationale?

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 the complexity of the model stems from the fact that initialization itself can be rather complex. It is essential rather than incidental complexity and it must be dealt with somehow - either through boilerplate, through compiler synthesis, through restrictions on desired flexibility, or through punting and requiring users to overwrite default values with assignment after initialization completes. Obviously this proposal goes pretty far down the path of synthesis.

It is very reasonable to have a discussion about what the right tradeoffs are here. My hope is that we can achieve flexibility without boilerplate and without causing confusion whenever that is possible.

I’m definitely interested in hearing more thoughts on this. What do you think the downsides are of synthesizing memberwise initialization for properties with an “initial value”?

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#partial-memberwise-initialization>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.

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.

One alternative to this I think I forgot to include in the alternatives section is to allow the `memberwise` declaration modifier on the initializer to be annotated in some way that prevents memberwise initialization synthesis of some parameters that would otherwise receive it. What do you think of this approach? It certainly runs in the direction of Swift’s emphasis on clarity.

If you like this approach better do you have any ideas on what the syntax might look like? I think it would be best to provide a list of properties omitted from memberwise init synthesis rather than an opt-in list as that would likely be the shorter list. I don’t have any great ideas for syntax though.

IMO an important aspect of the “flexible” part of this proposal is that it allows specific initializers to "take control” of initialization of specific properties that may be memberwise intialized by most initializers, while still receiving memberwise initialization of other properties.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#lazy-properties-and-incompatible-behaviors>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.

I don’t think the proposal changes lazy properties. If it does that was not the intent so please point out how and where and I will make a change.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#nomemberwise-properties>@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.

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.

Consider again the example of a Swift implementation of UI widget which would reasonably want to allow memberwise initialization of its appearance related properties but would also has a bunch of properties where memberwise initialization is not the right thing to do. Authors of the type need a way to prohibit memberwise initialization from happening. Lower access control would be sufficient to do this sometimes, but not always.

If we include syntax allowing specific initializers to block memberwise initialization of specific properties (as mentioned previously) we could get away without @nomemberwise. However, that would cause many memberwise initializers that could otherwise avoid an opt-out list to include one. It would also be less clear because the real intention of the author is that the property should never participate in memberwise initialization at all.

I hope this helps to explain why I consider it pretty important. I hope we can at least have a discussion about keeping it in the main body of the proposal.

If you are strongly opposed to this I will of course move it.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#delegating-and-convenience-initializers>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.

The … placeholder is an interesting idea. I really like it. The algorithm in the current proposal always places the memberwise parameters at the end of the parameter list, but just prior to a trailing closure argument if one exists. This would allow additional flexibility of placement.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#subclass-initializers>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.

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.

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

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#syntax-changes>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.

You’ve said this a couple of times so I assume you have a pretty good reason for it. I’m not totally clear on the reason though, it simplifies the model. The current memberwise initializer supports var members with an initial value. Why do you think we should move away from supporting this? Can you elaborate further?

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

I’m not sure how a property that isn’t stored would be involved in initialization, memberwise or not. Am I missing something here?

Typo "initialzier”.

Thanks!

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#objective-c-class-import>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.

This makes sense. I will do this.

The reason I included this section is that I think most developers would really like some syntactic sugar for configuring instances of Cocoa classes. Enabling them to work with memberwise initialization seems like a pretty natural way to do this. But you’re right, this could certainly be a future follow-on proposal once we hash out the details on the Swift side.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#impact-on-existing-code>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.

Yeah, I don’t have a strong opinion on this. I just wanted to point out that making it explicit wouldn’t be too burdensome and it would eliminate any “hidden” initializers.

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

-Chris

This is really great to hear! I’m looking forward to continued discussion and refinement.

Matthew

···

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


(Matthew Johnson) #13

I really like the idea and it’s something that’s extremely annoying to deal with today in Swift. My biggest concern is that the proposal, in its entirety, seems to really complicate the initialization rules.

Hi David. I’m glad that you like the basic idea. Thanks for taking the time to respond and offer feedback.

The proposal as written was only intended to be a place to start a conversation. I expected it to be revised and am already in the process of doing that. Chris had some great feedback that I am taking into account. I hope both of you see the changes as improvements to the current draft.

I looked at the approach you are suggesting and discussed something similar to it in the alternatives section. The problem with this approach is that it doesn’t address the fundamental problem I am trying to solve - namely that trivial code grows with MxN complexity (M memberwise initializable properties and N initializers).

Your approach reduces the trivial code by about 50% but it does not address the scaling problem. Scaling isn’t really significant in small struct examples but it would be quite non-trivial in something like a Swift UIKit.

You mentioned how your approach addresses private properties. I did consider allowing parameters to be synthesized for lower access level members. The property wouldn’t be visible, but a parameter could still be synthesized to initialize the member. I decided against this approach because I believe it is probably relatively uncommon to want to initialize a member not visible to the caller with a value provided directly by the caller. I will update the alternatives considered section to address this.

I would also like to point out:
1) Nothing in my proposal prevents you from taking full control of the signature for your initializer. You just need to write the assignments manually.
2) What you are proposing and what I am proposing are not mutually exclusive. It would be possible for them to exist side-by-side in the language.

Matthew

···

On Dec 22, 2015, at 12:08 AM, David Owens II <david@owensd.io> 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. It’s also not necessarily the case that your internal names are what you want exposed.

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
}

This provides you all of the freedom that you may need:
Order of the APIs is explicitly controlled
API names of the members are not exposed if not desired, especially helpful for non-public members
Default values are handled naturally

Optionally, you could keep the default simply with this:

memberwise init {}

This would use the property names as the argument labels.

Another example:

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

And the usage is much nicer:

let s = S(name: "Happy")

Here’s a subclassing example:

class Animal {
    let type: String
    let numberOfLegs: Int
    
    memberwise init(type: String, numberOfLegs: Int) {
        /* generated */ self.type = type
        /* generated */ self.numberOfLegs = numberOfLegs
    }
}

class Dog: Animal {
    let name: String
    
    memberwise private init(type: String = "dog", numberOfLegs: Int = 4, n name: String) {
        /* generated */ self.name = name
        /* generated */ super.init(type: type, numberOfLegs: numberOfLegs);
    }
    
    memberwise convenience init(name: String) {
        /* generated */ self.init(type: "dog", numberOfLegs: 4, n: name)
    }
}

let d = Dog(name: "Fido")

The biggest thing about convenience inits is that the generation of the init needs to pull the defaults from the designated init. Also, for super.init(), it’s a straight label/name lookup for the designated init() only. Anything more fancy must be written by the user.

You also disallow private init, but that doesn’t need to happen with explicit members. That’s the beauty of giving the public API a real contract with names.

struct S {
    let s: String
    private let i: Int
    
    memberwise init(name s: String, value i: Int = 12) {
        /* generated */ self.s = s
        /* generated */ self.i = i
    }
}

Also, partial initialization could be possible, much like you proposed originally:

struct S {
    let s: String
    private let i: Int
    
    memberwise init(name s: String) {
        /* generated */ self.s = s
        self.i = s.utf8.count
    }
}

My concern is that this starts to be more complicated and magical. I’d actually cut this from the proposal.

The last change I would make is that if any property has an assignment in the declaration, it cannot be memberwise initialized.

struct S {
    let s: String
    let i: Int = 10
    
    memberwise init(name s: String) {
        /* generated */ self.s = s
    }
}

In the above, i will always be 10.

-David

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

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


(Matthew Johnson) #14

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

···

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

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

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

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>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>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>@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>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>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>Detailed design

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#syntax-changes>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>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>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


(Matthew Johnson) #15

This is some sort of a cross-post from another thread ["automatic protocol forwarding"] — for anyone who wants to follow, I recommend to read https://kotlinlang.org/docs/reference/classes.html
The idea of "function-like class declaration" has been introduced here by Joe Groff, but apparently, its benefits have been underestimated.

If you feel Kotlin’s approach is better please respond to the memberwise initialization thread with some examples written in both Kotlin and in Swift using the memberwise initialization feature I am proposing to demonstrate how and why you think it is better.

Here is Kotlin:
class Person constructor(firstName: String, lastName: String) {
}

That is a bad start — do you want to make Kotlin look worse than it is? You can just write "class Person(firstName: String, lastName: String)", the other syntax merely exists to allow designated constructors with special access restrictions.

I was not trying to make Kotlin look worse. I copied from the Kotlin web page. I do see that constructor can be omitted when no modifiers are necessary.

Here is Swift under my proposal:
class Person {
    var firstName: String
    var lastName: String
    // compiler synthesizes memberwise init
}

However, my proposal gives you a lot of additional flexibility:

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:

struct Person { var firstName: String; var lastName: String
    // compiler synthesizes memberwise init
}

There are no additional lines of code when you do it this way. You need to introduce the member declarations with `let` or `var` but that is not necessarily a bad thing.

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.

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.

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

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

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.

And probably more. My approach was to design a solution that fits into the current Swift language and is orthogonal to other language features as much as possible.

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.

I neither state your proposal is bad, nor that we should simply copy an existing solution, but even as a big fan of Swift, I have to admit that Kotlin (which I probably will never use in real projects) clearly performs better in this area:
It's more concise, it doesn't need "required" nor "convenience", it offers an intuitive syntax for forwarding and it is easy to grasp.

I know that I'm not just challenging your own, personal ideas here, and have no illusions about the outcome of this bold move — but it is my honest opinion this proposal might (in total) be better than the status quo, but is inferior to Kotlin in every aspect (whereas the current scheme has the edge of familiarity on its side).

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.

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

Matthew

···

On Dec 31, 2015, at 5:44 AM, Tino Heth <2th@gmx.de> wrote:


(Tino) #16

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.

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

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 :wink: — or ask questions if you are just puzzled by it.

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.

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.

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.

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!

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

Best regards,
Tino


(Matthew Johnson) #17

An alternative would be to take a leaf out of Scala's book and use brackets following the type name.

Several people have suggested this. The idea of my proposal is to provide a scalable and flexible model for memberwise initialization.

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.

That said, in the current proposal you can get reasonably similar syntax. You just need to place the secondary initializer in an extension or explicitly declare the memberwise init. Of course you can also place properties on a single line (my preference). This is just to show how close you can get to the Scala syntax using the current proposal.

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

Or

struct Rect { var origin: Point = Point(), size: Size = Size()
  memberwise init(…) {}
       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)
       }
}

···

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

e.g:

   struct Rect(var origin: Point = Point(), var size: Size = Size()) {
       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)
       }
   }

Would be translated by the compiler into the equivalent of:

   struct Rect {
       var origin: Point // Beginning of declaration to end of type name
       var size: Size
       init(origin: Point = Point() /* Beginning of parameter name or label (if present) to end of declaration */, size: Size = Size()) {
           self.origin = origin
           self.size = size
       }
       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)
       }
   }

Advantages:

1. Allows argument labels
2. No new keyword
3. Shorter; so much so that both default initialisers and automatic member-wise initialisers for structs could be eliminated, which would nicely unify classes and structs and be clearer

Disadvantages:

1. Does not use init which is the norm in Swift
2. Can require some pretty printing because first line can be long, e.g.:

   struct Rect(
       var origin: Point = Point(),
       var size: Size = Size()
   ) {
       ...
   }

Further points:

1. Generic arguments go before the brackets, i.e. `struct Name<Generics>(Properties) {...}`.
2. Only applicable to stored properties.
3. Modifies `lazy`, `didSet`, and `willSet` would not currently be allowed but when Property Behavious (Swift 3) is accepted, then they would be allowed using the new Properties Behaviour syntax. (This sidesteps the problem of how to do didSet, and willSet, but note lazy could be done now but doesn't seem worthwhile.)
4. In addition to the syntax allowed in the brackets for an `init` the proposed new property declarations following the type name could also contain access level modifiers, `public`, `internal`, and `private` that are placed before `let` (optional, see below) or `var`.
5. `let` is optional and is the default for `init` argument lists and therefore the following is allowed `Rect(private origin: Point = Point(), puiblic var size: Size = Size()) {...}`, note `origin` is a `private let` and `size` a `public var`. If `var` is removed from `init` argument list declarations (Swift 3) it would be allowed in this context still.
6. The `init` written by the compiler cannot throw and is not failable.
7. The proposed new syntax is the same for and is equally valid for classes and structs and for classes the compiler written `init` is a designated initializer.
8. If the type implements a protocol then the compiler written `init` can satisfy that protocol, e.g. if a propocol `RectP` required `init(origin: Point, size: Size)` then `struct Rect(var origin: Point = Point(), var size: Size = Size()): RectP {...}` would be valid.

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

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

Flexible Memberwise Initialization

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-flexible-memberwise-initializers.md>
Author(s): Matthew Johnson <https://github.com/anandabits>
Status: Review
Review manager: TBD
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#introduction>Introduction

The Swift compiler is currently able to generate a memberwise initializer for us in some circumstances however there are currently many limitations to this. This proposal build on the idea of compiler generated memberwise initialization making it available to any initializer that opts in.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#motivation>Motivation

When designing initializers for a type we are currently faced with the unfortunate fact that the more flexibility we wish to offer users the more boilerplate we are required to write and maintain. We usually end up with more boilerplate and less flexibility than desired. There have been various strategies employed to mitigate this problem.

Sometimes properties that should be immutable are made mutable and a potentially unsafe ad-hoc two-phase initialization pattern is employed where an instance is initialized and then configured immediately afterwards. When properties that need to be mutable have a sensible default value they are simply default-initialized and the same post-initialization configuration strategy is employed when the default value is not correct for the intended use. This results in an instance which may pass through several incorrect states before it is correctly initialized for its intended use.

Flexible and concise initialization for both type authors and consumers will encourages using immutability where possible and removes the need for boilerplate from the concerns one must consider when designing the intializers for a type.

Quoting Chris Lattner <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151130/000518.html>:

The default memberwise initializer behavior of Swift has at least these deficiencies (IMO):
1) Defining a custom init in a struct disables the memberwise initializer, and there is no easy way to get it back.
2) Access control + the memberwise init often requires you to implement it yourself.
3) We don’t get memberwise inits for classes.
4) var properties with default initializers should have their parameter to the synthesized initializer defaulted.
5) lazy properties with memberwise initializers have problems (the memberwise init eagerly touches it).
Add to the list “all or nothing”. The compiler generates the entire initializer and does not help to eliminate boilerplate for any other initializers where it may be desirable to use memberwise intialization for a subset of members and initialize others manually.

It is common to have a type with a number of public members that are intended to be configured by clients, but also with some private state comprising implementation details of the type. This is especially prevalent in UI code which may expose many properties for configuring visual appearance, etc. Flexibile memberwise initialization can provide great benefit in these use cases, but it immediately becomes useless if it is "all or nothing".

We need a flexible solution that can synthesize memberwise initialization for some members while allowing the type auther full control over initialization of implementation details.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#proposed-solution>Proposed solution

I propose adding a memberwise declaration modifier for initializers which allows them to opt-in to synthesis of memberwise initialization and a @nomemberwise property attribute allowing them to opt-out of such synthesis.

This section of the document contains several examples of the solution in action. Specific details on how synthesis is performed are contained in the detailed design.

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

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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#properties-with-initial-values>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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#partial-memberwise-initialization>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()
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#access-control>access control

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

    // user declares:
    memberwise init() {
        // compiler error, i memberwise initialization cannot be synthesized
        // for i because it is less visible than the initializer itself
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#lazy-properties-and-incompatible-behaviors>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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#nomemberwise-properties>@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
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#uninitialized-nomemberwise-properties>uninitialized @nomemberwise properties

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

    // user declares:
    memberwise init() {
        // compiler error, i is not initialized
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#delegating-and-convenience-initializers>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)
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#subclass-initializers>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)
    }
}
<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#detailed-design>Detailed design

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

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

Initializers will be able to opt-in to synthesized memberwise initialization with the memberwise declaration modifier. This modifier will cause the compiler to follow the procedure outlined later in the design to synthesize memberwise parameters as well as memberwise initialization code at the beginning of the initializer body.

Properties will be able to opt-out of memberwise initialization with the @nomemberwise attribute. When they do so they will not be eligible for memberwise initialization synthesis. Because of this they must be initialized directly with an initial value or initialized directly by every initializer for the type.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#overview>Overview

Throughout this design the term memberwise initialization parameter is used to refer to initializer parameters synthesized by the compiler as part of memberwise initialization synthesis.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#algorithm>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.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#terminology>Terminology

direct memberwise initialization parameters are parameters which are synthesized by the compiler and initialized directly in the body of the initializer.

forwarded memberwise initialization parameters are parameters which are synthesized by the compiler and provided to another initializer that is called in the body of the initializer.

synthesized memberwise initialization parameters or simply memberwise initialization parameters is the full set of parameters synthesized by the compiler which includes both direct and forwarded memberwise initialization parameters.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#designated-initializers-and-non-delegating-struct-initializers>Designated initializers and non-delegating struct initializers

Determine the set of properties elibile for memberwise initialization synthesis. This set is known as the set of direct memberwise initialization parameters. In order to be eligible for memberwise initialization synthesis a property must be at least as visible as the initializer itself, must not have the @nomemberwise attribute, and must not have a behavior that does not allow memberwise initialization. Currently lazy is an example of such a behavior that should prohibit memberwise initialization. If both this proposal and the Property Behaviors proposal are accepted we will need a way for behaviors to specify whether they are compatible with memberwise initialization or not.

If any of the properties in that set produced in step one are directly initialized in the body of the initializer or have a name identical to an external parameter name for the intitializer remove them from the set. If the initializer contains a parameter with an external label matching the name of a property that is eligible for memberwise intialization it must initialize that property directly.

When performing memberwise initialization for a subclass, inspect the call it makes to its superclass initialzier. Determine the set of synthesized memberwise initialization parameters that exist for the superclass initializer that is called. These parameters may participate in memberwise initialization parameter forwarding. The set is known as the set of forwarded memberwise initialization parameters.

If the subclass initializer provides arguments for any of the parameters identified in step three remove them from the set. Because a value is provided for them directly synthesized forwarding is not necessary.

If the superclass property corresponding to any of the remaining forwarded memberwise initialization parameters has a lower visibility than the initializer itself report a compilation error. These parameters must be supplied directly by the subclass initializer.

Divide each of the sets gathered in step one and step three into two subsets, one of properties that contain initial values and the other containing properties that do not contain initial values.

Synthesize memberwise initialization parameters at the end of the initializer parameter list, but immediately prior to a trailing function parameter if such a parameter exists. The synthesized parameters should have external labels matching the property name. Place the synthesized parameters in the following order:

forwarded memberwise initialization parameters that do not have an initial value in the same order they appear in the superclass initializer.
direct memberwise initialization parameters that do not have an initial value in the order in which their corresponding properties are declared.
forwarded memberwise initialization parameters that do have an initial value in the same order they appear in the superclass intitializer. Also synthesize a default value matching the initial value for these parameters.
direct memberwise initialization parameters that do have an initial value in the order in which their corresponding properties are declared.
Synthesize initialization of all direct memberwise initialization parameters at the beginning of the initializer body.

Synthesize the initialization of any properties which are ineligible for memberwise initialization, are not initialized elsewhere in the initializer body, and which do have an initial value provided in their declaration. This step is identical to the synthesis of initialization for properties that declare initial values that happens today, but applies to a more restricted set of properties: those which are not initialized directly and are not eligible for memberwise initialization synthesis (when the initializer contains the memberwise declaration modifier).

ASIDE: it would be desirable to suppress the synthesis of properties that declare an initial value if that property is initialized directly in the body of the initializer whether or not the initializer opts-in to memberwise initialization. This does not currently happen today, making it impossible to override an initial value for immutable properties with a different value in the body of an initializer.

Synthesize arguments to the superclass initializer for forwarded memberwise initialization parameters. The call to the superclass initializer in the memberwise initializer body must be updated to forward any forwarded memberwise initialization parameters that were synthesized by the compiler.

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

Convenience initializers for classes and delegating initializers use the same algorithm for forwarding memberwise initialization parameters as described in the previous steps. They do not include any direct memberwise initialization parameters and do not synthesize initialization of any stored properties in the body of the initializer.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#objective-c-class-import>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.

Mutable Objective-C properties can be marked with the MEMBERWISE attribute. Readonly Objective-C properties cannot be marked with the MEMBERWISE attribute. The MEMBERWISE attribute should only be used for properties that are initialized with a default value (not a value provided directly by the caller or computed in some way) in all of the class's initializers.

Objective-C initializers may also be marked with the MEMBERWISE attribute. When Swift imports an Objectiv-C initializer marked with this attribute it allows callers to provide memberwise values for the properties declared in the class that are marked with the MEMBERWISE attribute. At call sites for these initializers the compiler performs a transformation that results in the memberwise properties being set with the provided value immediately after initialization of the instance completes.

It may also be desirable to allow specific initializers to hide the memberwise parameter for specific properties if necessary. NO_MEMBERWISE(prop1, prop2)

It is important to observe that the mechanism for performing memberwise initialization of Objective-C classes (post-initialization setter calls) is implemented in a different way than native Swift memberwise initialization. As long as developers are careful in how they annotate Objective-C types this implementation difference should not result in any observable differences to callers.

The difference in implementation is necessary if we wish to use call-site memberwise initialization syntax in Swift when initializing instances of Cocoa classes. There have been several threads with ideas for better syntax for initializing members of Cocoa class instances. I believe memberwise initialization is the best way to do this as it allows full configuration of the instance in the initializer call.

Obviously supporting memberwise initialization with Cocoa classes would require Apple to add the MEMBERWISE attribute where appropriate. If this proposal is accepted with the Objective-C class import provision intact my hope is that this will happen as it has in other cases where annotations are necessary to improve Swift interoperability. If Apple does not intend to do so it may be desirable to remove the Objective-C interop portion of this proposal.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#impact-on-existing-code>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.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#alternatives-considered>Alternatives considered

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#require-stored-properties-to-opt-in-to-memberwise-initialization>Require stored properties to opt-in to memberwise initialization

This is a reasonable option and and I expect a healthy debate about which default is better. The decision to require opt-out was made for several reasons:

The memberwise initializer for structs does not currently require an annotation for properties to opt-in. Requiring an annotation for a mechanism designed to supercede that mechanism may be viewed as boilerplate.
Stored properties with public visibility are often intialized directly with a value provided by the caller.
Stored properties with less visibility than a memberwise initializer are not eligible for memberwise initialization. No annotation is required to indicate that.
I do think a strong argument can be made that it may be safer and more clear to require an @memberwise attribute on stored properties in order to opt-in to memberwise initialization. I am very interested in community input on this.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#allow-all-initializers-to-participate-in-memberwise-initialization>Allow all initializers to participate in memberwise initialization

This option was not seriously considered. It would impact existing code and it would provide no indication in the declaration of the initializer that the compiler will synthesize additional parameters and perform additional initialization of stored properties in the body of the initializer.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#require-initializers-to-opt-out-of-memberwise-initialization>Require initializers to opt-out of memberwise initialization

This option was also not seriously considered. It has the same problems as allowing all initializers to participate in memberwise initialization.

<https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md#require-initializers-to-explicitly-specify-memberwise-initialization-parameters>Require initializers to explicitly specify memberwise initialization parameters

The thread "helpers for initializing properties of the same name as parameters <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151130/000428.html>" discussed an idea for synthesizing property initialization in the body of the initializer while requiring the parameters to be declard explicitly.

struct Foo {
    let bar: String
    let bas: Int
    let baz: Double
    init(self.bar: String, self.bas: Int, bax: Int) {
          // self.bar = bar synthesized by the compiler
          // self.bas = bas synthesized by the compiler
        self.baz = Double(bax)
    }
}
The downside of this approach is that the boilerplate parameter declarations grow at the rate MxN (properties x initializers). It also does not address forwarding of memberwise initialization parameters which makes it useless for convenience and delegating initializers.

Proponents of this approach believe it provides additional clarity and control over the current proposal.

Under the current proposal full control is still available. It requires initializers to opt-in to memberwise initialization. When full control is necessary an initializer will simply not opt-in to memberwise initialization synthesis. The boilerplate saved in the examples on the list is relatively minimal and is tolerable in situations where full control of initialization is required.

I believe the memberwise declaration modifier on the initializer makes it clear that the compiler will synthesize additional parameters. Furthermore, IDEs and generated documentation will contain the full, synthesized signature of the initializer.

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


(Howard Lovatt) #18

Yes you can get close, but:

1. Its weird that you can only do this in an extension.
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).
3. Only ‘really' works for structs, the compiler doesn’t write a member-wise initialiser for classes (just a default initialiser).
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).

···

On 5 Jan 2016, at 10:16 AM, Matthew Johnson <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)
       }
}


(Tino) #19

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

Tino


(Matthew Johnson) #20

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

Matthew

NOTE: The “access control for init” enhancement specifies that `var` init visibility will continue to use the setter visibility if no init visibility was directly specified. It would be very reasonable to change this so that the getter visibility is used if no init visibility is directly specified, thus making `private(set) var` as capable as `let`.

···

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