[Proposal Draft] partial initializers


(Matthew Johnson) #1

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

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
* Partial initializers: 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

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


(John McCall) #2

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

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
* Partial initializers: 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

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.

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.

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?

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.

John.

···

On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

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


(David Owens II) #3

The biggest drawback I see is that is doesn’t really address the ability to use normal functions to initialize the state of the type. For example, in order to implement a “reset” or “clear” mechanism, I still need to duplicate a bunch of init code.

class CFoo {
    private(set) var value: Int
    
    func reset() {
        value = 0
    }
    
    init() {
        // Really want to just call this...
        //reset()
        
        // But, instead, I need to duplicate the code from `reset()`
        value = 0
    }
    
    func inc() {
        ++value
    }
}

let c = CFoo()
c.inc()
c.value // 1
c.reset()
c.value // 0

Take the above code, the “init” functionality is really about putting the type in the correct state. I think I’d rather see a mechanism to make a non-initializer as adhering to the rules of an init() so that it could be used in multiple contexts.

I think this modification makes your proposal much more interesting and applicable to more use cases.

-David

···

On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@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

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
* Partial initializers: 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


(Matthew Johnson) #4

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

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
* Partial initializers: 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

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.

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.

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

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.

Matthew

···

On Jan 11, 2016, at 11:21 AM, John McCall <rjmccall@apple.com> wrote:

On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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


(Matthew Johnson) #5

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

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
* Partial initializers: 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

The biggest drawback I see is that is doesn’t really address the ability to use normal functions to initialize the state of the type. For example, in order to implement a “reset” or “clear” mechanism, I still need to duplicate a bunch of init code.

class CFoo {
    private(set) var value: Int
    
    func reset() {
        value = 0
    }
    
    init() {
        // Really want to just call this...
        //reset()
        
        // But, instead, I need to duplicate the code from `reset()`
        value = 0
    }
    
    func inc() {
        ++value
    }
}

let c = CFoo()
c.inc()
c.value // 1
c.reset()
c.value // 0

Take the above code, the “init” functionality is really about putting the type in the correct state. I think I’d rather see a mechanism to make a non-initializer as adhering to the rules of an init() so that it could be used in multiple contexts.

I think this modification makes your proposal much more interesting and applicable to more use cases.

That is a fair point.

The reason I didn’t go that route has a lot to do with `let` properties. It would feel weird to me to be assigning to a `let` in a non-init method (that is why I didn’t like Joe’s tuple property idea). And a method that does that couldn’t be called any other time anyway.

Maybe we could introduce partial inits as well as an attribute or decl modifier for methods that have similar behavior to partial inits, but are not allowed to assign to a `let` property (maybe @init). I could add something along those lines to this proposal or it could be a separate follow-on proposal.

What do you think of that?

Matthew

···

On Jan 11, 2016, at 7:33 PM, David Owens II <david@owensd.io> wrote:

On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

-David


(John McCall) #6

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

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
* Partial initializers: 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

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?

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.

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.

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.

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.

John.

···

On Jan 11, 2016, at 9:34 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 11, 2016, at 11:21 AM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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


(Matthew Johnson) #7

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

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
* Partial initializers: 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

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

···

On Jan 11, 2016, at 12:52 PM, John McCall <rjmccall@apple.com> wrote:

On Jan 11, 2016, at 9:34 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 11, 2016, at 11:21 AM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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


(Matthew Johnson) #8

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

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
* Partial initializers: 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

The biggest drawback I see is that is doesn’t really address the ability to use normal functions to initialize the state of the type. For example, in order to implement a “reset” or “clear” mechanism, I still need to duplicate a bunch of init code.

class CFoo {
    private(set) var value: Int
    
    func reset() {
        value = 0
    }
    
    init() {
        // Really want to just call this...
        //reset()
        
        // But, instead, I need to duplicate the code from `reset()`
        value = 0
    }
    
    func inc() {
        ++value
    }
}

let c = CFoo()
c.inc()
c.value // 1
c.reset()
c.value // 0

Take the above code, the “init” functionality is really about putting the type in the correct state. I think I’d rather see a mechanism to make a non-initializer as adhering to the rules of an init() so that it could be used in multiple contexts.

