[Review] SE-0018 Flexible Memberwise Initialization

I want to thank the core team for putting so much thought and consideration into their feedback. This was clearly given extensive discussion. I also want to appologize if it proved to be a distraction from more important goals.

No need to apologize at all, I was the one getting scolded here, not you :-)

I agree with the recommendation the team has made. I began to be concerned about the "complexity" of the “automatic" model prior to the start of the review and those concerns proved to be shared by many. Using an "opt-in" approach will provide much-needed clarity. I am especially happy to see that the core team has decided that supporting default parameter values for `let` properties is an important aspect of an eventual solution.

There is one thing that isn't clear to me in the feedback. Does the core team believe the implicit memberwise initializer should:

1) Remain in its current form
2) Remain in a slightly enhanced form (i.e. receive default parameter values)
3) Remain in an enhanced form and also be extended to classes
4) Be removed in favor of making all initilaizers explicitly declared

The core team as a whole didn’t discuss this, but the design that I think makes sense is:

- Extend the current support to root classes and derived classes whose base has a zero-argument init to chain to (e.g. NSObject).
- *Consider* extending the current support to have default values on the parameters. This will be contentious with the core team given the discussion about killing side effects and changing let axioms, etc.

That said, when it comes time to discuss this, I’d suggest just looking at both options and enumerating tradeoffs.

As requested, I will defer work on a modified formal proposal until the time is right. Chris, please let me know when you're ready to take this topic up again. I will draft a proposal based on the "opt-in" approach (with no future enhancements!).

Sounds great, please feel free to ping the list in the mid to late march timeframe, when hopefully we’ll have most of the big ticket items for swift 3 at least figured out, if not all finished.

In the meantime, anyone who is interested in seeing an outline of possible options for an "opt-in" approach that builds on more general initialization features should keep an eye out for the second (first complete) draft of my Partial Initializers proposal. At John McCall's request I am including an a section that describes how partial initializers could be related to memberwise intialization. I hope to have the draft ready later today.

Thank you again to everyone who participated in the discussion and review of my proposal. It has been a very productive conversation. My thinking about the topic has been refined and clarified significantly by this process.

Thank you again for driving this Matthew, I still really want to see this area improved and cleaned up.

-Chris

···

On Jan 14, 2016, at 6:35 AM, Matthew Johnson <matthew@anandabits.com> wrote:

The latter I'm afraid.

···

Sent from my moss-covered three-handled family gradunza

On Jan 6, 2016, at 7:12 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Jan 6, 2016, at 8:46 PM, Dave Abrahams <dabrahams@apple.com> wrote:

Sent from my moss-covered three-handled family gradunza

On Jan 6, 2016, at 5:47 PM, Joe Groff <jgroff@apple.com> wrote:

On Jan 6, 2016, at 5:23 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 6, 2016, at 6:04 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 6, 2016, at 2:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift community,

The review of "Flexible Memberwise Initialization" begins now and runs through January 10th. The proposal is available here:

  https://github.com/apple/swift-evolution/blob/master/proposals/0018-flexible-memberwise-initialization.md

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  * What is your evaluation of the proposal?

It’s okay.

  * Is the problem being addressed significant enough to warrant a change to Swift?

I’m lukewarm about that. I have never found writing out the initializers I want to be a significant burden, and I find my code is better when they’re explicit. Every new feature increases the language's complexity and surface area, and I fear this one is not going to pay its way.

  * Does this proposal fit well with the feel and direction of Swift?

Yes, but I worry that it may be too early to add it. Other features in this space, like truly generic variadics, may well obsolete anything we do today. I’m not sure we should be designing convenience features that are likely to overlap with more general features coming down the road unless the inconvenience is very painful… which I personally don’t find it to be.

It isn’t clear to me how generic variadics might obsolete the functionality of this proposal. Can you elaborate on that?

Not sure if this is exactly what Dave has in mind, but an idea that comes to mind: we could say that structs and classes have a magic "members" tuple and typealias:

struct Foo {
  var x, y: Int

  // Implicit members
  typealias Members = (x: Int, y: Int)
  var members: Members {
    get { return (x: x, y: y) }
    set { (x, y) = newValue }
  }
}

With plausible future features for forwarding tuples as arguments, then the memberwise initializer could be implemented like this:

      // Say that a parameter declared 'x...' receives a tuple of arguments labeled according to its type,
      // like '**x' in Python
init(members...: Members) {
  self.members = members
}

And I think all your other use cases could be covered as well.

That's exactly what I had in mind. Thanks, Joe!

Is there any chance of generic variadics along these lines being part of Swift 3? Or is this down the road further?

Overall I think this is a definite improvement to the current status quo and something needs to be solved. My only concern with this specific proposal is that on an initial glance it's not easy to intuit what the behavior is. I like the end result of this proposal, and I think since it'd be frequently used it'd be easy to remember what it was despite being unintuitive.

I had originally suggested this alternative:

struct Foo {
    let bar: String
    let bas: Int
    let baz: Double
    init(self.bar: String, self.bas: Int, bax: Int) {
          // self.bar = bar synthesized by the compiler
          // self.bas = bas synthesized by the compiler
        self.baz = Double(bax)
    }
}

The upside of this is that it's more clear what happens, downside is that adding a property requires 2 changes (as opposed to current 3, but more than the 1 change required in proposal).

The problem with this is that you have to duplicate information and you may often have more than one initializer. It ends up being much more verbose. I don’t think it adds enough value over fully manual initialization to warrant a language change.

Perhaps a syntax more along the lines of this would be more intuitive and still eliminate the boilerplate.

struct Foo {
    let bar: String
    let bas: Int
    let baz: Double
    init(bax: Int, self = ...) {
          // self.bar = bar synthesized by the compiler
          // self.bas = bas synthesized by the compiler
        self.baz = Double(bax)
    }
}

`memberwise` means nothing to the uninitiated, and `...` without any other operator is a little confusing. I think this solves the issue of saying what the `...` is doing.

I don’t like this. People are going to have to learn the language regardless of the syntax. Remember that the initializer will have the `memberwise` declaration modifier in addition to the `…` in the parameter list. I don’t think `self = …` would be more clear than that to people who have learned about memberwise initialization. And it feels like something that should belong in the body of the initializer if it was required.

···

On Jan 7, 2016, at 9:43 AM, Tal Atlas <me@tal.by> wrote:

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

On Jan 7, 2016, at 2:46 AM, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

  * What is your evaluation of the proposal?

+1 to the basic proposal. I'm much more reserved about the "Future enhancements"; some I don't want at all, some sound plausible but probably need changes.

Thanks for your support Kevin!

As I stated last night, there are two primary things that I think need improving via enhancements of some kind. I hope you can agree with these points:

1. We need a way to specify a default value for memberwise parameters for `let` properties.
2. We need a little bit more control over which properties participate in memberwise initialization when the “automatic” rules don’t quite do the right thing for a particular use case.

Most of the enhancements listed show various ways we could address the second point. We don’t need all of them.

Under the current proposal there will be cases where memberwise initialization would be of great use, but the rules of the automatic model expose a property (probably a `let`) that shouldn’t be exposed. We won’t be able to use memberwise initialization at all in those cases if we don’t have a way to correct that behavior.

There may also be cases where a property doesn’t participate in memberwsie initialization when we would like it to. In those cases we can add an explicit parameter and manually initialize the property, continuing to use memberwise initialization for other properties.

Ideally we can find a simple enhancement that solves both visibility problems. I’m definitely open to any ideas on how we can handle this.

Also, a question and a concern about the basic proposal. The question: you state that the only impact this has on existing code is structs with private properties that have been getting an implicit internal memberwise initializer will have the initializer be private. That's fine, but assuming that the implicit memberwise initializer behaves identically to `memberwise init(...) {}`, surely this proposal also makes the implicit memberwise initializer gain defaulted arguments for any var properties with an initializer expression? Don't get me wrong, I think it's good to change that, but it should be explicitly noted.

That is a great point! It won’t break any existing code, but it will change behavior slightly. I will update the proposal and submit a PR with this change.

As for my concern, it's with the following rule:

If the initializer body assigns to a var property that received memberwise initialization synthesis report a warning. It is unlikely that overwriting the value provided by the caller is the desired behavior.

I understand why you put this in there, but this is a warning that cannot be suppressed and will make it impossible to use a memberwise initializer for perfectly legitimate cases where you do in fact want to mutate the property after it's been assigned to.

For normal initializers I agree with you. However, I think it’s a reasonable for callers to assume that if you expose a property via memberwise initialization the post-initialization value will match the value they provide. This warning is intended to alert you to the fact that you are violating that reasonable assumption.

Do you have an example of where you would want a caller to initialize a property, but then overwrite the value they provide during initialization?

What might be a reasonable thing is a warning that occurs if you assign to the var property without ever having read from it (i.e. a dead store warning on the memberwise initialization of the property). That way if I mutate a property to contain a derived value it's fine, but if I simply write to it without ever reading it, it's a problem.

  * Is the problem being addressed significant enough to warrant a change to Swift?

I think so. Writing initializers can be a large source of boilerplate in Swift, especially when using classes where you can't get the implicit memberwise initializer.

  * Does this proposal fit well with the feel and direction of Swift?

Yes.

  * If you have you used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I'm not aware of a similar feature in any language I'm familiar with.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I spent a few minutes reading over the whole proposal. I did not read any of the swift-evolution thread.

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

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

Meanwhile, back on the topic of the "..." placeholder.

For me, it boils down to:

   1. The addition of the "memberwise" keyword and associated behavior
   would stand on it's own, without the inclusion of the "..." placeholder
   syntax. (IMO)
   2. Omitting the "..." placeholder syntax *now* wouldn't prevent it from
   being added *later*.

If you agree with those statements, then the principle of "smallest,
incremental change <Swift.org - Contributing;
[swift.org] seems to imply that the placeholder should be removed from this
proposal.

That's my opinion. Does the rest of the community feel that the "..."
placeholder is a *necessary* part of this proposal?

···

On Wed, Jan 6, 2016 at 5:23 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 6, 2016, at 7:21 PM, Matthew Johnson <matthew@anandabits.com> > wrote:

On Jan 6, 2016, at 6:39 PM, Alex Johnson <ajohnson@quickleft.com> wrote:

Hi Matthew,

Thanks for the explanation.

Before getting into a deeper discussion, I'd like to try to enumerate the
reasons for adding the placeholder as I understand them:
Does that seem accurate?

Add clarity by visually distinguishing memberwise initializers from normal
initializers.
Introduce a "synthesized parameters placeholder" syntax that might be
useful in other places.
Allow some control over where the synthesized memberwise parameters end up
in the initializer signature.

This is mostly correct (in terms of my motivation - Chris may have
additional reasons).

The point about clarity in regards to the `…` is about making it clear
when looking at the signature that synthesized parameters are included in
addition to those that are manually specified. This the most important
point.

The `memberwise` declaration modifier on the initializer itself is what
distinguishes memberwise initializers from other initializers.

Matthew

~ Alex

On Wed, Jan 6, 2016 at 3:48 PM, Matthew Johnson <matthew@anandabits.com> > wrote:

On Jan 6, 2016, at 5:26 PM, Alex Johnson via swift-evolution < >> swift-evolution@swift.org> wrote:

(this is mostly a repost of a message I sent to the "[draft]" thread for
this proposal, with some light editing to better match terminology in the
proposal)

*What is your evaluation of the proposal?*

I like this proposal. I think it will bring some much-needed ease-of-use.

I have reservations about the "..." placeholder for the memberwise
initialization parameters, though. I know this was suggested by
Chris Lattner, so I'm inclined to defer to his judgement. But, here are my
thoughts:

First, it's very close to the varags syntax (e.g. "Int...") which can
also appear in initializer parameter lists.

Second, and I think more important, I'm not sure that it's all that
*useful*. It's presence isn't necessary for triggering memberwise
initialization synthesis; that is already done by the "memberwise" keyword.

The primary example given in the proposal is:

memberwise init(anInt: Int, anotherInt: Int, ...) {
  /* code using anInt and anotherInt */
}

That is, it's used to indicate where the synthesized parameters appear in
the parameter list if there are also custom (non-memberwise) parameters.

My question is, *could the memberwise initialization parameters always
be last?* That would eliminate the need for the placeholder.

I don't think I've seen a compelling case for embedding the "..."
*within* a list of custom arguments, like:

memberwise init(anInt: Int, ..., anotherInt: Int) {
  /* code using anInt and anotherInt */
}

It's been mentioned several times in the discussion of this proposal that
this behavior is purely optional. If it turns out that there are rare cases
where placing the memberwise params in the middle is useful, authors can
use manual initialization.

Hi Alex, thanks for your review.

The initial draft of the proposal did exactly what you suggest - it did
not include the placeholder and always placed the memberwise parameters
last. Personally, I believe the placeholder adds clarity and really liked
the idea when Chris suggested it.

Aside from personal preference, I like that this proposal introduces a
“synthesized parameter placeholder” syntax. Similar syntax will also be
used in the parameter forwarding proposal mentioned in the future
enhancements section of this proposal.

I think the `…` works really well. That said, I don’t mind if people wish
to bikeshed on it. If that discussion starts it is worth noting that one
thing I like about the `…` is that it combines with an identifier cleanly.
For example : `…memberwise`.

Combining the placeholder with an identifier allows more than one
placeholder to be used in the same parameter list. For example, if you are
forwarding memberwise parameters exposed by a super init you might also
have `…super`.

That said, I don’t want the review thread to get distracted with
discussions around general parameter forwarding so please just consider
this as a preview of how this syntax might scale to future applications.
For now, lets keep this thread focused on the review of the current
proposal. :)

Matthew

On Wed, Jan 6, 2016 at 2:47 PM, Chris Lattner via swift-evolution < >> swift-evolution@swift.org> wrote:

Hello Swift community,

The review of "Flexible Memberwise Initialization" begins now and runs
through January 10th. The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0018-flexible-memberwise-initialization.md

Reviews are an important part of the Swift evolution process. All reviews
should be sent to the swift-evolution mailing list at

        https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the
review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review
through constructive criticism and, eventually, determine the direction of
Swift. When writing your review, here are some questions you might want to
answer in your review:

        * What is your evaluation of the proposal?

        * Is the problem being addressed significant enough to warrant a

change to Swift?

        * Does this proposal fit well with the feel and direction of

Swift?

        * If you have you used other languages or libraries with a

similar feature, how do you feel that this proposal compares to those?

        * How much effort did you put into your review? A glance, a

quick reading, or an in-depth study?

More information about the Swift evolution process is available at

        https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Chris

Review Manager

_______________________________________________

swift-evolution mailing list

swift-evolution@swift.org

https://lists.swift.org/mailman/listinfo/swift-evolution

--

*Alex Johnson | Engineering Lead*

*Quick Left, Inc. <https://quickleft.com/&gt;\*
*Boulder **|* *Denver* *|* *Portland** |** San Francisco*
1 (844) QL-NERDS
@nonsensery

<https://github.com/quickleft&gt; <Facebook;
<https://twitter.com/quickleft&gt; <https://instagram.com/quick_left/&gt;
<https://www.flickr.com/photos/quickleft&gt; <https://vimeo.com/quickleft&gt;

*What's it like to work with us? **TrainingPeaks, iTriage, and Ping
Identity share their stories in this short video** A Client's View
<https://vimeo.com/92286352&gt;\*\.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--

*Alex Johnson | Engineering Lead*

*Quick Left, Inc. <https://quickleft.com/&gt;\*
*Boulder **|* *Denver* *|* *Portland** |** San Francisco*

1 (844) QL-NERDS

@nonsensery

<https://github.com/quickleft&gt; <Facebook;
<https://twitter.com/quickleft&gt; <https://instagram.com/quick_left/&gt;
<https://www.flickr.com/photos/quickleft&gt; <https://vimeo.com/quickleft&gt;

*What's it like to work with us? **TrainingPeaks, iTriage, and Ping
Identity share their stories in this short video** A Client's View
<https://vimeo.com/92286352&gt;\*\.

Here's an even better example, since it matches some code I actually
already have in a production app. Assuming that the "..." placeholder
can go anywhere (which seems reasonable), I have extensions on CGRect
and friends that could use memberwise init instead to be written like

extension CGRect { memberwise init(..., roundedToScale: CGFloat) }

This method takes the components of a rect, as well as a scale, and it
properly rounds the components so that the rect falls along pixel
boundaries if displayed on a screen with the given scale.

-Kevin Ballard

···

On Thu, Jan 7, 2016, at 01:31 PM, Kevin Ballard wrote:

On Thu, Jan 7, 2016, at 07:12 AM, Matthew Johnson wrote:

As for my concern, it's with the following rule:

If the initializer body assigns to a var property that received
memberwise initialization synthesis report a warning. It is
unlikely that overwriting the value provided by the caller is the
desired behavior.

I understand why you put this in there, but this is a warning that
cannot be suppressed and will make it impossible to use a memberwise
initializer for perfectly legitimate cases where you do in fact want
to mutate the property after it's been assigned to.

For normal initializers I agree with you. However, I think it’s a
reasonable for callers to assume that if you expose a property via
memberwise initialization the post-initialization value will match
the value they provide. This warning is intended to alert you to the
fact that you are violating that reasonable assumption.

I think that's a reasonable assumption in many cases, but I don't like
the fact that the feature cannot be used at all in the rare case where
it actually makes sense to mutate the value.

Do you have an example of where you would want a caller to initialize
a property, but then overwrite the value they provide *during
initialization*?

Sure, how about something like a Rect type that always guarantees it's
in "standard" form (e.g. no negative sizes):

struct StandardRect { var origin: CGPoint var size: CGSize {
didSet { // ensure standardized form here } }

memberwise init(...) { if size.width < 0 { origin.x
+= size.width size.width = -size.width } if
size.height < 0 { origin.y += size.height
size.height = -size.height } } }

Or how about a struct that represents a URL request complete with
headers, and forces the inclusion of Content-Type:

struct URLRequest { var headers: [String: String] = [:] // ...
other properties here ... memberwise init(contentType: String, ...)
{ headers["Content-Type"] = contentType } }

As for my concern, it's with the following rule:

If the initializer body assigns to a var property that received memberwise initialization synthesis report a warning. It is unlikely that overwriting the value provided by the caller is the desired behavior.

I understand why you put this in there, but this is a warning that cannot be suppressed and will make it impossible to use a memberwise initializer for perfectly legitimate cases where you do in fact want to mutate the property after it's been assigned to.

For normal initializers I agree with you. However, I think it’s a reasonable for callers to assume that if you expose a property via memberwise initialization the post-initialization value will match the value they provide. This warning is intended to alert you to the fact that you are violating that reasonable assumption.

I think that's a reasonable assumption in many cases, but I don't like the fact that the feature cannot be used at all in the rare case where it actually makes sense to mutate the value.

Do you have an example of where you would want a caller to initialize a property, but then overwrite the value they provide during initialization?

Sure, how about something like a Rect type that always guarantees it's in "standard" form (e.g. no negative sizes):

struct StandardRect {
    var origin: CGPoint
    var size: CGSize {
        didSet {
            // ensure standardized form here
        }
    }
    
    memberwise init(...) {
        if size.width < 0 {
            origin.x += size.width
            size.width = -size.width
        }
        if size.height < 0 {
            origin.y += size.height
            size.height = -size.height
        }
    }
}

This is a good example. Thanks!

I think cases like this will be rare so I still think a warning is a good idea. Something like -Wno-overwrite-memberwise-init would allow it to be suppressed in cases where you actually do intend to do this. Would that satisfy you?

···

On Jan 7, 2016, at 3:31 PM, Kevin Ballard <kevin@sb.org> wrote:
On Thu, Jan 7, 2016, at 07:12 AM, Matthew Johnson wrote:

Or how about a struct that represents a URL request complete with headers, and forces the inclusion of Content-Type:

struct URLRequest {
    var headers: [String: String] = [:]
    // ... other properties here ...
    memberwise init(contentType: String, ...) {
        headers["Content-Type"] = contentType
    }
}

-Kevin Ballard

AFAIU with the current proposal I would have to write the following to give a let property a default value:

class C {
  var x: Int
  var y: Int
  let c: Int

  memberwise init(…, c: Int = 42) {
    self.c = c
  }
}

Did I understand this right?

No, this wouldn’t be possible. The let would receive a memberwise initialization parameter with no default. This code would declare a second parameter with the same label which would produce a compiler error. It would also attempt a second initialization of `self.c` which would also produce a compiler error.

The lack of a default for `let` properties is a very unfortunate limitation that I hope will be resolved. I attempted to solve it but could not come up with a solution Chris considered workable. I don’t think it is bad enough to reject the proposal altogether. The proposal is still a significant step forward from the existing memberwise initializer and it does not prevent you from writing manual initializers when necessary.

One way to tweak the proposal to allow the code above would be to suppress the synthesized memberwise initialization parameter instead of producing a compiler error. I didn’t choose that approach because it complicates both the model and the implementation further. I think that complication is unnecessary if we are able to solve the two critical (IMO) issues remaining with this proposal:

1. We need a way to specify a default value for memberwise parameters for `let` properties.
2. We need a little bit more control over which properties participate in memberwise initialization when the “automatic” rules don’t quite do the right thing for a particular use case.

Matthew

···

On Jan 8, 2016, at 3:33 AM, Thorsten Seitz <trsfoo@googlemail.com> wrote:

-Thorsten

Am 08.01.2016 um 04:37 schrieb Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

On Jan 7, 2016, at 8:55 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The proposal says that "let" properties with inline initializers should be left out of the memberwise initialization, AIUI on the grounds that a manually-written initializer would not be allowed to override the inline initialization:

Yes, this is because Chris insisted that the proposal be pure sugar for something that could be manually written. This ensures that anyone using memberwise initialization can stop using it in any case where that becomes necessary.

class C {
  let x = 1738
  init(x: Int = 679) {
    self.x = x // Error, self.x already initialized
  }
}

However, this is also true for vars. Semantically, if you change 'x' to a var in the above example, you get an initialization followed by an assignment:

class C {
  let x = dump(1738)
  init(x: Int = dump(679)) {
    self.x = x
  }
}

