[swift-evolution] [Proposal Draft] partial initializers

Matthew Johnson matthew at anandabits.com
Mon Jan 11 15:37:20 CST 2016


> On Jan 11, 2016, at 12:52 PM, John McCall <rjmccall at apple.com> wrote:
> 
>> On Jan 11, 2016, at 9:34 AM, Matthew Johnson <matthew at anandabits.com <mailto:matthew at anandabits.com>> wrote:
>>> On Jan 11, 2016, at 11:21 AM, John McCall <rjmccall at apple.com <mailto:rjmccall at apple.com>> wrote:
>>> 
>>>> On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>> 
>>>> I have always considered the Flexible Memberwise Initialization proposal to be just a first step (as evidenced by the many future enhancements it discussed).  Its review has inspired new ideas and helped to shape my vision of the best long-term solution.    My final thoughts about the review can be found here: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/006176.html>
>>>> 
>>>> Partial initializers is the second in a series of three proposals describing general features that can work together to form a complete solution.
>>>> 
>>>> The proposal drafts can be found at the following links:
>>>> 
>>>> * Parameter forwarding: https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md <https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md>
>>>> * Partial initializers: https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md <https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md>
>>>> * Property lists: https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md <https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md>
>>>> 
>>>> Matthew
>>>> Partial Initializers
>>>> 
>>>> Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-partial-initializers.md>
>>>> Author(s): Matthew Johnson <https://github.com/anandabits>
>>>> Status: Awaiting review
>>>> Review manager: TBD
>>>> Introduction
>>>> 
>>>> This proposal introduces partial initializers. They perform part, but not all, of phase 1 initialization for a type. Partial initializers can only be called by designated initializers of the same type or other partial initializers of the same type.
>>>> 
>>>> Swift-evolution thread: Proposal Draft: Partial Initializers <https://lists.swift.org/pipermail/swift-evolution>
>>>> Motivation
>>>> 
>>>> Partial initializers will make it much easier to factor out common initialization logic than it is today.
>>>> 
>>>> Memberwise initialization
>>>> 
>>>> Partial initializers are a general feature that can work together with Parameter Forwarding <https://github.com/anandabits/swift-evolution/edit/parameter-forwarding/proposals/NNNN-parameter-forwarding.md> and Property Lists <https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md> to enable extremely flexible memberwise initialization. 
>>>> 
>>>> The combination of partial initializers and parameter forwarding is sufficiently powerfule to replace the explicit memberwise initializers of the Flexible Memberwise Initialization <https://github.com/apple/swift-evolution/blob/master/proposals/0018-flexible-memberwise-initialization.md> proposal by simply adding a three implicit partial initializers.
>>>> 
>>>> Extensions with stored properties
>>>> 
>>>> Partial initialization is an enabling feature for stored properties in class extensions. Extension with stored properties would be required to have a designated initializer. That extension initializer would effectively be treated as a partial initializer by designated initializers of the class. 
>>>> 
>>>> John McCall briefly described <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151221/004479.html> how this might work in the mailing list thread discussing extensions with stored properties. 
>>>> 
>>>> Proposed solution
>>>> 
>>>> The proposed solution is to introduce a partial declaration modifier for initializers. 
>>>> 
>>>> When this declaration modifier is present the entire body of the initializer must comply with phase 1 initialization. It is possible for a partial initializer to initialize all stored properties but it is not possible for a partial initializer to call a super initializer.
>>>> Partial initializers can only be called during phase 1 of initialization by another partial initalizer of the same type, by a designated initializer of the same type, or by another struct initializer of the same type.
>>>> The compiler keeps track of the properties initialized by a call to a partial initializer and uses that knowledge when enforcing initialization rules in phase 1 in the calling initializer.
>>>> Partial initializers receive an identifier, which avoids the need to rely on overloading to differentiate between partial initializers.
>>> That’s interesting.  This is also a syntactic for partial deinit, I suppose, although that seems pointless outside of class extensions (and even then it’s suspect).  I guess it’s clear enough what’s happening because the .init suffix is not normally writable.
> 
> What are the rules on this identifier?  Does it have to be unique, or can partial initializers also be overloaded?  What do you expect to do for extensions?

Good questions.  I could go either way on allowing overloads.  I suppose it makes sense to allow them since they are allowed pretty much everywhere else.  What do you think would be best?

For extensions I think it makes the most sense to require an identifier for extensions that include stored properties.  I think this makes it clear in the designated initializers for the type exactly which extension is getting initialized by a specific init call.

Class extensions could then have 3 kinds of initializers:

* designated initializers for the extension (must init all stored properties introduced in the extension)
* partial initializers for the extension (can init some, but not necessarily all stored properties introduced in the extension and are callable only by the extension designated initializers)
* convenience initializers for the class (we already have these)

Example:

