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

Reply via email to