I think this modification makes your proposal much more interesting and applicable to more use cases.

That is a fair point.

The reason I didn’t go that route has a lot to do with `let` properties. It would feel weird to me to be assigning to a `let` in a non-init method (that is why I didn’t like Joe’s tuple property idea). And a method that does that couldn’t be called any other time anyway.

Maybe we could introduce partial inits as well as an attribute or decl modifier for methods that have similar behavior to partial inits, but are not allowed to assign to a `let` property (maybe @init). I could add something along those lines to this proposal or it could be a separate follow-on proposal.

What do you think of that?

Actually, another solution to this would be to just allow partial initializers that don’t initialize `let` properties to be called post-initialization. They have identifiers so they could be called like a normal method after initialization completes.

I kind of like this idea better because it emphasizes the fact that they are initializing state and not doing anything else. It sets them apart from normal methods. It also avoids the need for more syntax.

Attempting to call a partial init that does initialize a `let` property post-initialization would be a compiler error.

Matthew

···

On Jan 11, 2016, at 7:43 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 11, 2016, at 7:33 PM, David Owens II <david@owensd.io <mailto:david@owensd.io>> wrote:

On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Matthew

-David

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


(Matthew Johnson) #9

The model will be much simpler if you ban redundant assignments to ‘var’ properties as well.

Is this just referring to the partial init itself? Or the entire initialization sequence? I ask because I am wondering specifically about `var` properties with an initial value. The compiler will insert an initialization at the beginning of the designated initializer. Is it ok if a partial init overwrites that value? That is the only redundant assignment that I would care about, at least for now. But that seems important to allow.

Matthew


(David Owens II) #10

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

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
* Partial initializers: 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

The biggest drawback I see is that is doesn’t really address the ability to use normal functions to initialize the state of the type. For example, in order to implement a “reset” or “clear” mechanism, I still need to duplicate a bunch of init code.

class CFoo {
    private(set) var value: Int
    
    func reset() {
        value = 0
    }
    
    init() {
        // Really want to just call this...
        //reset()
        
        // But, instead, I need to duplicate the code from `reset()`
        value = 0
    }
    
    func inc() {
        ++value
    }
}

let c = CFoo()
c.inc()
c.value // 1
c.reset()
c.value // 0

Take the above code, the “init” functionality is really about putting the type in the correct state. I think I’d rather see a mechanism to make a non-initializer as adhering to the rules of an init() so that it could be used in multiple contexts.

I think this modification makes your proposal much more interesting and applicable to more use cases.

That is a fair point.

The reason I didn’t go that route has a lot to do with `let` properties. It would feel weird to me to be assigning to a `let` in a non-init method (that is why I didn’t like Joe’s tuple property idea). And a method that does that couldn’t be called any other time anyway.

Well, this is a limitation (assigning to lets) that convenience inits() have as well. Also, as it’s a function that is callable, it makes little sense to have `let` assignments in it anyway. So that limitation seems fine. The annotation would only be there to allow the compiler (and code authors) know that this method is intended to be allowed in the initialization rules, so some restrictions may apply.

Maybe we could introduce partial inits as well as an attribute or decl modifier for methods that have similar behavior to partial inits, but are not allowed to assign to a `let` property (maybe @init). I could add something along those lines to this proposal or it could be a separate follow-on proposal.

What do you think of that?

What I’m saying is that you don’t need the partial inits anymore, you still need some of your rules, but the actual “partial init” would be replaced by this modifier on functions.

Maybe something like this:

    initializer func reset() {
        value1 = 0
    }

The “initializer” modifier would be your “partial init” modifier. For this to work though, you’d basically have to take the same limitation as convenience inits() today and disallow `let` values to be set. However, most of your other rules would apply to these.

Well… I guess there is this too:

Partial initializers receive an identifier, which avoids the need to rely on overloading to differentiate between partial initializers.

This would still remove the ability to use `let` assignments, but those functions could be allowed to be called like normal functions.

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

var s = S(12)
s.bAndC(1)

Again, you’ve have to remove the `let` assignments…

Anyhow, just mostly thinking out loud at this point.

-David

···