extension MoreStuff MyClass {
  let a, b: Int
  partial init onlyA() {
    a = 44
  }
  init (i: Int) {
    a = i
    b = i * 2
  }
}

class MyClass {
  let x, y: Int
  init() {
    x = 2
    y = 2
    MoreStuff.init(44)
  }
}

If we allowed stored properties in struct extensions they would just define normal primary, delegating, or partial initializers, of course requiring visibility to any properties they initialize.  That said, I don’t think the plan is to support stored properties in struct extensions anyway.

If we allow stored properties in protocols conformed to within the same module as the type definition (as Chris and Doug are discussing) a similar syntax could be used in the designated initializers of the type to initialize the protocol storage: `ProtocolName.init(arg1, arg2, etc)`.
>>>> Struct partial initializers are allowed to include an access control modifier specifying their visibility. They can be called in any initializer of the same type where they are visible.
>>>> Class partial initializers are always private. This is because they can only be declared in the main body of a class and can only be called by a designated initializer of the same type.
>>> So you’re explicitly forbidding the class-extension use case I had described as the major motivation for this feature.
>> 
>> No, this proposal is just introducing the feature into the current language.  I consider introducing the class-extension use case to be part of a new proposal.  That said, if you felt like it should be introduced in this proposal (presumably along with stored properties in extensions) we could do that.
> 
> Oh, of course, you’re absolutely right.  That’s a reasonable approach, but understand that this is basically a completely different feature, in terms of both its expected uses and its implementation, from partial initialization of class extensions.  The only similarity is syntax.  It would abstractly be reasonable to independently decide that one of these features isn’t a good idea, isn’t worth the complexity, or is simply out of scope.

Yes I agree that it is different and independent.  I thought the implementation might have some overlap but you obviously know better than I do about that!

> 
> Also, I think it wouldn’t hurt for this proposal to be more explicit about how you expect it to interact with other proposals.  You say that it combines well with memberwise initializers; okay, I think I see where you’re going with that, but spell it out, please.  Similarly, a brief section outlining how you expect this to work with stored properties in extensions would not be out of place, and this line in particular should call out the fact that it will change if stored properties are added to other contexts.

It’s definitely not a complete proposal yet.  I just wanted to get the discussion going while the memberwise init proposal is under consideration.  I’ll be sure to to include the items you mention here.

>>>> There is no restriction on the order in which the partial initializers are called aside from the rule that the can only be called during phase 1 of initialization and the rule that a let property must be initialized once and only once during phase 1.
>>> Any initialization proposal needs to be explicit about the new rules it’s proposing for definitive initialization.  How are partial initializers checked?  What properties can be accessed within them?  How do calls to them fit into other checking?
>> 
>> Agree.  Detailed design is still open.  Here’s the basic idea:
>> 
>> 1. A partial initializer can initialize any subset of stored properties.
>> 2. A partial initializer can only read a property it initializes itself.
>> 3. If an initializer initializes a `let` property it cannot call a partial initializer that also initializes that property.
>> 4. After calling a partial initializer, the properties it initialized are considered initialized in the calling initializer as well.  They can be read from, etc.
>> 
>> Partial initializers themselves are not “checked” per-se.  The compiler would keep track of the properties they initialize and use that information when checking the calling initializer (for duplicate `let` assignments, complete initialization, etc).
> 
> Okay.  This requires a simple dependency-ordering pass among partial initializers, but that should be straightforward to do.
> 
> The model will be much simpler if you ban redundant assignments to ‘var’ properties as well.

I will be happy to do that if it makes implementation easier!  I don’t have any specific use cases in mind that would require allowing redundant assignments to `var` properties.  That can always be added later if necessary.

>>>> Basic example
>>>> 
>>>> struct S {
>>>>   let a, b, c, d: Int
>>>>   partial init bAndC(i: Int = 10) {
>>>>     b = 10
>>>>     c = 20
>>>>   }
>>>>   init(i: Int) {
>>>>     a = i * 2
>>>>     d = i * 4
>>>>     bAndC.init()
>>>>   }
>>>> }
>>> Interesting.  So partial initializers in the main declaration provide a way to abstract out common initializer logic from multiple initializers without creating a separate initializer.  We otherwise don't have this because (1) you cannot call methods during phase 1 of initialization and (2) we do not do the appropriate DI-checking in a method.  I think that’s potentially very useful for classes with a lot of complex initialization logic.
>> 
>> Agree.  It is a generally useful initialization feature and also paves the way for stored properties declared outside the main body of the type.
>> 
>> It combines really well with syntactic sugar to forward parameters directly from a primary to a partial initializer as well as sugar for concise declaration of trivial partial memberwise initializers.
> 
> Sure.  I’m just asking you to spell it out.

Yep, will do.  I’ll be continuing to work on filling it out over the next few days.

