[Review] SE-0018 Flexible Memberwise Initialization

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!

···

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:

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

···

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 <mailto:ajohnson@quickleft.com>> wrote:

~ 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

Meta: most reviews have the review dates in the Status field of their document, this one doesn't. I was a little confused at first.

For the proposal itself:

# What is your evaluation of the proposal?

+1

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

Yes, I write a lot of code just to initialize code. This will probably make some of that code clearer.

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

Probably?

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

I frequently write C++ and Python and neither of them have it. This is especially annoying for short programs.

# C# has an initializer syntax that allows you to assign properties at creation time, and it is handy:

Foo foo = new Foo() {
  Bar = "bar",
  Baz = 1,
};

However, this pattern wouldn't be of much help in Swift, where most types don't have a reasonable default value.

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

I followed the first week and a half of the proposal.

With all this said, I am confused with the way the proposal is written. The Proposed Solution section talks about opt-in memberwise initialization with the memberwise modifier on properties, but it is listed as a future direction and the Detailed Design makes no mention of it. I would like to be clear that my current appreciation of the proposal only extends to the Detailed Design, and not necessarily to the future directions.

Felix, I apologize if this was confusing. The decision of the “automatic” vs “opt-in” model for property eligibility is one that I feel is very important. That is why I mentioned the tradeoff in the Proposed Solution section.

The opt-in model is not part of the current proposal. The Detailed Design describes exactly how the current proposal will work. I appreciate your support of that!

The future enhancements are included to give an idea of some directions in which memberwise initialization could evolve. I feel strongly about two aspects of these enhancements:

1. We need a way to specify a default value for memberwise parameters for `let` properties.
2. It would be really nice to have 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 6, 2016, at 6:39 PM, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

Félix

Le 6 janv. 2016 à 19:04:41, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

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.

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

The proposal is more elegant than how you’d do this in Python, but on the other hand the mechanisms you’d use in Python are not single-purpose language features directed at memberwise initialization; they definitely pay their way because they can be used for much more.

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

A glance, admittedly.

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

-Dave

_______________________________________________
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

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.

Thanks for posting an example. This makes sense and is definitely an interesting approach.

Would you be able to use the members property in phase 1 of initialization to initialize those members? Is part of the magic that the compiler understands enough about that property and tuple to allow for that?

How would use cases involving a subset of members be handled? This is the primary use case I have had in mind while developing this proposal.

···

Sent from my iPad

On Jan 6, 2016, at 7: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:

-Joe

Sent from my moss-covered three-handled family gradunza

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?

···

Sent from my iPad

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

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:

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

-Kevin Ballard

···

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

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

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.

···

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

On Jan 7, 2016, at 2:46 AM, Kevin Ballard via swift-evolution < > 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
https://lists.swift.org/mailman/listinfo/swift-evolution

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

I cast a reluctant -1

I've followed this thread since its beginning and do believe that this
could be a nice feature but I agree that it doesn't pay for its own
complexity in this proposal. We would be better served if we could spell
out what needs to be implemented for the variadics and or the magic Members
tuple, in my opinion.

···

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

On Jan 7, 2016, at 8:55 PM, Joe Groff via swift-evolution < > 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
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).

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.

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:

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.

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.

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.

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.

···

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

On Jan 7, 2016, at 9:02 AM, plx via swift-evolution <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> 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
https://lists.swift.org/mailman/listinfo/swift-evolution

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?

-Thorsten

···

Am 08.01.2016 um 04:37 schrieb Matthew Johnson via swift-evolution <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

Still, 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? 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.

The struct memberwise behavior today is really simple and doesn’t expose a public API allowing for non-exhaustive behavior requirements and a much simpler design.

The struct rule is basically this:

A member is added to the initializer when the following conditions are true:
    1. The member is not declared with let and an initialization value
    2. The member is not a computed property

Also, this generated initializer is only generated when no other init() has been defined.

The really important observation though is that it keeps the generated initializer private, which means that there are no real API contracts to consider as it can only be used within your own code. It’s a nicety to have when simply building up some code to solve a problem.

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

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.

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

The only thing I see your proposal removing is the re-typing of all of the members in the init signature that can be configured. And in fact, the current proposal doesn't support this. You have a futures section that looks a way to make this possible kinda like this:

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

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

-David

···

On Jan 8, 2016, at 9:31 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Ah, right, per initializer — not per initialized variable. My bad! That makes much more sense.

P

···

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

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

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

FWIW I believe it’s meant to be interpreted as "M lines of boilerplate per initializer" x "N initializers” => ~ MN lines of initializer-related boilerplate per type.

If you transition to a manual initializer you can provide your own mangled internal parameter names for your manual initializer, so there's no behavioral change. And in most cases the distinction won't actually matter anyway, it's just the cases where a property may change during initialization (either due to modifying it directly, or due to calling a method that modifies it and then reading the property afterwards) where you actually care that the identifier refers to the property.

-Kevin Ballard

···

On Fri, Jan 8, 2016, at 11:15 AM, Matthew Johnson wrote:

> On Jan 8, 2016, at 1:05 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:
>
> I had another thought last night about this proposal. Since it appears to be working as syntactic sugar, if I write something like
>
> var x: Int
> memberwise init(...) {
> // what does x refer to here?
> }
>
> Inside of the memberwise init, does the bare identifier `x` refer to the property, or to the implicit parameter? I think it should refer to the property, because making it refer to the implicit parameter means you'd be referring to a variable that was never visibly declared anywhere.
> AFAIK the only precedent for that is the implicit argument `newValue` to setters, `oldValue` to didSet observers, and `error` to catch blocks, but those are all constant identifiers that mean the same thing in every such context, as opposed to memberwise init where the implicit parameters are different for each init. More generally, we should avoid exposing such non-declared variables without a compelling reason.
>
> The other argument in favor of making `x` refer to the property is because that's just generally more useful. All the properties match the values of the parameters on the first line of the user-supplied body to the memberwise init, so there's no benefit to be had from leaving the parameters accessible to user code.
>
> As for how to actually accomplish this, assuming this feature is implemented as an AST transformation, ideally we'd have some way to synthesize identifiers in the AST that cannot possibly be referenced by user code (e.g. some form of identifier namespacing if it exists, or some form of gensym-like behavior). Alternatively, if there's no particularly good way to do this, we could just use something like `__name` as the internal parameter name.

Thanks for bringing this up Kevin. I agree it should refer to the property. Not sure what, but for some reason I didn’t think to state this explicitly in the proposal. In fact, my examples accidentally implied otherwise because they didn’t show the compiler using a mangled name of some kind for the internal parameter name in the expansions.

The only downside is that it changes behavior if you transition from a memberwsie initializer to a manual initializer.

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

FWIW I believe it’s meant to be interpreted as "M lines of boilerplate per initializer" x "N initializers” => ~ MN lines of initializer-related boilerplate per type.

Yep.

···

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

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

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

Yes, of course. But if you forget to provide your own internal name it would be a behavior change. I agree in most cases it wouldn’t matter much either way. I just thought it was important to point out that there is a rare case where it could result in something unintended / unexpected.

Matthew

···

On Jan 8, 2016, at 1:17 PM, Kevin Ballard <kevin@sb.org> wrote:

On Fri, Jan 8, 2016, at 11:15 AM, Matthew Johnson wrote:

On Jan 8, 2016, at 1:05 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

I had another thought last night about this proposal. Since it appears to be working as syntactic sugar, if I write something like

var x: Int
memberwise init(...) {
  // what does x refer to here?
}

Inside of the memberwise init, does the bare identifier `x` refer to the property, or to the implicit parameter? I think it should refer to the property, because making it refer to the implicit parameter means you'd be referring to a variable that was never visibly declared anywhere.
AFAIK the only precedent for that is the implicit argument `newValue` to setters, `oldValue` to didSet observers, and `error` to catch blocks, but those are all constant identifiers that mean the same thing in every such context, as opposed to memberwise init where the implicit parameters are different for each init. More generally, we should avoid exposing such non-declared variables without a compelling reason.

The other argument in favor of making `x` refer to the property is because that's just generally more useful. All the properties match the values of the parameters on the first line of the user-supplied body to the memberwise init, so there's no benefit to be had from leaving the parameters accessible to user code.

As for how to actually accomplish this, assuming this feature is implemented as an AST transformation, ideally we'd have some way to synthesize identifiers in the AST that cannot possibly be referenced by user code (e.g. some form of identifier namespacing if it exists, or some form of gensym-like behavior). Alternatively, if there's no particularly good way to do this, we could just use something like `__name` as the internal parameter name.

Thanks for bringing this up Kevin. I agree it should refer to the property. Not sure what, but for some reason I didn’t think to state this explicitly in the proposal. In fact, my examples accidentally implied otherwise because they didn’t show the compiler using a mangled name of some kind for the internal parameter name in the expansions.

The only downside is that it changes behavior if you transition from a memberwsie initializer to a manual initializer.

If you transition to a manual initializer you can provide your own mangled internal parameter names for your manual initializer, so there's no behavioral change. And in most cases the distinction won't actually matter anyway, it's just the cases where a property may change during initialization (either due to modifying it directly, or due to calling a method that modifies it and then reading the property afterwards) where you actually care that the identifier refers to the property.

Still, 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? 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.

The struct memberwise behavior today is really simple and doesn’t expose a public API allowing for non-exhaustive behavior requirements and a much simpler design.

The struct rule is basically this:

A member is added to the initializer when the following conditions are true:
    1. The member is not declared with let and an initialization value
    2. The member is not a computed property

Also, this generated initializer is only generated when no other init() has been defined.

The really important observation though is that it keeps the generated initializer private, which means that there are no real API contracts to consider as it can only be used within your own code. It’s a nicety to have when simply building up some code to solve a problem.

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.

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?

    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.

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.

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

···

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

On Jan 8, 2016, at 9:31 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The only thing I see your proposal removing is the re-typing of all of the members in the init signature that can be configured. And in fact, the current proposal doesn't support this. You have a futures section that looks a way to make this possible kinda like this:

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

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

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

···

On Jan 8, 2016, at 11:31 AM, Matthew Johnson <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:

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.

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

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

(The next most common request related to this proposal is 1, memberwise inits for classes, mostly in simple cases. 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'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.)

Jordan

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

···

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

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

Thanks, that's great to hear!

- Janosch

···

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 <mailto:jnosh@jnosh.com>> wrote:

On 11 Jan 2016, at 07:37, Douglas Gregor 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:

  * 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