      C() // dumps 1738, then 679

which, if the initialization has side effects, will likely be surprising. We could say that the memberwise initializer elides the inline initialization of `var`s, on the grounds that initializations ought not to have side effects, but then we're introducing a behavior change in inline initializers for `var`s in the face of `memberwise` initializers that also cannot be replicated by a manually-written initializer. If we make that behavior change for vars, I think it's reasonable, and more orthogonal, to extend the same grace to lets as well. That also simplifies the rules for what appears in the memberwise initializer—there's now only two rules (or one, if we also remove the access control filter, as I've suggested in another subthread).

I agree. The dual assignment for `var` seems wasteful in addition to potentially surprising and the limitation for `let` is unfortunate. Of course the same can be said for all initializers that might wish to assign a different value to a `let` or do assign a different value to a `var`.

What you suggest is exactly how I wanted the proposal to work. Chris was opposed to this approach. I would be very happy with this change if you can sell Chris on it! I think many others would be as well.

In addition to this not being possible to implement manually, Chris explained to me that it is important to the optimizer to be able to assume a `let` with an inline initializer will never have any other value (especially when that inline initializer is a constant value). Allowing an instance member to be optimized away when initialized with a constant value enables it to be referenced with convenient syntax due to implicit `self`. It would have never occurred to me to make a true constant value an instance property, but apparently this is a common practice. And with a guaranteed optimization is makes sense to take advantage of the implicit `self`.

At the same time, IMO the inability to provide a default value for a property that is only used when it is not initialized manually is a more important issue. I wish the “inline initializer” worked this way.

I know the implementation would be a bit more complex - the compiler would have to analyze the body of non-memberwise initializers before synthesizing the inline-initialization if necessary. But it already emits an “uninitialized member” error message so it already knows which properties are initialized and which are not. That information may not be available in the right part of the compiler but we do not how to determine this.

This would solve the init / assign problem for `var` and the no overridable default problem for `let` in both existing and memberwise initializers. It would also mean that memberwise initializers can work this way while still being pure sugar for code that can be written manually. Unfortunately this seems like something that is not open for change.

Matthew

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

After reading both your response below and also the proposal rather carefully, I agree that the possible issues I raised are all either not real issues or already addressed; thanks again for crafting the proposal and also for taking the time to reply to so much feedback.

That being said, I can’t shake a feeling that, overall, although I am definitely in favor of something along the lines of this proposal, in its concrete details at present this proposal isn’t really sitting anywhere near even a local-optimum on the `(flexibility,complexity) -> functionality` surface, as it were; it seems like both of these are possible:

- (a) make it a bit more flexible, for a high gain in functionality at a low incremental cost in complexity
- (b) make it a bit less flexible, for a modest loss in functionality and a large drop in complexity

…but for (b) it’s just a feeling and I don’t have a specific proposal (this may well be close to a minimum-viable-proposal for such a feature).

I agree with you.

As for (b) some people have suggested alternatives with less functionality like David’s `self propName: PropType` suggestion. So far I don’t think any of those add enough value over current state to be worth considering.

For (a) my sense is that although I can understand why you don’t want to even provide the option of specifying an explicit memberwise-parameter list, it really does seem that supporting at least an optional list makes it possible to get a lot more functionality for not much more *actual* complexity; this isn’t incompatible with also supporting an “automatic” option that uses the logic from the proposal where possible.

In terms of (a), I definitely agree that it would be desirable to allow for a bit more functionality. That is why I have suggested several enhancements. I don’t think we want all of them, but some are definitely desirable.

Chris really wanted to start with a core proposal and evaluate the enhancements independently, which is a good idea but also makes the proposal slightly less flexible than desirable. The enhancements I feel are most important are:

1. We need a way to specify a default value for memberwise parameters for `let` properties.
2. We need a little bit more control over which properties participate in memberwise initialization when the “automatic” rules don’t quite do the right thing for a particular use case. I think allowing distinct access control for `init` is the best way to handle this.

You mention specifying an explicit parameter list but don’t really elaborate further. Would the opt-in model described in the proposal meet what you have in mind? Or are you also consider with parameter order being independent of property declaration order?

Your example below hints at general parameter forwarding. I’ll elaborate further down.

Here’s a concrete example to illustrate why I’m harping on this point; I apologize for the length, but I think “small-n” examples can often give false intuition into how things will behave in real life:

Thank you for this! I probably should have provided at least one more concrete example myself. I am learning from that mistake in the second draft of my next proposal.

class FancyCollectionViewDriver : NSObject, UICollectionViewDataSource, UICollectionViewDelegate /*, etc... */ {
  
  let collectionView: UICollectionView
  let contentPresentation: ContentPresentation
  let modelBroker: ModelBroker
  let imageBroker: ImageBroker
  let analyticsSink: AnalyticsSink
  private(set) var currentData: ModelData
  private(set) weak var interactionDelegate: DriverDelegateProtocol?
  // ^ can't be non-optional `unowned let` for reasons,
  // but we expect a non-nil argument in init
  // NOTE: numerous private state-tracking variables omitted since we are only focusing on initialization

  // Present-day initializer, full of boilerplate:
  required init(
    collectionView: UICollectionView,
    contentPresentation: ContentPresentation,
    modelBroker: ModelBroker,
    imageBroker: ImageBroker,
    analyticsSink: AnalyticsSink,
    // note use of different argument name:
    initialData: ModelData,
    // note use of non-optional:
    interactionDelegate: DriverDelegateProtocol) {
      // oh boy here we go again:
      self.collectionView = collectionView
      self.contentPresentation = contentPresentation
      self.modelBroker = modelBroker
      self.imageBroker = imageBroker
      self.analyticsSink = analyticsSink
      self.currentData = initialData
      self.interactionDelegate = interactionDelegate
      super.init()
      // only non-assignment logic in the entire init:
      self.collectionView.dataSource = self
      self.collectionView.delegate = self
    }
    
    // best we can do under proposal w/out modifying
    // class design:
    required memberwise init(
    // lots of boilerplate gone:
    ...,
    // this isn't changed:
    initialData: ModelData,
    // this isn't changed:
    interactionDelegate: DriverDelegateProtocol) {
      // synthesized stuff is effectively here
      self.currentData = initialData
      self.interactionDelegate = interactionDelegate
      super.init()
      // only non-assignment logic in the entire init:
      self.collectionView.dataSource = self
      self.collectionView.delegate = self
    }
  
}

…which I do think is already a huge improvement.

And gets even better if we allow access control for init. Your `private(set)` members would now become eligible for memberwise initialization in an `internal` initializer because `init` has a distinct access level which defaults to `internal`.
    
    required memberwise init(...) {
      // synthesized stuff is effectively here
      super.init()
      // only non-assignment logic in the entire init:
      self.collectionView.dataSource = self
      self.collectionView.delegate = self
    }

Now suppose that stored-properties-in-extensions hits (as the "partial-init" flavor); in that case I’d ideally be able to move some of the parts into their own files like so:

// in `FancyCollectionViewDriver+Analytics.swift`
extension FancyCollectionViewDriver {
  // ^ depending on advances in the protocol system, at some point may
  // evolve into a protocol-adoption to get useful default implementations
  
  let analyticsReporter: AnalyticsReporter
  // ^ moved here, not in main declaration
  // assume also a bunch of private state-tracking stuff...
  
  // a bunch of things like this:
  func reportEventTypeA(eventAInfo: EventAInfo)
  func reportEventTypeB(eventBInfo: BventAInfo)
  
}

// in `FancyCollectionViewDriver+Interaction.swift`
extension FancyCollectionViewDriver {
  
  private(set) var interactionDelegate: DriverDelegateProtocol?
  // ^ moved here, not in main declaration
  // assume also a bunch of private state-tracking stuff...
  
  // a bunch of things like this:
  func handleInteractionA(interactionAInfo: InteractionAInfo)
  func handleInteractionB(interactionBInfo: InteractionBInfo)
  
}

…(and so on for e.g. the `imageBroker` also), but under the current proposal this would put them outside the scope of a memberwise init (this isn’t news to you, I’m just making it concrete).

So in this scenario, we’re either reapproaching the MxN problem memberwise-init is meant to avoid:

init(
  // still save some boilerplate:
  …,
  imageBroker: ImageBroker,
  analyticsReporter: AnalyticsReporter,
  initialData: ModelData,
  interactionDelegate: DriverDelegateProtocol) {
  // some boilerplate synththesized here...
  // ...but getting closer to where we started:
  self.partial_init(imageBroker: imageBroker)
  self.partial_init(analyticsReporter: analyticsReporter)
  self.currentData = modelData
  self.partial_init(interactionDelegate: interactionDelegate)
  super.init()
  self.collectionView.dataSource = self
  self.collectionView.delegate = self
}

…or we’re making choices between taking full-advantage of properties-in-extensions (which IMHO would often be a *huge* readability win) versus taking full-advantage of boilerplate-reduction in our inits.

If extensions with stored properties are going to properly encapsulate their state (I believe this is the way to go) they are going to have to initialize state on their own and the primary initializer will have to provide arguments the initializer requires. Allowing stored properties in extensions automatically appear in a memberwise initializer violates that encapsulation.

The good news is that a general parameter forwarding mechanism can help if the extension init (or super init) requires a bunch of arguments and you just want to forward them from your caller. This is alluded to in the enhancement section of the proposal but is really a completely general and orthogonal feature.

It might look something like this:

memberwise init(…superArgs, …partial1args, …partial2args, …memberwise) {
  extension1.init(directArgument: 42, …partial1args)
  extension2.init(…partial2args)
  super.init(…superArgs)
}

A couple things to note about how this might work:

1. The initializer calls (or function / method calls in other cases) must be unambiguous from the code alone.
2. Any parameters for which a direct argument is not provided are “captured” by the `…identifier` placeholder.
3. Parameters matching those captured by the identifier are synthesized in the location specified in the parameter list.

Obviously this won’t help if the extensions only require a single argument. But if you have extensions requiring several arguments that you want to pass through it would help

Which is ultimately why I suspect that the “right" version of the proposed feature should cut to the chase and incorporate some way to explicitly-specify the memberwise parameter list — which, again, need not be incompatible with the ability to request automatic synthesis using logic ~ what’s in the proposal — as such an explicit list takes the pressure off of getting the default behavior as-right-as-possible while also making it simpler to support some very nice-to-have capabilities not supported by this proposal as-written.

How would this be different than the opt-in model described in the proposal? Do you have something specific in mind?

That’s my 2c; thanks to anyone who’s read through all this and thanks again for drafting a concrete-enough proposal to discuss properly.

Thanks for all your feedback and your example! I really appreciate it!

Matthew

···

On Jan 8, 2016, at 9:46 AM, plx via swift-evolution <swift-evolution@swift.org> wrote:

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

On Jan 7, 2016, at 9:02 AM, plx via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I like the general notion of something along these lines but admittedly haven’t had time to dig into the proposal specifics yet.

I have some concerns about cross-interactions with other features that are either also under discussion or are at least very anticipatable.

First, I know there has already been some discussion of allowing definition of stored fields in (some) extensions (e.g., something like allowing definition of stored fields in extensions within the module that defines the type).

E.G., something like this may become possible (assume all files are compiled together):