On Jan 11, 2016, at 5:43 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 11, 2016, at 7:33 PM, David Owens II <david@owensd.io <mailto:david@owensd.io>> wrote:

On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(John McCall) #11

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

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
* Partial initializers: 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

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?

Your examples seem to lead towards them being overloadable: you would add a partial initializer for a cluster of properties, and then maybe there are multiple ways to initialize those properties.

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

Hmm. I’m not really comfortable with the idea of these partial initializers in extensions not being somehow marked as partial. Without that, they look just like a designated initializer; and designated initializers in extensions are a plausible future language direction (they would, of course, have to peer-delegate to another designated initializer).

Maybe a better model would be to say that they’re still partial initializers, and you can only call a partial initializer for an extension from a designated initializer if the partial initializer is complete for its extension. That restriction could be weekend over time if we wanted to. On the flip side, that restriction is also consistent with not allowing incomplete partial initializers at all.

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!

Maybe. With only complete partial initializers, DI just needs to track that all extensions that declare partial initializers have a partial initializer called; it doesn’t necessarily even need to allow recursive partial initialization, so the logic is basically local. With incomplete partial initializers, and especially with delegation between them, it gets significantly more complex.

Oh, you should add that partial initializers should not be allowed to fail. That’s a liftable restriction in time, I think, but it cuts down on the initial complexity quite a bit.

John.

···

On Jan 11, 2016, at 1:37 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 11, 2016, at 12:52 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 11, 2016, at 9:34 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 11, 2016, at 11:21 AM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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


(Matthew Johnson) #12

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

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
* Partial initializers: 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

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?

Your examples seem to lead towards them being overloadable: you would add a partial initializer for a cluster of properties, and then maybe there are multiple ways to initialize those properties.

Actually, I just realized that they cannot be overloadable. I think the concise syntax for forwarding to partial initializers will be extremely useful and it would be ambiguous if we allow overloading.

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

Hmm. I’m not really comfortable with the idea of these partial initializers in extensions not being somehow marked as partial. Without that, they look just like a designated initializer; and designated initializers in extensions are a plausible future language direction (they would, of course, have to peer-delegate to another designated initializer).

Good to know. I didn’t realize designated inits in extensions that was something was a possible direction. In that case I agree for sure.

Maybe a better model would be to say that they’re still partial initializers, and you can only call a partial initializer for an extension from a designated initializer if the partial initializer is complete for its extension. That restriction could be weekend over time if we wanted to. On the flip side, that restriction is also consistent with not allowing incomplete partial initializers at all.

What do you think about marking the “complete for the extension” partial inits `extension init` rather than `partial init`. I think the distinction is useful, both for verifying the programmers intent that the init is complete for the extension, as well as for distinguishing which are eligible to be called from the designated initializer and which are not.

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!

Maybe. With only complete partial initializers, DI just needs to track that all extensions that declare partial initializers have a partial initializer called; it doesn’t necessarily even need to allow recursive partial initialization, so the logic is basically local. With incomplete partial initializers, and especially with delegation between them, it gets significantly more complex.

Makes sense. If allowing one partial init to call another partial init is a potential obstacle to accepting this proposal I would remove that. Please let me know if that is the case.

Oh, you should add that partial initializers should not be allowed to fail. That’s a liftable restriction in time, I think, but it cuts down on the initial complexity quite a bit.

Yes, I agree. This makes sense as a restriction.

What do you think of David’s comments about sharing the init logic with post-init code? Obviously this isn’t possible for init logic that initializes `let` properties, but for initialization logic that only touches `var` properties it makes sense.

I lean towards adjusting the rules to allow a partial init that only touches `var` properties to be callable as a normal method post-initialization. These would also be allowed to have an access control specifier for the post-init visibility. Do you like that direction?

Thanks for all of your feedback and comments today!

Matthew

···

On Jan 11, 2016, at 3:57 PM, John McCall <rjmccall@apple.com> wrote:

On Jan 11, 2016, at 1:37 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 11, 2016, at 12:52 PM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 11, 2016, at 9:34 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 11, 2016, at 11:21 AM, John McCall <rjmccall@apple.com <mailto:rjmccall@apple.com>> wrote:

On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

John.

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


(Brent Royal-Gordon) #13

