[swift-evolution] [Proposal Draft] Flexible memberwise initialization

Howard Lovatt howard.lovatt at gmail.com
Mon Jan 4 15:25:47 CST 2016


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 at 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 <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 at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160105/3fcd785c/attachment.html>


More information about the swift-evolution mailing list