  // in `ComplicatedClass.swift`
  class ComplicatedClass {
    let text: String

    // how will this get expanded,
    // given the extensions below?
    memberwise init(...)
  }

  // in `ComplicatedClass+Foo.swift`
  extension ComplicatedClass {
    var fooData: Foo? = nil
    // presumably stuff-involving-`fooData`
  }

  // in `ComplicatedClass+Bar.swift`
  extension ComplicatedClass {
    var barData: Bar = Bar.standardBar
    // presumably stuff-involving-`barData`
  }

It doesn't seem impossible to specify how the memberwise-initialization would interact with constructs like the above, but I'd worry a bit about it making a feature that's already looking *rather* complicated even more so.

Especially since, if I had to pick just one, I'd think the ability to define stored properties outside the initial definition is a bigger win than a nice memberwise-initialization construct, even though both seem handy.

I followed the stored-properties-in-extensions discussion reasonably closely. My understanding is that the extension will need to initialize its own properties, either with an initial value or with a `partial init`. Designated initializers would be required to call the `partial init` for any extension that defines one.

This being the case, memberwise initialization would not directly interact with this feature at all. Memberwise initializers declared in the main body of type itself would only expose stored properties defined in the type itself.

It would also be possible to support `partial memberwise init` in extensions which would expose the stored properties declared in the extension as part of a partial initializer.

I don’t think there are difficult complications here.

Secondly, I’m a bit unsure how this will interact with e.g. the property-behavior proposal if both wind up ratified. For `lazy`, the interaction with `memberwise` is easy — it is omitted from the list — but when you get into e.g. something like a hypothetical `logged` or `synchronized` or `atomic` — wherein there is custom behavior, but the field would still need initialization — you’d want them to be included in the
`memberwise` init.

My thought here is that a behavior would define whether a property allows and / or requires initialization in phase 1 or not. This is probably necessary independent of memberwise initialization. Properties that allow or require phase 1 initialization would be eligible for memberwise initialization. Properties that don’t allow phase 1 initialization would not be eligible for memberwise initialization.

It’s a bit unfair to bring up another proposal, but this proposal and something like the property-behavior proposal *would* need to work well together (if both are approved).

Agreed. That is why there is a rule that references property behaviors in the proposal.

Thirdly, I’m not sure what the current plans are (if any) for users to be able to specify the precise memory-layout of a struct; apologies if this is already a feature, I simply haven’t looked into it.

**Today**: I order stored-field declarations for ease-of-reading (e.g. grouped into logical groups, and organized for ease-of-reading).

**Under Proposal**: I sometimes will get to choose between the “ease-of-reading” declaration ordering and the “cleanest-reading memberwise init” declaration ordering. These may not always be identical.

Agree. This is something that could be addressed in a future enhancement if necessary. This proposal is focused on the basic mechanism.

Also, nothing in the proposal prevents you from continuing to write a manual initializer when the synthesized initializer will not do what you require. If you are already explicitly restating the property identifiers to specify parameter order you are already half way to a manual initializer implementation.

Granted, if you need more than one memberwise initializer you would have to duplicate that effort. But re-ordering is going to have a hard time providing enough value if the basic feature does what we need in the majority of cases.

**Future?**: I may have to choose between the “ease-of-reading” declaration ordering, the “cleanest-reading member wise init” declaration ordering, and (perhaps?) the “intended memory-layout” declaration ordering.

I don’t want to make this proposal more-complicated than it already is, but I worry a bit about having too many things impacting the choice of how to order declarations in source files; it may be better to include a way to explicitly declare the ordering-for-memberwise:

E.G., some way of explicitly indicating the memberwise ordering, perhaps like this:

  // syntax example re-using `ComplicatedClass`
  class ComplicatedClass {
    @memberwise($parameterList)
    // ^ can use just @memberwise to get default ordering + the defaults from
    // the property declarations, but perhaps require the explicit listing
    // whenver the ordering is not well-defined (e.g. if you have properties
    // declared in extensions…then you need to order it yourself)
    //
    // @memberwise(text="Example",barData=,fooData)
    // - `text="Example"` => memberwise init has text="Example"
    // - `barData=` => memberwise init has `barData` w/out default
    // - `fooData` => memberwise init has `fooData` w/default if it has one
    //
    // …and e.g. the above would make:
    //
    // memberwise init(...)
    //
    // ...expand-to:
    //
    // init(text:String = "Example", barData: Bar, fooData:Foo?=nil)
    //
    // ...and with the @memberwise declaration supporting a `...` for `super`
    // placement, like so:
    //
    // // superclass members come before:
    // @memberwise(...,)
    // @memberwise(...,$parameterList)
    //
    // // superclass members come after
    // @memberwise(,...)
    // @memberwise($parameterList,...)
    //
    // ...perhaps with tweaked syntax (`@memberwise(...,$)` or `@memberwise(...,self)`)
    // to be bit easier to read when you don't have an explicit parameter list?
  }

...which of course potentially only-further complicates the feature in some ways, but avoids having this use of this feature *necessarily* impact how one might choose to order declarations?

On Jan 6, 2016, at 4:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello Swift community,

The review of "Flexible Memberwise Initialization" begins now and runs through January 10th. The proposal is available here:

  https://github.com/apple/swift-evolution/blob/master/proposals/0018-flexible-memberwise-initialization.md

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  * What is your evaluation of the proposal?
  * Is the problem being addressed significant enough to warrant a change to Swift?
  * Does this proposal fit well with the feel and direction of Swift?
  * If you have you used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

  https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

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

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

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

Actually it is internal, not private, and it exposes private properties via that internal initializer. It’s only in your own code, but I don't think it should be violating access control in that way.

Not for me in Xcode 7.2. Has this changed? Maybe it’s my app target? The implicit init() is only visible for me within the same file the struct is defined in.

The proposal essentially is doing two things:

    1. Create a way to generate a public API contract based on a series of fairly complicated rules, potential annotations, and member orderings within the type

Would you feel better about the proposal if it did not allow for public memberwise initializers?

Marginally. My main concern is the complexity of the rules, especially when looking at the direction many of the futures take. There are all of these annotations that get put all over that litter the type definition simply to support memberwise inits.

    2. Generate the pass through of assignment for the parameters of the init() and their backing values.

The vast amount complexity comes from trying to solve #1.

This is not true. I would still want access control enforced even if memberwise initializers could not be public.

There’s no concern with access control as it’s explicitly handled. I can expose whatever I want as the API and route it however I want in code.

As for this:

(Aside, a small nitpick, but it really bugs me: initialization has O(M+N) complexity, not O(M×N) complexity. One doesn’t initialize every member with every parameter.)

MxN is members x initializers.

Paul, this has also bugged me too; I do not see how it is accurate. There aren’t N initializers, there is one initializer that must fully instantiate the type. Each type may have additional convenience initializers, but these are not required. Further, they cannot make use of the placeholder. There is a “futures” that talks about it, but that’s out of scope of the original proposal.

Swift allows more than one designated initializer. N never large but it more than 1 in enough cases to matter. The point is that it results in enough boilerplate that I believe it affects how many people design their initializers.

Sure, but your base proposal does not allow a way for there to be more than one memberwise designated initializer. So in the base case, your proposal doesn’t solve what you call the MxN problem. In the futures, you describe ways to annotate inits() so that members aren’t considered in the signature.

struct Point {
    let x: Int
    let y: Int
    let z: Int
    
    @nomemberwise(y, z)
    memberwise init(...) {
        y = 0
        z = 0
    }

    @nomemberwise(z)
    memberwise init(...) {
        z = 0
    }

    memberwise init(...) {}
}

Let’s say that for simplicity the @nomemberwise() attribute takes a list of parameters. This is one version of the code that can be written to support zero-ing out by default.

Or, we can do this (what Swift has today):

struct Point {
    let x: Int
    let y: Int
    let z: Int
    
    init(x: Int, y: Int = 0, z: Int = 0) {
        self.x = x
        self.y = y
        self.z = z
    }
}

Or with the below example:

struct Point {
    let x: Int
    let y: Int
    let z: Int
    
    init(self x: Int, self y: Int = 0, self z: Int = 0) {}
}

Even in all of your futures, I don’t see how you fix the "MxN problem" for let without bringing back the assignment in the type declaration, which was generally not well received.

Your example hear illustrates the complexity (slightly modified from your proposal’s usage):

struct Foo {
  let bar: String
  let bas: Int
  let baz: Double

  init(self bar: String, self bas: Int, bax: Int) {
    // self.bar = bar synthesized by the compiler
    // self.bas = bas synthesized by the compiler
    self.baz = Double(bax)
  }
}

This approach has been mentioned quite a few times. It results in a lot of duplication without saving a lot. This is especially true if you have a lot of properties and more than one designated initializer that could use memberwise initialization .

IMO, if we were going to take this approach we should at least be able to omit redundant type information and default values for `var` properties where they exist. At least then we are saving as much as we can under this approach.

struct Foo {
  let bar: String
  let bas: Int
  let baz: Double

  init(self bar, self bas, bax: Int) {
    // self.bar = bar synthesized by the compiler
    // self.bas = bas synthesized by the compiler
    self.baz = Double(bax)
  }
}

I think it keeps coming up because it’s far simpler. While there is duplication in the type signature, the code is still smaller, more flexible, and more applicable than being limited to only initialization. Further, and I think this is what is far more important, I only need to look at single place to understand what is going on with initialization for any particular call. I don’t need to find out what members are annotated, or create the list of head of members and exclude certain ones if @nomemberize() is used. Each member being initialized as a configuration entity from the user is right there, no questions asked.

-David

···

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

Actually it is internal, not private, and it exposes private properties via that internal initializer. It’s only in your own code, but I don't think it should be violating access control in that way.

Not for me in Xcode 7.2. Has this changed? Maybe it’s my app target? The implicit init() is only visible for me within the same file the struct is defined in.

Wow, you’re right. It is enforcing access control. The implicit init is internal if there are no private properties, but private if there are private properties. I wonder if this has changed. If not, I’m embarrassed that I didn’t understand the current behavior in detail.

I thought it was always internal. I’ve never actually used it for a struct with private properties before and I think the docs all say internal so maybe that is why. In any case, I’m glad to see that it is enforcing access control rules.

The proposal essentially is doing two things:

    1. Create a way to generate a public API contract based on a series of fairly complicated rules, potential annotations, and member orderings within the type

Would you feel better about the proposal if it did not allow for public memberwise initializers?

Marginally. My main concern is the complexity of the rules, especially when looking at the direction many of the futures take. There are all of these annotations that get put all over that litter the type
definition simply to support memberwise inits.

Maybe I wasn’t clear enough, but I never intended for all of them to be accepted. They are intended to show different ways to allow a bit more control.

The proposal only changes current state in a few ways:

1. Allow the memberwise initializer to be used in classes
2. Allow default parameter values for `var` properties
3. Fix the problem the current memberwise initializer has with lazy properties
4. Use the `set` rather than `get` visibility for `var` properties
5. Allow you to request the memberwise initializer, including:
  i. Specify access level, which will result in omission of memberwise parameters for more-private properties
  ii. Add additional parameters
  iii. include an initializer body

Are there specific changes in this list that you dislike?