What I’m saying is that you don’t need the partial inits anymore, you still need some of your rules, but the actual “partial init” would be replaced by this modifier on functions.

Maybe something like this:

    initializer func reset() {
        value1 = 0
    }

The “initializer” modifier would be your “partial init” modifier.

I was about to post this idea (although I was going to suggest `partial func`, and possibly giving the `partial` keyword a parenthesized list of the properties that it expects to be initialized before you call it).

For this to work though, you’d basically have to take the same limitation as convenience inits() today and disallow `let` values to be set. However, most of your other rules would apply to these.

Keep in mind that it's only the assignment itself that would have to be in the initializer. You could still use a partial method to encapsulate the logic used to calculate the constant's value:

  let foo: Foo
  var bar: Bar
  
  init(bar: Bar) {
    self.bar = bar
    foo = makeFoo()
    super.init()
  }
  
  partial(bar) func makeFoo() -> Foo {
    return Foo(bar: bar)
  }

···

--
Brent Royal-Gordon
Architechies


(Matthew Johnson) #14

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

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
* Partial initializers: 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

The biggest drawback I see is that is doesn’t really address the ability to use normal functions to initialize the state of the type. For example, in order to implement a “reset” or “clear” mechanism, I still need to duplicate a bunch of init code.

class CFoo {
    private(set) var value: Int
    
    func reset() {
        value = 0
    }
    
    init() {
        // Really want to just call this...
        //reset()
        
        // But, instead, I need to duplicate the code from `reset()`
        value = 0
    }
    
    func inc() {
        ++value
    }
}

let c = CFoo()
c.inc()
c.value // 1
c.reset()
c.value // 0

Take the above code, the “init” functionality is really about putting the type in the correct state. I think I’d rather see a mechanism to make a non-initializer as adhering to the rules of an init() so that it could be used in multiple contexts.

I think this modification makes your proposal much more interesting and applicable to more use cases.

That is a fair point.

The reason I didn’t go that route has a lot to do with `let` properties. It would feel weird to me to be assigning to a `let` in a non-init method (that is why I didn’t like Joe’s tuple property idea). And a method that does that couldn’t be called any other time anyway.

Well, this is a limitation (assigning to lets) that convenience inits() have as well. Also, as it’s a function that is callable, it makes little sense to have `let` assignments in it anyway. So that limitation seems fine. The annotation would only be there to allow the compiler (and code authors) know that this method is intended to be allowed in the initialization rules, so some restrictions may apply.

Right, but I do want to allow partial inits to write to a 'let'. That's why I went down the other path.

Maybe we could introduce partial inits as well as an attribute or decl modifier for methods that have similar behavior to partial inits, but are not allowed to assign to a `let` property (maybe @init). I could add something along those lines to this proposal or it could be a separate follow-on proposal.

What do you think of that?

What I’m saying is that you don’t need the partial inits anymore, you still need some of your rules, but the actual “partial init” would be replaced by this modifier on functions.

Maybe something like this:

    initializer func reset() {
        value1 = 0
    }

The “initializer” modifier would be your “partial init” modifier. For this to work though, you’d basically have to take the same limitation as convenience inits() today and disallow `let` values to be set. However, most of your other rules would apply to these.

Exactly. That 'let' limitation is why I don't consider this approach sufficient.

Well… I guess there is this too:

Partial initializers receive an identifier, which avoids the need to rely on overloading to differentiate between partial initializers.

This would still remove the ability to use `let` assignments, but those functions could be allowed to be called like normal functions.

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

var s = S(12)
s.bAndC(1)

Again, you’ve have to remove the `let` assignments…

Yes, I think allowing this makes sense as long as the partial init does not write to a 'let'. I have updated the proposal to allow for this. I will publish a new draft soon, hopefully later today.

Matthew

Anyhow, just mostly thinking out loud at this point.

No harm in that! I appreciate the feedback. That's why I shared it even though it was still an early draft.

Matthew

···

Sent from my iPad

On Jan 11, 2016, at 10:33 PM, David Owens II <david@owensd.io> wrote:

On Jan 11, 2016, at 5:43 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 11, 2016, at 7:33 PM, David Owens II <david@owensd.io> wrote:
On Jan 10, 2016, at 7:44 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