Matthew

> 
> John.
> 
>> 
>> Matthew
>> 
>>> 
>>> John.
>>> 
>>>> Calling multiple partial initializers
>>>> 
>>>> struct S {
>>>>   let a, b, c, d: Int
>>>>   partial init bAndC() {
>>>>     b = 10
>>>>     c = 20
>>>>   }
>>>>   partial init configureD(i: Int) {
>>>>     d = i * 4
>>>>   }
>>>>   init(i: Int) {
>>>>     configureD.init(i)
>>>>     a = i * 2
>>>>     bAndC.init()
>>>>   }
>>>> }
>>>> One partial init calling another
>>>> 
>>>> struct S {
>>>>   let a, b, c, d: Int
>>>>   partial init bAndC() {
>>>>     b = 10
>>>>     c = 20
>>>>   }
>>>>   partial init bcAndD(i: Int) {
>>>>     d = i * 4
>>>>     bAndC.init()
>>>>   }
>>>>   init(i: Int) {
>>>>     a = i * 2
>>>>     bcAndD.init(i)
>>>>   }
>>>> }
>>>> Syntactic sugar for forwarding
>>>> 
>>>> It will be a common use case to factor out some common initialization logic using a partial initializer. Often the parameters for the partial initializer will simply be forwarded. 
>>>> 
>>>> Syntactic sugar is provided to streamline this use case. It matches the placeholder syntax of the parameter forwarding proposal <https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md>, with the placeholder identifier matching the name of a partial initializer. The implementation of forwarding matches the implementation of the parameter forwarding proposal.
>>>> 
>>>> Basic forwarding sugar example
>>>> 
>>>> struct S {
>>>>   let a, b, c, d: Int
>>>>   partial init bAndC(b: Int = 10, cLabel c: Int = 20) {
>>>>     b = b
>>>>     c = c
>>>>   }
>>>>   
>>>>   // user writes
>>>>   init(i: Int, ...bAndC) {
>>>>     a = i * 2
>>>>     d = i * 4
>>>>   }
>>>>   
>>>>   // compiler synthesizes
>>>>   init(i: Int, b: Int = 10, cLabel: Int = 20) {
>>>>     bAndC.init(b: b, cLabel: cLabel)
>>>>     a = i * 2
>>>>     d = i * 4
>>>>   }
>>>>   
>>>>   // equivalent to writing the following under the parameter forwarding proposal:
>>>>   // NOTE: the placeholder identifier is changed to `bAndCParams` here to avoid
>>>>   // conflict with the name of the partial initializer itself
>>>>   init(i: Int, ...bAndCParams) {
>>>>     bAndC.init(...bAndCParams)
>>>>     a = i * 2
>>>>     d = i * 4
>>>>   }
>>>> }
>>>> Forwarding to more than one partial initializer
>>>> 
>>>> struct S {
>>>>   let a, b, c, d: Int
>>>>   partial init bAndC(b: Int = 10, cLabel c: Int = 20) {
>>>>     b = b
>>>>     c = c
>>>>   }
>>>>   partial init aAndD(i: Int) {
>>>>     a = i * 10
>>>>     d = i * 100
>>>>   }
>>>>   
>>>>   // user writes
>>>>   init(...aAndD, ...bAndC) {}
>>>>   
>>>>   // compiler synthesizes
>>>>   init(i: Int, b: Int = 10, cLabel: Int = 20) {
>>>>     aAndD.init(i: Int)
>>>>     bAndC.init(b: b, cLabel: cLabel)
>>>>   }
>>>>   
>>>>   // equivalent to writing the following under the parameter forwarding proposal:
>>>>   // NOTE: the placeholder identifier is changed to `bAndCParams` here to avoid
>>>>   // conflict with the name of the partial initializer itself
>>>>   init(...aAndDParams, ...bAndCParams) {
>>>>     aAndD.init(...aAndDParams)
>>>>     bAndC.init(...bAndCParams)
>>>>   }
>>>> }
>>>> One partial initizer forwarding to another
>>>> 
>>>> struct S {
>>>>   let a, b, c, d: Int
>>>>   partial init bAndC(b: Int = 10, cLabel c: Int = 20) {
>>>>     b = b
>>>>     c = c
>>>>   }
>>>>   
>>>>   // user writes
>>>>   partial init abAndC(...bAndC) {
>>>>     a = 42
>>>>   }
>>>>   
>>>>   // compiler synthesizes
>>>>   partial init abAndC(b: Int = 10, cLabel c: Int = 20) {
>>>>     bAndC.init(b: b, c: c)
>>>>     a = 42
>>>>   }
>>>>   
>>>>   // equivalent to writing the following under the parameter forwarding proposal:
>>>>   // NOTE: the placeholder identifier is changed to `bAndCParams` here to avoid
>>>>   // conflict with the name of the partial initializer itself
>>>>   partial init abAndC(i: Int, ...bAndCParams) {
>>>>     bAndC.init(...bAndCParams)
>>>>     a = 42
>>>>   }
>>>> }
>>>> Forwarding to super
>>>> 
>>>> If super contains a single designated initializer subclasses can use the same syntax to forward parameters to the super initializer. The call to super is added at the end of the initializer body. This means that if phase 2 initialization logic is necessary it will not be possible to use the syntactic sugar.
>>>> 
>>>> class Base {
>>>>   init(i: Int, s: String, f: Float) {}
>>>> }
>>>> 
>>>> class Derived: Base {
>>>>   let d: Double
>>>>   
>>>>   // user writes
>>>>   init(...super) {
>>>>     d = 42
>>>>   }
>>>>   
>>>>   // compiler synthesizes
>>>>   init(i: Int, s: String, f: Float) {
>>>>     d = 42
>>>>     super.init(i: i, s: s, f: f)
>>>>   }
>>>>   
>>>>   // equivalent to writing the following under the parameter forwarding proposal:
>>>>   init(...superParams) {
>>>>     d = 42
>>>>     super.init(...superParams)
>>>>   }
>>>> }
>>>> If super contains more than one initializer
>>>> 
>>>> struct S {
>>>>   let i: Int, s: String, f: Float
>>>> }
>>>> 
>>>> class Base {
>>>>   init(s: S) {}
>>>>   init(i: Int, s: String, f: Float) {}
>>>> }
>>>> 
>>>> class Derived: Base {
>>>>   let d: Double = 42
>>>>   // error: ambiguous forward to super
>>>>   init(...super) {} 
>>>> }
>>>> Implicit partial initializers
>>>> 
>>>> Three implicit paritial initializers exist. They match the behavior of public, internal, and private memberwise intializers using the automatic property eligibility model described in the Flexible Memberwise Initialization <https://github.com/apple/swift-evolution/blob/master/proposals/0018-flexible-memberwise-initialization.md> proposal, thus making that proposal obsolete if this proposal is accepted. The private and internal implicit partial initializers also match the behavior of the implicit memberwise initializer if one exists for the type.
>>>> 
>>>>   // flexibile memberwise initialization proposal:
>>>>   public memberwise init(...) {
>>>>     // init all private an internal props
>>>>   }
>>>>   // corresponding syntax using implicit partial init and forwarding:
>>>>   public init(...publicMemberwise) {
>>>>     // init all private an internal props
>>>>   }
>>>>   
>>>>   // flexibile memberwise initialization proposal:
>>>>   memberwise init(...) {
>>>>     // init all private props
>>>>   }
>>>>   // corresponding syntax using implicit partial init and forwarding:
>>>>   init(...internalMemberwise) {
>>>>     // init all private props
>>>>   }
>>>>   
>>>>   // flexibile memberwise initialization proposal:
>>>>   private memberwise init(...) {}
>>>>   // corresponding syntax using implicit partial init and forwarding:
>>>>   private init(...privateMemberwise) {}
>>>> Detailed design
>>>> 
>>>> TODO but should fall out pretty clearly from the proposed solution
>>>> 
>>>> Impact on existing code
>>>> 
>>>> This is a strictly additive change. It has no impact on existing code.
>>>> 
>>>> Alternatives considered
>>>> 
>>>> I believe the basic structure of partial initialization falls naturally out of the current initialization rules.
>>>> 
>>>> The syntax for declaring and invoking partial initializers is game for bikeshedding.
>>>> 
>>>> Members computed tuple property
>>>> 
>>>> Joe Groff posted the idea of using a members computed tuple property <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/005619.html> during the review of the Flexible Memberwise Initialization <https://github.com/apple/swift-evolution/blob/master/proposals/0018-flexible-memberwise-initialization.md> proposal. The ensuing discussion inspired me to think more deeply about how more general features could support the memberwise initialization use case. That line of thinking eventually led me to create this proposal as well as the Property Lists <https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/https://github.com/anandabits/swift-evolution/blob/property-lists/proposals/NNNN-property-lists.md> proposal.
>>>> 
>>>> There are a few problems with the members computed tuple approach:
>>>> 
>>>> It uses a computed property setter to initialize let properties. This is not something you can do in manually written code and just feels wrong. Initialization, especially of let properties, but also the first set of a var property, should happen in an initilizer context. Partial initializers allow for that in a much more elegant fashion than a weird special case property with a setter that is kind of an initializer.
>>>> The question of how to expose default property values in initializer parameters was never answered.
>>>> The question of how to provide memberwise initialization for a subset of properties was never answered.
>>>> 
>>>> 
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160111/e482b3a9/attachment.html>


More information about the swift-evolution mailing list