    2. Generate the pass through of assignment for the parameters of the init() and their backing values.

The vast amount complexity comes from trying to solve #1.

This is not true. I would still want access control enforced even if memberwise initializers could not be public.

There’s no concern with access control as it’s explicitly handled. I can expose whatever I want as the API and route it however I want in code.

I understand that this is true with your approach. I was responding to your statement that the source of complexity in my proposal is from trying to allow public memberwise inits (your solve #1). It doesn’t come from wanting to allow public inits. It mainly comes from enforcing access control. It turns out that the current implicit init is already doing that.

As for this:

(Aside, a small nitpick, but it really bugs me: initialization has O(M+N) complexity, not O(M×N) complexity. One doesn’t initialize every member with every parameter.)

MxN is members x initializers.

Paul, this has also bugged me too; I do not see how it is accurate. There aren’t N initializers, there is one initializer that must fully instantiate the type. Each type may have additional convenience initializers, but these are not required. Further, they cannot make use of the placeholder. There is a “futures” that talks about it, but that’s out of scope of the original proposal.

Swift allows more than one designated initializer. N never large but it more than 1 in enough cases to matter. The point is that it results in enough boilerplate that I believe it affects how many people design their initializers.

Sure, but your base proposal does not allow a way for there to be more than one memberwise designated initializer. So in the base case, your proposal doesn’t solve what you call the MxN problem. In the futures, you describe ways to annotate inits() so that members aren’t considered in the signature.

My proposal does allow for more than 1 memberwise init. The idea is that there are different ways to init the private state. It also allows memberwise inits with different access control, which would possibly receive different memberwise parameters due to the access control rules.

struct Point {
    let x: Int
    let y: Int
    let z: Int
    
    @nomemberwise(y, z)
    memberwise init(...) {
        y = 0
        z = 0
    }

    @nomemberwise(z)
    memberwise init(...) {
        z = 0
    }

    memberwise init(...) {}
}

Let’s say that for simplicity the @nomemberwise() attribute takes a list of parameters. This is one version of the code that can be written to support zero-ing out by default.

Or, we can do this (what Swift has today):

struct Point {
    let x: Int
    let y: Int
    let z: Int
    
    init(x: Int, y: Int = 0, z: Int = 0) {
        self.x = x
        self.y = y
        self.z = z
    }
}

Or with the below example:

struct Point {
    let x: Int
    let y: Int
    let z: Int
    
    init(self x: Int, self y: Int = 0, self z: Int = 0) {}
}

Even in all of your futures, I don’t see how you fix the "MxN problem" for let without bringing back the assignment in the type declaration, which was generally not well received.

Your example hear illustrates the complexity (slightly modified from your proposal’s usage):

struct Foo {
  let bar: String
  let bas: Int
  let baz: Double

  init(self bar: String, self bas: Int, bax: Int) {
    // self.bar = bar synthesized by the compiler
    // self.bas = bas synthesized by the compiler
    self.baz = Double(bax)
  }
}

This approach has been mentioned quite a few times. It results in a lot of duplication without saving a lot. This is especially true if you have a lot of properties and more than one designated initializer that could use memberwise initialization .

IMO, if we were going to take this approach we should at least be able to omit redundant type information and default values for `var` properties where they exist. At least then we are saving as much as we can under this approach.

struct Foo {
  let bar: String
  let bas: Int
  let baz: Double

  init(self bar, self bas, bax: Int) {
    // self.bar = bar synthesized by the compiler
    // self.bas = bas synthesized by the compiler
    self.baz = Double(bax)
  }
}

I think it keeps coming up because it’s far simpler. While there is duplication in the type signature, the code is still smaller, more flexible, and more applicable than being limited to only initialization. Further, and I think this is what is far more important, I only need to look at single place to understand what is going on with initialization for any particular call. I don’t need to find out what members are annotated, or create the list of head of members and exclude certain ones if @nomemberize() is used. Each member being initialized as a configuration entity from the user is right there, no questions asked.

How is this more applicable than just for initialization? Are you suggesting a self parameter be allowed in any method and it would result in an immediate set of that property?

Your critique regarding clarity is very reasonable and it is definitely the biggest drawback with the automatic model. A number of people have commented on both the complexity / lack of clarity as well as the limitations of the automatic model. The opt-in model described in the proposal has some of the same issues. I have started to think more about an opt-in model that would avoid all of those issues.

I really appreciate the feedback and conversation. I am continuing to think about all of the comments and am hoping we can come out of this review with a vision for a path forward that makes most people happy whether the proposal is accepted or not. (I think it’s clear that no solution will make everyone happy but do hope we can hit a sweet spot)

Matthew

···

On Jan 8, 2016, at 2:09 PM, David Owens II <david@owensd.io> wrote:

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

I can’t shake unease about the proposed solution. As I read the examples, they’re not quite self-explanatory: a lot of magic, but the result doesn’t feel quite magical. Without being able to see what the compiler synthesizes, it’s often not obvious what will happen. As I read the detailed rules, they all sound quite sensible, but taken together feel like they’ll be hard to keep track of, and will lead to a lot of frustrated experimenting with the compiler. Tricky questions lurk all over. For example, what happens when I have a 7-member struct, all 7 parameters use memberwise initialization, but then I want to add some custom logic for the initialization of member 3? I think I have to either reorder the params or abandon … altogether? I feel like those tricky questions should melt away once I grasp the underlying principle, but there isn’t one to grasp; it’s just a bunch of tricky cases.

On reflection, it comes down to this: the feature to functionality ratio is too high.

Would you propose removing the current implicit memberwise initializer for structs on the same grounds?

No, it’s a much smaller feature surface. I would proposed promoting it from a simple, situational feature to something very generic — much like what Joe Groff is doing with “lazy.”

How exactly would you do this? I don’t understand what you think that would look like. Making it more capable is what my proposal is attempting to do so I am confused by this statement. This proposal changes it in the following ways:

1. Allow the memberwise initializer to be used in classes
2. Allow default parameter values for `var` properties
3. Fix the problem the current memberwise initializer has with lazy properties
4. Use the `set` rather than `get` visibility for `var` properties
5. Allow you to request the memberwise initializer, including:
  i. Specify access level, which will result in omission of memberwise parameters for more-private properties
  ii. Add additional parameters
  iii. include an initializer body

This proposal effectively fleshes that feature out giving it more functionality. The only fundamental complexity it adds is the access control rules, which I feel are pretty important to enforce.

I tend to agree with others who think the additional complexity in this proposal is substantial. Perhaps it would come to seem simple if we all lived with it, though that’s not my gut reaction. It’s a lot of hidden rules.

See the list above. I don’t think the behavior changes beyond the implicit memberwise init as they are perceived.

It feels to me like this functionality should come from a feature set that is more general, more arbitrarily composable, and pays greater dividends in a wider variety of situations. As a simple example, what if I want to write an updateFrom(other: Self) method that does a mass copy of all properties? Why doesn’t this proposal help with that, too? Because the … placeholder and the synthesized copying are tightly coupled (1) to each other and (2) to initialization.

I’m not sure what the better answer is, but it’s out there. I didn’t follow the whole discussion, but I did notice Joe Groff’s proposal for a Members tuple; that seems to me to be getting much warmer. I’d much prefer something along those lines, even if it were slightly more verbose.

I think the direction suggested by Joe (and Dave) is interesting. But they haven’t explained how it would handle some important use cases this proposal addresses (default parameter values, subset of members without using a struct, etc). If we are going to reject this proposal in hope of a more general solution I would at least like to see a path forward that might be able to address these use cases.

Agreed — I think this proposal has tremendous value at the very least as an in-depth exploration of all the cases to consider in searching for a more general solution.

I do have some new ideas in the direction they discussed. I’m going to work on fleshing those out tonight.

More importantly, the general features on their own would not address the problems addressed by this proposal. There would still need to be initializer-specific magic. Joe hinted at what that might be but has not fleshed out all the details yet. Maybe it would be a simpler model but we would need to see more specific details.

I don’t believe a fully generalized solution is possible. There are a lot of initialization-specific constraints that must be met (definitive initialization, single initialization of `let` properties, etc).

As I said in the original review, I’d be willing to sacrifice some concision in service of making the solution more general.

For example, the proposal goes to lengths to (1) automatically select a subset of members for memberwise initialization and (2) automatically insert the initialization code. I’d be willing to sacrifice both those implicit behaviors for some more generically composable mechanisms that let me turn a (sub)set of members into a tuple type, add it to arg lists, and mass assign it.

Here’s a sketch of that — not a proposal, total BS syntax, totally hypothetical:

    struct S {
        let s0, s1, s2: String
        private let i: Int

        init(anInt: Int, anotherInt: Int, otherArgs: Members.except(i)) {
            members = otherArgs // assigned members inferred from tuple item names
            i = anInt > anotherInt ? anInt : anotherInt
        }
    }

I’d be happy — happier! — with a solution like that, despite the modest additional keystrokes, because (1) members and Members would presumably have a more predictable behavior that’s easier to remember and to understand by reading, and (2) they’d be usable in other contexts:

    mutating func updateFrom(other: S) {
        self.members = other.except(i)
        i = anInt > anotherInt ? anInt : anotherInt
    }

…or heck, even this:

    mutating func updateTwoStrings(s0: String, s1: String) {
        members = arguments
    }

    mutating func updateTwoStrings(s0: String, s1: String, message: String) {
        print(message)
        members = arguments.except(message)
    }

OK, I concede I'm now brainstorming quite a feature list here:

members property that turns all (stored?) properties into a tuple,
Members property that returns the type of the above,
select / except operations on any tuple that create a new tuple by filtering keys,
assignment of a tuple to members that matches by tuple key (and the tuple can contain a subset of all properties),
some way of variadically expanding a tuple type in an arg list, and
arguments implicit variable that gives all func args as a tuple. (That last one’s not necessary to replace this proposal; just threw it in there because I’m brainstorming.)

That’s a lot! But all these feature are more independent, flexible, and transparent than the ones in the proposal. They (1) need not all be understood all at once, (2) have less implicit behavior and rules about corner cases, (3) thus have a simpler mental model and are easier to understand just by reading, and (4) provide more capabilities in a broader range of contexts.

I really don’t think the “members” computed tuple property is a workable solution for initializing `let` properties. It would be really strange to allow it to do so. Any such property that was allowed to do so would not work outside an initializer anyway as it would try to mutate a `let` when you used it.

I have also been thinking a lot about approaches that would be similar in some sense and build on a general purpose parameter forwarding mechanism. I have some ideas that I am going to work on fleshing out tonight.

···

On Jan 8, 2016, at 4:14 PM, Paul Cantrell <cantrell@pobox.com> wrote:

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

On Jan 8, 2016, at 11:03 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Again, it’s only a sketch. Just making stuff up here! The obvious question is “how exactly would it all work,” and I don’t know either — but I feel like it could, and I’d really like to pursue this sort of direction before going with the more narrow proposal at hand.

That said, I originally wrote:

I think it should be deferred in search of a more generic solution, perhaps to be resurrected if the search for generality fails.

I said “deferred” instead of “rejected” because my objection is that there may be a better solution — but that’s only a gut feeling, and if we really truly establish that there isn’t, then I’d give this proposal another look.

(Aside, a small nitpick, but it really bugs me: initialization has O(M+N) complexity, not O(M×N) complexity. One doesn’t initialize every member with every parameter.)

MxN is members x initializers.

Yes, as others pointed out, my careless misreading! Makes much more sense now. Sorry for that.

Cheers,

Paul

I like these ideas!

Some brain storming from me as well:

The members property should probably not be a tuple but a struct, thereby providing names for the values.
I think splitting this property into two (varMembers and letMembers) with a read-only property members tying both together for convenience (members.vars, members.lets).

I’ve thrown together a playground to toy with these ideas:

import Foundation

/*
struct S {
    var s0: String
    var s1: String = "s1"
    var s2: String = "s2"
    let j: Int default 42
    private let i: Int

    // implicit property #members
    var #members: Members {
  get { return Members(#vars, #lets) }
    }

    // implicit property #vars
    var #vars: Members.Vars

    // implicit property #lets
    let #lets: Members.Lets

    init(members: Members) {
        #members = members
    }

    init(anInt: Int, anotherInt: Int, members: Members) {
        #members = members
        i = anInt > anotherInt ? anInt : anotherInt
    }

    mutating func updateFrom(other: S) {
        self.#members.vars = other.#members.vars
    }
}
*/

// faking the above
struct S {
    struct Members {
        struct Vars {
            var s0: String?
            var s1: String
            var s2: String
        }
        struct Lets {
            var j: Int
            private var i: Int?
        }
        var vars: Vars
        var lets: Lets
        init(vars: Vars, lets: Lets) {
            self.vars = vars
            self.lets = lets
        }
        init(s0: String? = nil, s1: String = "s1", s2: String = "s2", j: Int = 42, i: Int? = nil) {
            self.vars = Vars(s0: s0, s1: s1, s2: s2)
            self.lets = Lets(j: j, i: i)
        }
    }
    // having separate varMembers and letMembers allows to ensure
    // that letMembers cannot be changed
    // (this would not be possible with "var members: Members")
    var varMembers: Members.Vars
    let letMembers: Members.Lets
    
    var members: Members {
        get { return Members(vars: varMembers, lets: letMembers) }
    }
    
    var s0: String {
        get { return varMembers.s0! }
        set { varMembers.s0 = newValue }
    }
    
    var s1: String {
        get { return varMembers.s1 }
        set { varMembers.s1 = newValue }
    }
    
    var s2: String {
        get { return varMembers.s2 }
        set { varMembers.s2 = newValue }
    }
    
    var j: Int {
        get { return letMembers.j }
    }
    
    private var i: Int {
        get { return letMembers.i! }
    }
    
    init(members: Members) {
        varMembers = members.vars
        letMembers = members.lets
    }
    
    init(anInt: Int, anotherInt: Int, members: Members) {
        varMembers = members.vars
        var lets = members.lets
        lets.i = anInt > anotherInt ? anInt : anotherInt
        letMembers = lets
    }
    
    mutating func updateFrom(other: S) {
        self.varMembers = other.varMembers
        // self.letMembers = other.letMembers // type error!
    }
}

extension S: CustomStringConvertible {
    var description: String {
        return "S(s0: \"\(s0)\", s1: \"\(s1)\", s2: \"\(s2)\", j: \(j), i: \(i))"
    }
}

var s: S = S(members: S.Members(s0: "foo", s1: "bar", s2: "baz", j: 1, i: 0))
//s.members.lets.i = 1 // type error!
var s2: S = S(anInt: 1, anotherInt: 2, members: S.Members(s0: "other", j: 2))
s.updateFrom(s2)
s.s0 = "new"
var ms = s.varMembers
ms.s0 = "broken?"
s.s0 // is still "new"
// s.members.vars.s0 = "broken??" // type error!
var s3 = S(members: s2.members)

-Thorsten

···

Am 08.01.2016 um 23:14 schrieb Paul Cantrell via swift-evolution <swift-evolution@swift.org>:

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

On Jan 8, 2016, at 11:03 AM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I can’t shake unease about the proposed solution. As I read the examples, they’re not quite self-explanatory: a lot of magic, but the result doesn’t feel quite magical. Without being able to see what the compiler synthesizes, it’s often not obvious what will happen. As I read the detailed rules, they all sound quite sensible, but taken together feel like they’ll be hard to keep track of, and will lead to a lot of frustrated experimenting with the compiler. Tricky questions lurk all over. For example, what happens when I have a 7-member struct, all 7 parameters use memberwise initialization, but then I want to add some custom logic for the initialization of member 3? I think I have to either reorder the params or abandon … altogether? I feel like those tricky questions should melt away once I grasp the underlying principle, but there isn’t one to grasp; it’s just a bunch of tricky cases.

On reflection, it comes down to this: the feature to functionality ratio is too high.

Would you propose removing the current implicit memberwise initializer for structs on the same grounds?

No, it’s a much smaller feature surface. I would proposed promoting it from a simple, situational feature to something very generic — much like what Joe Groff is doing with “lazy.”

This proposal effectively fleshes that feature out giving it more functionality. The only fundamental complexity it adds is the access control rules, which I feel are pretty important to enforce.

I tend to agree with others who think the additional complexity in this proposal is substantial. Perhaps it would come to seem simple if we all lived with it, though that’s not my gut reaction. It’s a lot of hidden rules.

It feels to me like this functionality should come from a feature set that is more general, more arbitrarily composable, and pays greater dividends in a wider variety of situations. As a simple example, what if I want to write an updateFrom(other: Self) method that does a mass copy of all properties? Why doesn’t this proposal help with that, too? Because the … placeholder and the synthesized copying are tightly coupled (1) to each other and (2) to initialization.

I’m not sure what the better answer is, but it’s out there. I didn’t follow the whole discussion, but I did notice Joe Groff’s proposal for a Members tuple; that seems to me to be getting much warmer. I’d much prefer something along those lines, even if it were slightly more verbose.

I think the direction suggested by Joe (and Dave) is interesting. But they haven’t explained how it would handle some important use cases this proposal addresses (default parameter values, subset of members without using a struct, etc). If we are going to reject this proposal in hope of a more general solution I would at least like to see a path forward that might be able to address these use cases.

Agreed — I think this proposal has tremendous value at the very least as an in-depth exploration of all the cases to consider in searching for a more general solution.

More importantly, the general features on their own would not address the problems addressed by this proposal. There would still need to be initializer-specific magic. Joe hinted at what that might be but has not fleshed out all the details yet. Maybe it would be a simpler model but we would need to see more specific details.

I don’t believe a fully generalized solution is possible. There are a lot of initialization-specific constraints that must be met (definitive initialization, single initialization of `let` properties, etc).

As I said in the original review, I’d be willing to sacrifice some concision in service of making the solution more general.

For example, the proposal goes to lengths to (1) automatically select a subset of members for memberwise initialization and (2) automatically insert the initialization code. I’d be willing to sacrifice both those implicit behaviors for some more generically composable mechanisms that let me turn a (sub)set of members into a tuple type, add it to arg lists, and mass assign it.

Here’s a sketch of that — not a proposal, total BS syntax, totally hypothetical:

    struct S {
        let s0, s1, s2: String
        private let i: Int

        init(anInt: Int, anotherInt: Int, otherArgs: Members.except(i)) {
            members = otherArgs // assigned members inferred from tuple item names
            i = anInt > anotherInt ? anInt : anotherInt
        }
    }

I’d be happy — happier! — with a solution like that, despite the modest additional keystrokes, because (1) members and Members would presumably have a more predictable behavior that’s easier to remember and to understand by reading, and (2) they’d be usable in other contexts:

    mutating func updateFrom(other: S) {
        self.members = other.except(i)
        i = anInt > anotherInt ? anInt : anotherInt
    }

…or heck, even this:

    mutating func updateTwoStrings(s0: String, s1: String) {
        members = arguments
    }

    mutating func updateTwoStrings(s0: String, s1: String, message: String) {
        print(message)
        members = arguments.except(message)
    }

OK, I concede I'm now brainstorming quite a feature list here:

members property that turns all (stored?) properties into a tuple,
Members property that returns the type of the above,
select / except operations on any tuple that create a new tuple by filtering keys,
assignment of a tuple to members that matches by tuple key (and the tuple can contain a subset of all properties),
some way of variadically expanding a tuple type in an arg list, and
arguments implicit variable that gives all func args as a tuple. (That last one’s not necessary to replace this proposal; just threw it in there because I’m brainstorming.)

That’s a lot! But all these feature are more independent, flexible, and transparent than the ones in the proposal. They (1) need not all be understood all at once, (2) have less implicit behavior and rules about corner cases, (3) thus have a simpler mental model and are easier to understand just by reading, and (4) provide more capabilities in a broader range of contexts.

Again, it’s only a sketch. Just making stuff up here! The obvious question is “how exactly would it all work,” and I don’t know either — but I feel like it could, and I’d really like to pursue this sort of direction before going with the more narrow proposal at hand.

That said, I originally wrote:

I think it should be deferred in search of a more generic solution, perhaps to be resurrected if the search for generality fails.

I said “deferred” instead of “rejected” because my objection is that there may be a better solution — but that’s only a gut feeling, and if we really truly establish that there isn’t, then I’d give this proposal another look.

(Aside, a small nitpick, but it really bugs me: initialization has O(M+N) complexity, not O(M×N) complexity. One doesn’t initialize every member with every parameter.)

MxN is members x initializers.

Yes, as others pointed out, my careless misreading! Makes much more sense now. Sorry for that.

Cheers,

Paul

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

Sorry to leave this over the weekend before getting back to you! Planning to respond here, then go catch up on the thread—a possibly questionable ordering of tasks, but one that ensures I will actually respond at all.

No problem. Thanks for responding!

- Given that these initializers are based on access control, is "public memberwise init(...)" different from "internal memberwise init(...)"? Can I put both in the same type?

If you have at least one internal property they would result in different signatures so would not be a duplicate declaration. The public memberwise init would obviously need to initialize the internal property somehow.

That's what I expected, just wanted to have it spelled out. Thanks.

It seems like a big, complicated feature that "does what you mean" only when it's first added and then becomes an impediment to any later change.

I can certainly sympathize with this. It is the big drawback of the automatic model. At the same time, this proposal builds on the existing memberwise init for structs and doesn’t really make the rules that much more complex. But it does make the feature more prevalent and visible, thus making it more important to understand the rules. And of course it could be argued that the current memberwise init is too complex as it is.

For the record, here is a concise list of how this proposal expands the current memberwise init:

1. Allow the memberwise initializer to be used in classes
2. Allow default parameter values for `var` properties
3. Fix the problem the current memberwise initializer has with lazy properties
4. Use the `set` rather than `get` visibility for `var` properties
5. Allow you to request the memberwise initializer, including:
  i. Add additional parameters
  ii. include an initializer body
  iii. Specify access level, which will result in omission of memberwise parameters for more-private properties (it would be reasonable to limit this to private and internal if concerns about allowing it to be public are a significant factor)

I am curious to hear your thoughts on which of these points are not desirable, and what your opinion is about the existing rules for the implicit memberwise init for structs.

I'm happy with 1, 2, and 3 as a separate proposal, or several separate proposals. (For 1 in particular, the restriction predates the access control model, but making it implicit would also interfere with inheriting designated initializers.) I'm not sure 4 is interesting enough, but sure.

Hmm, thought you had agreed with #4 in the discussion about that topic. That said, it is less important in the context of an `internal` or `private` implicit memberwise initializer.

The particular concern I have with 5 is that the most interesting use case to me is for 'public' (indeed, that's by far the majority of the requests for this we've seen in Radar, rdar://problem/18065955 <rdar://problem/18065955>), but that's also the most dangerous one if you have anything unusual going on: different access control, library evolution, etc. (i) and (ii) are mostly just extra features, but (iii) is where it gets weird.

I can certainly understand your concerns regarding `public` as well as (iii).

I’m hoping this proposal will be accepted even if some features are removed. Even if only 1-3 or 1-4 were accepted to improve the implicit memberwise initializer I would consider that a good thing.

(The next most common request related to this proposal is 1, memberwise inits for classes, mostly in simple cases. rdar://problem/16704095 <rdar://problem/16704095>)

For public memberwise inits, I think my discomfort ultimately comes down to "the order of stored properties in a class is not usually part of the class's ABI", and this makes it too easy to opt into that, and then too hard to explain which members and why. There's also the problem of wanting to tweak one parameter's behavior, and being unable to do that; having to build more features on top of this one to fix that seems like it's going in the wrong direction.

I hope you’ll take a look at the new proposals I submitted. I think they provide a better approach to providing this kind of control than the enhancements I included in this proposal. The Partial Initializers proposal might be the best one to look at first. It would effectively supersede the explicit memberwise initializers in this proposal with a more general purpose implementation.

I'm motivated to solve the tiny value struct case, the public C-like struct case, but I don't think I want this creeping into the interface of a public class. I'd rather go with the "annotation on parameters" solution (which would be a separate proposal, of course).

public struct Point {
  public var x: Double
  public var y: Double
  public init(self x: Double, self y: Double) {}
}

or

  public init(@assigned x: Double, @assigned y: Double) {}
  public init(self.x: Double, self.y: Double) {}
  // something else

I’ll ask you the same question I asked David about this approach: if the parameter must be explicitly related to a property for assignment, why should we repeat the type information (and default value information if it is a var)? Doesn’t it seem reasonable to omit the type?

I'm happy to come up with a syntax to omit the type and default value; I do still think the listing of parameters should be explicit. (And it should be obvious where it gets any type and default value from.)

Glad to hear you think we could omit the type and default value (but still allow for overriding the default value). I think as a sugar feature it needs to save as much as possible to pay its way.

Another idea along the lines of paying its way as much as possible: maybe if all parameters are “self” parameters you could specify that once at the function level rather than repeating it for every parameter. What do you think about that idea? Maybe something like an @self attribute:

@self public init(x, y) {}

I agree that it should be obvious where those come from - I think the type and default value would come from the property declaration. Is there anywhere else it could come from?

I think this feature could be useful. I just don’t see it as being a complete solution. It would combine with my new Partial Initializers proposal very nicely.

Matthew

···

On Jan 11, 2016, at 1:44 PM, Jordan Rose <jordan_rose@apple.com> wrote:

  * What is your evaluation of the proposal?

It’s a well-considered and well-written proposal. I agree with the semantics of memberwise initializers (+1 to adding a reasonable implicit memberwise initializer for classes, and the ability to use default arguments in that implicit memberwise initializer). However, I would prefer to accept the semantics as improvements to the creation of the implicit memberwise initializer, so it’s a -1 to the “memberwise” specifier and “…” placeholder syntax.

Since this has been mentioned a few times now I'd like to add that I would support that as well.

I'm not really sure enough yet as to how the reviews work, so I'll ask here:

Is it possible for a subset or modified version of a proposal to be accepted or would the proposal be rejected while asking for a reduced/modified follow-up proposal?

Also are proposals always accepted or rejected, or could a proposal be returned as "needs more work, submit again later" instead of a flat out rejection?

The core team can do any of the above, including accepting subsets of proposals, accepting a proposal with modification, sending a proposal back for revision to come through the process again, etc. In general, we’ll try to do the lowest-overhead thing that makes sense for Swift.

  - Doug

Thanks, that's great to hear!

I agree. I know that some parts of this proposal are more controversial than others, especially. Least controversial being the improvements to the implicit memberwise initializer.

I would much prefer to see a modified version of the proposal accepted than the whole thing rejected.

Matthew

···

Sent from my iPhone

On Jan 11, 2016, at 5:22 PM, Janosch Hildebrand via swift-evolution <swift-evolution@swift.org> wrote:

On 12 Jan 2016, at 00:18, Douglas Gregor <dgregor@apple.com> wrote:

On Jan 11, 2016, at 3:09 PM, Janosch Hildebrand <jnosh@jnosh.com> wrote:

On 11 Jan 2016, at 07:37, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:
On Jan 6, 2016, at 2:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

- Janosch

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

The latter I'm afraid.

I was just discussing this design space with Chris Willmore, who's been working on revamping how our function type model works. If we move to a multiple-argument model for functions rather than the current every-function-takes-a-tuple-argument model, then we will likely need at least limited support for packing and unpacking tuples from and to arguments in order to avoid regressing at argument forwarding use cases. However, even that limited packing/unpacking functionality might be enough to seriously consider a more general magic "members" property as an alternative.

-Joe

···

On Jan 7, 2016, at 8:28 AM, Dave Abrahams <dabrahams@apple.com> wrote:

Sent from my moss-covered three-handled family gradunza

On Jan 6, 2016, at 7:12 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Sent from my iPad

On Jan 6, 2016, at 8:46 PM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

Sent from my moss-covered three-handled family gradunza

On Jan 6, 2016, at 5:47 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 6, 2016, at 5:23 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 6, 2016, at 6:04 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 6, 2016, at 2:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello Swift community,

The review of "Flexible Memberwise Initialization" begins now and runs through January 10th. The proposal is available here:

  https://github.com/apple/swift-evolution/blob/master/proposals/0018-flexible-memberwise-initialization.md

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  * What is your evaluation of the proposal?

It’s okay.

  * Is the problem being addressed significant enough to warrant a change to Swift?

I’m lukewarm about that. I have never found writing out the initializers I want to be a significant burden, and I find my code is better when they’re explicit. Every new feature increases the language's complexity and surface area, and I fear this one is not going to pay its way.

  * Does this proposal fit well with the feel and direction of Swift?

Yes, but I worry that it may be too early to add it. Other features in this space, like truly generic variadics, may well obsolete anything we do today. I’m not sure we should be designing convenience features that are likely to overlap with more general features coming down the road unless the inconvenience is very painful… which I personally don’t find it to be.

It isn’t clear to me how generic variadics might obsolete the functionality of this proposal. Can you elaborate on that?

Not sure if this is exactly what Dave has in mind, but an idea that comes to mind: we could say that structs and classes have a magic "members" tuple and typealias:

struct Foo {
  var x, y: Int

  // Implicit members
  typealias Members = (x: Int, y: Int)
  var members: Members {
    get { return (x: x, y: y) }
    set { (x, y) = newValue }
  }
}

With plausible future features for forwarding tuples as arguments, then the memberwise initializer could be implemented like this:

      // Say that a parameter declared 'x...' receives a tuple of arguments labeled according to its type,
      // like '**x' in Python
init(members...: Members) {
  self.members = members
}

And I think all your other use cases could be covered as well.

That's exactly what I had in mind. Thanks, Joe!

Is there any chance of generic variadics along these lines being part of Swift 3? Or is this down the road further?

Meanwhile, back on the topic of the "..." placeholder.

For me, it boils down to:
The addition of the "memberwise" keyword and associated behavior would stand on it's own, without the inclusion of the "..." placeholder syntax. (IMO)
Omitting the "..." placeholder syntax now wouldn't prevent it from being added later.
If you agree with those statements, then the principle of "smallest, incremental change <Swift.org - Contributing; [swift.org <http://swift.org/&gt;\] seems to imply that the placeholder should be removed from this proposal.

That's my opinion. Does the rest of the community feel that the "..." placeholder is a necessary part of this proposal?

That’s a fair opinion and is how this worked in the first draft of the proposal. If folks want to bikeshed on this topic be my guest.

Of course I wouldn’t have changed the proposal if I didn’t think `…` was a good idea so you know where I stand. :)

···

On Jan 7, 2016, at 1:23 PM, Alex Johnson <ajohnson@quickleft.com> wrote:

On Wed, Jan 6, 2016 at 5:23 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 6, 2016, at 7:21 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 6, 2016, at 6:39 PM, Alex Johnson <ajohnson@quickleft.com <mailto:ajohnson@quickleft.com>> wrote:

Hi Matthew,

Thanks for the explanation.

Before getting into a deeper discussion, I'd like to try to enumerate the reasons for adding the placeholder as I understand them:
Does that seem accurate?

Add clarity by visually distinguishing memberwise initializers from normal initializers.
Introduce a "synthesized parameters placeholder" syntax that might be useful in other places.
Allow some control over where the synthesized memberwise parameters end up in the initializer signature.

This is mostly correct (in terms of my motivation - Chris may have additional reasons).

The point about clarity in regards to the `…` is about making it clear when looking at the signature that synthesized parameters are included in addition to those that are manually specified. This the most important point.

The `memberwise` declaration modifier on the initializer itself is what distinguishes memberwise initializers from other initializers.

Matthew

~ Alex

On Wed, Jan 6, 2016 at 3:48 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 6, 2016, at 5:26 PM, Alex Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

(this is mostly a repost of a message I sent to the "[draft]" thread for this proposal, with some light editing to better match terminology in the proposal)

What is your evaluation of the proposal?

I like this proposal. I think it will bring some much-needed ease-of-use.

I have reservations about the "..." placeholder for the memberwise initialization parameters, though. I know this was suggested by Chris Lattner, so I'm inclined to defer to his judgement. But, here are my thoughts:

First, it's very close to the varags syntax (e.g. "Int...") which can also appear in initializer parameter lists.

Second, and I think more important, I'm not sure that it's all that useful. It's presence isn't necessary for triggering memberwise initialization synthesis; that is already done by the "memberwise" keyword.

The primary example given in the proposal is:

memberwise init(anInt: Int, anotherInt: Int, ...) {
  /* code using anInt and anotherInt */
}

That is, it's used to indicate where the synthesized parameters appear in the parameter list if there are also custom (non-memberwise) parameters.

My question is, could the memberwise initialization parameters always be last? That would eliminate the need for the placeholder.

I don't think I've seen a compelling case for embedding the "..." within a list of custom arguments, like:

memberwise init(anInt: Int, ..., anotherInt: Int) {
  /* code using anInt and anotherInt */
}

It's been mentioned several times in the discussion of this proposal that this behavior is purely optional. If it turns out that there are rare cases where placing the memberwise params in the middle is useful, authors can use manual initialization.

Hi Alex, thanks for your review.

The initial draft of the proposal did exactly what you suggest - it did not include the placeholder and always placed the memberwise parameters last. Personally, I believe the placeholder adds clarity and really liked the idea when Chris suggested it.

Aside from personal preference, I like that this proposal introduces a “synthesized parameter placeholder” syntax. Similar syntax will also be used in the parameter forwarding proposal mentioned in the future enhancements section of this proposal.

I think the `…` works really well. That said, I don’t mind if people wish to bikeshed on it. If that discussion starts it is worth noting that one thing I like about the `…` is that it combines with an identifier cleanly. For example : `…memberwise`.

Combining the placeholder with an identifier allows more than one placeholder to be used in the same parameter list. For example, if you are forwarding memberwise parameters exposed by a super init you might also have `…super`.

That said, I don’t want the review thread to get distracted with discussions around general parameter forwarding so please just consider this as a preview of how this syntax might scale to future applications. For now, lets keep this thread focused on the review of the current proposal. :)

Matthew

On Wed, Jan 6, 2016 at 2:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello Swift community,

The review of "Flexible Memberwise Initialization" begins now and runs through January 10th. The proposal is available here:

        https://github.com/apple/swift-evolution/blob/master/proposals/0018-flexible-memberwise-initialization.md

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

        https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

        * What is your evaluation of the proposal?
        * Is the problem being addressed significant enough to warrant a change to Swift?
        * Does this proposal fit well with the feel and direction of Swift?
        * If you have you used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
        * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

        https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

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

--

Alex Johnson | Engineering Lead

Quick Left, Inc. <https://quickleft.com/&gt;
Boulder | Denver | Portland | San Francisco
1 (844) QL-NERDS
@nonsensery

<https://github.com/quickleft&gt; <Facebook; <https://twitter.com/quickleft&gt; <https://instagram.com/quick_left/&gt; <https://www.flickr.com/photos/quickleft&gt; <https://vimeo.com/quickleft&gt;

What's it like to work with us? TrainingPeaks, iTriage, and Ping Identity share their stories in this short video A Client's View <https://vimeo.com/92286352&gt;\.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
<>https://lists.swift.org/mailman/listinfo/swift-evolution

--

Alex Johnson | Engineering Lead

Quick Left, Inc. <https://quickleft.com/&gt;
Boulder | Denver | Portland | San Francisco
1 (844) QL-NERDS
@nonsensery

<https://github.com/quickleft&gt; <Facebook; <https://twitter.com/quickleft&gt; <https://instagram.com/quick_left/&gt; <https://www.flickr.com/photos/quickleft&gt; <https://vimeo.com/quickleft&gt;

<>
What's it like to work with us? TrainingPeaks, iTriage, and Ping Identity share their stories in this short video A Client's View <https://vimeo.com/92286352&gt;\.

Yes, I feel that the "..." placeholder is necessary. I cannot support a
proposal that results in

memberwise init() {}

because that initializer very clearly takes no arguments! The "..."
serves as a visual indicator for where the arguments go and I think
that's important.

Also, and though this isn't explicitly spelled out in the proposal it
seems like a reasonable assumption to make, the existence of "..." lets
me put it somewhere other than at the end of the arguments list, e.g.

memberwise init(..., foo: String) {}

-Kevin Ballard

Links:

  1. Swift.org - Contributing

···

On Thu, Jan 7, 2016, at 11:23 AM, Alex Johnson via swift-evolution wrote:

Meanwhile, back on the topic of the "..." placeholder.

For me, it boils down to:
1. The addition of the "memberwise" keyword and associated behavior
    would stand on it's own, without the inclusion of the "..."
    placeholder syntax. (IMO)
2. Omitting the "..." placeholder syntax *now* wouldn't prevent it
    from being added *later*. If you agree with those statements, then
    the principle of "smallest, incremental change[1]" [swift.org]
    seems to imply that the placeholder should be removed from this
    proposal.

That's my opinion. Does the rest of the community feel that the "..."
placeholder is a *necessary* part of this proposal?

No. It's not appropriate to have the only way to suppress a warning
on perfectly legal code to be passing a flag to the swiftc
invocation. Especially because we have no precedent yet for even
having flags like that.

What's wrong with the suggestion to make the warning behave the same way
as dead store warnings (e.g. warn if the property is overwritten without
any prior reads)? We already have logic for doing this kind of analysis.

-Kevin Ballard

···

On Thu, Jan 7, 2016, at 03:11 PM, Matthew Johnson wrote:

On Jan 7, 2016, at 3:31 PM, Kevin Ballard <kevin@sb.org> wrote:

On Thu, Jan 7, 2016, at 07:12 AM, Matthew Johnson wrote:

As for my concern, it's with the following rule:

If the initializer body assigns to a var property that received
memberwise initialization synthesis report a warning. It is
unlikely that overwriting the value provided by the caller is the
desired behavior.

I understand why you put this in there, but this is a warning that
cannot be suppressed and will make it impossible to use a
memberwise initializer for perfectly legitimate cases where you do
in fact want to mutate the property after it's been assigned to.

For normal initializers I agree with you. However, I think it’s a
reasonable for callers to assume that if you expose a property via
memberwise initialization the post-initialization value will match
the value they provide. This warning is intended to alert you to
the fact that you are violating that reasonable assumption.

I think that's a reasonable assumption in many cases, but I don't
like the fact that the feature cannot be used at all in the rare case
where it actually makes sense to mutate the value.

Do you have an example of where you would want a caller to
initialize a property, but then overwrite the value they provide
*during initialization*?

Sure, how about something like a Rect type that always guarantees
it's in "standard" form (e.g. no negative sizes):

struct StandardRect { var origin: CGPoint var size: CGSize {
didSet { // ensure standardized form here } }

memberwise init(...) { if size.width < 0 { origin.x
+= size.width size.width = -size.width } if
size.height < 0 { origin.y += size.height
size.height = -size.height } } }

This is a good example. Thanks!

I think cases like this will be rare so I still think a warning is a
good idea. Something like -Wno-overwrite-memberwise-init would allow
it to be suppressed in cases where you actually do intend to do this.
Would that satisfy you?

As for my concern, it's with the following rule:

If the initializer body assigns to a var property that received memberwise initialization synthesis report a warning. It is unlikely that overwriting the value provided by the caller is the desired behavior.

I understand why you put this in there, but this is a warning that cannot be suppressed and will make it impossible to use a memberwise initializer for perfectly legitimate cases where you do in fact want to mutate the property after it's been assigned to.

For normal initializers I agree with you. However, I think it’s a reasonable for callers to assume that if you expose a property via memberwise initialization the post-initialization value will match the value they provide. This warning is intended to alert you to the fact that you are violating that reasonable assumption.

I think that's a reasonable assumption in many cases, but I don't like the fact that the feature cannot be used at all in the rare case where it actually makes sense to mutate the value.

Do you have an example of where you would want a caller to initialize a property, but then overwrite the value they provide during initialization?

Sure, how about something like a Rect type that always guarantees it's in "standard" form (e.g. no negative sizes):

struct StandardRect {
    var origin: CGPoint
    var size: CGSize {
        didSet {
            // ensure standardized form here
        }
    }

    memberwise init(...) {
        if size.width < 0 {
            origin.x += size.width
            size.width = -size.width
        }
        if size.height < 0 {
            origin.y += size.height
            size.height = -size.height
        }
    }
}

This is a good example. Thanks!

I think cases like this will be rare so I still think a warning is a good idea. Something like -Wno-overwrite-memberwise-init would allow it to be suppressed in cases where you actually do intend to do this. Would that satisfy you?

No. It's not appropriate to have the only way to suppress a warning on perfectly legal code to be passing a flag to the swiftc invocation. Especially because we have no precedent yet for even having flags like that.

We will probably have warning flags eventually.

What's wrong with the suggestion to make the warning behave the same way as dead store warnings (e.g. warn if the property is overwritten without any prior reads)? We already have logic for doing this kind of analysis.

I missed that suggestion. This is probably sufficient.

···

Sent from my iPad

On Jan 7, 2016, at 5:41 PM, Kevin Ballard <kevin@sb.org> wrote:

On Thu, Jan 7, 2016, at 03:11 PM, Matthew Johnson wrote:

On Jan 7, 2016, at 3:31 PM, Kevin Ballard <kevin@sb.org> wrote:
On Thu, Jan 7, 2016, at 07:12 AM, Matthew Johnson wrote:

-Kevin Ballard

Do you have an example of where you would want a caller to initialize a property, but then overwrite the value they provide during initialization?

Sure, how about something like a Rect type that always guarantees it's in "standard" form (e.g. no negative sizes):

struct StandardRect {
    var origin: CGPoint
    var size: CGSize {
        didSet {
            // ensure standardized form here
        }
    }

    memberwise init(...) {
        if size.width < 0 {
            origin.x += size.width
            size.width = -size.width
        }
        if size.height < 0 {
            origin.y += size.height
            size.height = -size.height
        }
    }
}

This is a good example. Thanks!

Actually I do not like this example for several reasons: (1) I would make the rectangle an immutable type with let properties, (2) the didSet already seems to do what is encoded in the memberwise init, so this seems to be redundant, (3) the memberwise init is so complex that having the automatic initialization feature is not really worth it for this example, especially as it seems to require using var properties instead of let properties to do the overwriting.

I think cases like this will be rare so I still think a warning is a good idea. Something like -Wno-overwrite-memberwise-init would allow it to be suppressed in cases where you actually do intend to do this. Would that satisfy you?

No. It's not appropriate to have the only way to suppress a warning on perfectly legal code to be passing a flag to the swiftc invocation. Especially because we have no precedent yet for even having flags like that.

What's wrong with the suggestion to make the warning behave the same way as dead store warnings (e.g. warn if the property is overwritten without any prior reads)? We already have logic for doing this kind of analysis.

I think this would not be sufficient, because this would not allow overwriting a property based on the value of another property which might be necessary as well.
Actually isn’t this what happens in your example? The property origin is overwritten without being read, so this would generate the warning, or did I understand something wrong?

-Thorsten

···

Am 08.01.2016 um 00:41 schrieb Kevin Ballard via swift-evolution <swift-evolution@swift.org>:
On Thu, Jan 7, 2016, at 03:11 PM, Matthew Johnson wrote:

On Jan 7, 2016, at 3:31 PM, Kevin Ballard <kevin@sb.org <mailto:kevin@sb.org>> wrote:
On Thu, Jan 7, 2016, at 07:12 AM, Matthew Johnson wrote:

-Kevin Ballard

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