-David


(David Owens II) #15

True, but that is a bit awkward, especially if there are multiple lets you want to initialize. It could also expose implementation details in the case of being able to reset the data from the call as some of the `let` could potentially be private.

Also, Matthew, it would probably be good to remove the forwarding stuff from this proposal. The idea of these helper-type initializers really doesn’t need to be coupled with that to stand on its own.

-David

···

On Jan 11, 2016, at 9:49 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

Keep in mind that it's only the assignment itself that would have to be in the initializer. You could still use a partial method to encapsulate the logic used to calculate the constant's value:

  let foo: Foo
  var bar: Bar
  
  init(bar: Bar) {
    self.bar = bar
    foo = makeFoo()
    super.init()
  }
  
  partial(bar) func makeFoo() -> Foo {
    return Foo(bar: bar)
  }


(Matthew Johnson) #16

What I’m saying is that you don’t need the partial inits anymore, you still need some of your rules, but the actual “partial init” would be replaced by this modifier on functions.

Maybe something like this:

   initializer func reset() {
       value1 = 0
   }

The “initializer” modifier would be your “partial init” modifier.

I was about to post this idea (although I was going to suggest `partial func`, and possibly giving the `partial` keyword a parenthesized list of the properties that it expects to be initialized before you call it).

How common do you think it would be for a partial init to need to require that some properties already be initialized. Do you think that is important right away or do you think it's ok to start without it and enhance the feature later if we run into common needs for it? I lean towards this as a possible enhancement rather than an immediate requirement.

Matthew

···

Sent from my iPad
On Jan 11, 2016, at 11:49 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

For this to work though, you’d basically have to take the same limitation as convenience inits() today and disallow `let` values to be set. However, most of your other rules would apply to these.

Keep in mind that it's only the assignment itself that would have to be in the initializer. You could still use a partial method to encapsulate the logic used to calculate the constant's value:

   let foo: Foo
   var bar: Bar
   
   init(bar: Bar) {
       self.bar = bar
       foo = makeFoo()
       super.init()
   }
   
   partial(bar) func makeFoo() -> Foo {
       return Foo(bar: bar)
   }

--
Brent Royal-Gordon
Architechies


(Matthew Johnson) #17

Keep in mind that it's only the assignment itself that would have to be in the initializer. You could still use a partial method to encapsulate the logic used to calculate the constant's value:

  let foo: Foo
  var bar: Bar
  
  init(bar: Bar) {
    self.bar = bar
    foo = makeFoo()
    super.init()
  }
  
  partial(bar) func makeFoo() -> Foo {
    return Foo(bar: bar)
  }

True, but that is a bit awkward, especially if there are multiple lets you want to initialize. It could also expose implementation details in the case of being able to reset the data from the call as some of the `let` could potentially be private.

Also, Matthew, it would probably be good to remove the forwarding stuff from this proposal. The idea of these helper-type initializers really doesn’t need to be coupled with that to stand on its own.

This could be done, but I believe it will be very common to just forward to a partial init. Allowing that to be done concisely is important enough to be part of the core proposal. I am removing any mention of general forwarding though so this proposal will stand on its own.

Matthew

···

Sent from my iPad

On Jan 12, 2016, at 2:00 AM, David Owens II <david@owensd.io> wrote:

On Jan 11, 2016, at 9:49 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

-David


(Brent Royal-Gordon) #18

I was about to post this idea (although I was going to suggest `partial func`, and possibly giving the `partial` keyword a parenthesized list of the properties that it expects to be initialized before you call it).

How common do you think it would be for a partial init to need to require that some properties already be initialized. Do you think that is important right away or do you think it's ok to start without it and enhance the feature later if we run into common needs for it? I lean towards this as a possible enhancement rather than an immediate requirement.

I think that many, possibly most, partial methods—especially those that are meant to be called as normal methods will need to access existing properties, and the compiler will need to make sure partial method calls are ordered such that everything needed is already initialized; it's simply a question of whether we declare what's needed or automatically detect it. I've noticed in previous proposals that scanning the method body is disfavored, so I figure that's not our best bet here.

(On the other hand, if we don't declare required properties, I guess that might leave `partial(only)` to indicate a method that initializes a constant and thus can't be called except in a designated initializer.)

···

--
Brent Royal-Gordon
Architechies


(Matthew Johnson) #19

I was about to post this idea (although I was going to suggest `partial func`, and possibly giving the `partial` keyword a parenthesized list of the properties that it expects to be initialized before you call it).

How common do you think it would be for a partial init to need to require that some properties already be initialized. Do you think that is important right away or do you think it's ok to start without it and enhance the feature later if we run into common needs for it? I lean towards this as a possible enhancement rather than an immediate requirement.

I think that many, possibly most, partial methods—especially those that are meant to be called as normal methods will need to access existing properties

Do you have some concrete examples? I’m sure this would be useful in some cases, I’m just not sure whether we need to include it right away.

If the memberwise init review is anything to go by, there will be a lot of pushback that something like this makes the proposal too complex. People might warm to it more after using the basic feature for a while and realizing the limitation this poses in practice.

, and the compiler will need to make sure partial method calls are ordered such that everything needed is already initialized; it's simply a question of whether we declare what's needed or automatically detect it. I've noticed in previous proposals that scanning the method body is disfavored, so I figure that's not our best bet here.

This is another reason I am not sure about this. I think you are right that this would require a declaration. It becomes much more verbose that way.

One thing I would be concerned about is that the compiler will enforce proper order during initialization, but it will not enforce anything later. If you are resetting state you will be on your own to do things in the correct order. If the partial inits are not able to read from a property they didn’t write that at least helps prevent mistakes during the reset sequence (of course at the cost of some flexibility in structuring your code).

···

On Jan 12, 2016, at 2:23 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

(On the other hand, if we don't declare required properties, I guess that might leave `partial(only)` to indicate a method that initializes a constant and thus can't be called except in a designated initializer.)

--
Brent Royal-Gordon
Architechies


(Brent Royal-Gordon) #20

Let's start with this:

, and the compiler will need to make sure partial method calls are ordered such that everything needed is already initialized; it's simply a question of whether we declare what's needed or automatically detect it. I've noticed in previous proposals that scanning the method body is disfavored, so I figure that's not our best bet here.

This is another reason I am not sure about this. I think you are right that this would require a declaration. It becomes much more verbose that way.

The compiler will absolutely, 100%, inescapably need to know which properties a given partial method initializes. Otherwise it can't properly perform its phase one check, and if this proposal breaks safe initialization, it's not going to be accepted.

We could specify that the compiler takes a peek at the implementation and figures out which fields it initializes, or we could specify that the method declares the fields it will initialize. But however we choose to do it, doing it in *some* way is not negotiable.

I think that many, possibly most, partial methods—especially those that are meant to be called as normal methods will need to access existing properties

Do you have some concrete examples? I’m sure this would be useful in some cases, I’m just not sure whether we need to include it right away.

Sure. Let's extend your resetting example so that the value you can reset the property *to* is configurable:

  struct Resettable<Value> {
    private let initialValue: Value
    var value: Value
    
    partial(uses initialValue, inits value) mutating func reset() {
      value = initialValue
    }
    
    init(_ initialValue: Value) {
      self.initialValue = initialValue
      reset()
    }
  }

If the memberwise init review is anything to go by, there will be a lot of pushback that something like this makes the proposal too complex. People might warm to it more after using the basic feature for a while and realizing the limitation this poses in practice.

You don't want to make things overly complicated, but you also don't want to ignore obvious needs. In hindsight, SE-0019 made both of those mistakes in different places.

One thing I would be concerned about is that the compiler will enforce proper order during initialization, but it will not enforce anything later. If you are resetting state you will be on your own to do things in the correct order. If the partial inits are not able to read from a property they didn’t write that at least helps prevent mistakes during the reset sequence (of course at the cost of some flexibility in structuring your code).

I'm not really sure why this is a concern. After `init` finishes, none of the fields you access could be uninitialized. There's nothing built in to Swift that lets you ensure methods are only called when the instance is in a state that expects them; all you've got for that is precondition(). I just don't see how this is any worse than what we've already got.

···

--
Brent Royal-Gordon
Architechies