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

Joe Groff jgroff at apple.com
Mon Dec 28 12:59:45 CST 2015


> On Dec 27, 2015, at 9:51 AM, Matthew Johnson via swift-evolution <swift-evolution at swift.org> wrote:
> 
>> 
>> On Dec 26, 2015, at 11:31 PM, Chris Lattner <clattner at apple.com <mailto:clattner at apple.com>> wrote:
>> 
>> On Dec 25, 2015, at 12:04 PM, Matthew Johnson <matthew at anandabits.com <mailto:matthew at anandabits.com>> wrote:
>>>>> Discussion on a couple of topics continues inline below as well.
>>>> 
>>>> Great, comments below:
>>> 
>>> Thanks for continuing the discussion.  I have updated the proposal to reflect the core functionality and moved everything else to the future enhancements section.  I think this draft is ready or nearly ready for a PR.
>>> 
>>> Here is a link to the current draft: https://github.com/anandabits/swift-evolution/edit/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md <https://github.com/anandabits/swift-evolution/edit/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md>
>>> 
>>> Here is a link to the commit diff: https://github.com/anandabits/swift-evolution/commit/f15360d2e201709640f9137d86a8b705a07b5466?short_path=f5ec377#diff-f5ec377f4782587684c5732547456b70 <https://github.com/anandabits/swift-evolution/commit/f15360d2e201709640f9137d86a8b705a07b5466?short_path=f5ec377#diff-f5ec377f4782587684c5732547456b70>
>> Thanks again for pushing this forward, this is looking great.
> 
> Sounds good.  I just submitted a PR.  I think it’s ready.  Please let me know if you feel any further changes are necessary.
> 
>> 
>>>> It is also annoying that writing it as a static property would force you to write something like “X.a" instead of just “a”.
>>> 
>>> I agree with this.  I can accept an argument that this provides enough value to avoid changing the language.
>>> 
>>> That said, I do think default values for let properties are higher value.  If I had to pick one it would be default values and I would consider it to be worthy of a breaking change in the language.  But It would be great if we can find a way to support both.
>> 
>> I understand your desire, but this really is something we’ll have to discuss carefully.  Changing Swift soft that “let x = 42” doesn’t necessarily mean that x is 42 is a pretty deep semantic change, which will be surprising for many people (as was noted by others on this thread).  I agree that it would be great to get more flexible initialization for lets, but keep in mind that you can already do this long-hand if you want.
> 
> I know changing the existing behavior would require very careful thinking.  I am absolutely open to any solution that allow a default value for let properties to be specified whether it changes the existing behavior or not.  What is the best way I can help to move this discussion forward in a productive manner?  Would it be a good idea to start a new thread on the topic?  Or is this something you feel like the core team needs to mull over for a while before we can have a productive conversation on the list?

At least for structs, there's almost no reason for the memberwise-initialized properties to be `let`s, since preventing partial mutation of a value doesn't put any effective limits on users of the type. If you have:

struct Foo {
  let x
  let y

  memberwise init(...)
}

then even though you can't say:

var foo = Foo(x: 1, y: 1)
foo.x = 2

you can do the equivalent partial update by memberwise initialization:

var foo = Foo(x: 1, y: 1)
foo = Foo(x: 2, y: foo.y)

and it's highly likely both forms will be optimized to the same thing.

-Joe

> 
>> 
>>>> This I still have a concern with, in two ways:
>>>> 
>>>> 1) This still has tight coupling between the base and derived class.  Properties in a based class are not knowable by a derived class in general (e.g. across module boundaries) and this directly runs afoul of our resilience plans.  Specifically, across module boundaries, properties can change from stored to computed (or back) without breaking clients.
>>>> 
>>>> 2) You’re introducing another unnecessary feature "super.init(…)” which will have to be independently justified. 
>>>> 
>>> 
>>> I will continue thinking about how this might be solved and also about other cases where such a forwarding feature might be useful.  
>> 
>> Sounds good.  This is definitely an interesting area to investigate, but I don't want the general goodness of your base memberwise init proposal to have to wait :-)
> 
> I agree with you on not holding back the base proposal.  
> 
> I really appreciate you noticing that this should really be orthogonal to the base proposal.  I’ve already been giving this a lot of thought.  It is very clear to me now that a much more general parameter / argument forwarding feature is the right approach.  I am going to pursue a proposal for that as well.
> 
>> 
>>>> Other comments:
>>>> 
>>>> In "Impact on existing code”, given your proposal, I think that we should keep the implicit memberwise initializer on classes, start generating it for root classes, and generate it for derived classes whose parent has a DI with no arguments (e.g. subclasses of NSObject).  We should keep the current behavior where it is generated with internal behavior, and it is surpressed if *any* initializers are defined inside of the type.
>>> 
>>> I’ll update that section to reflect these comments.  
>>> 
>>> One question I have is what the implicit memberwise initializer should do in the case of private members.  If we make it follow the rules of this proposal we would break existing structs with private members that are currently receiving the implicit memberwise initializer.  
>>> 
>>> I think this would be a good breaking change for both consistency with this proposal and because implicitly exposing private members via the initializer was a questionable choice.  A mechanical migration could generate code to add an explicit implementation of the previously implicit initializer that doesn’t qualify under the rules of the new proposal.  How do you feel about this?
>> 
>> I don’t have a strong opinion about this, and I can see reasonable arguments on either side.  Breaking source compatibility in this case isn’t a show-stopper, since this will roll out in Swift 3.
> 
> Glad to hear breaking compatibility is ok in this case if it is required for consistency.
> 
>> 
>> Here are the pros and cons as I see them with disallow-ing more-private fields to be published through less-private memberwise inits:
>> 
>> Neutral: Either approach is fine for “CGRect” like types that are really just public bags of public fields.
>> Pro: Makes logical sense at first blush.  Memberwise inits publishing private state would be odd/surprising.
>> Pro: Safer default, in that you don’t accidentally publish stuff you don’t want through a memberwise init.
>> Con: This puts tension between access control for stored properties and memberwise inits.  You have to choose between narrower access control or getting the benefit of a memberwise init.  Another way to say it: this design means that narrow access control leads to boilerplate.
>> 
>> I’m sure there are others as well.
>> 
>> Again, I don’t have a strong opinion, but I’d lean slightly towards publishing all stored properties through the memberwise init.  If you don’t have a strong opinion either, it would be fine to add a section pointing out the tradeoffs, and we can all discuss it during the review period.  I suspect some of the other core team folks will have an opinion on this as well.
> 
> I briefly addressed this in the alternatives considered section.  I’ll fill that out with additional details including the points you raise.
> 
> I feel pretty strongly that we should enforce the access control rules stated in the proposal.  In addition to the Pros you note:
> 
> 1. I think it is usually the right thing to do.  If the caller can’t see a member it probably doesn’t make sense to allow them to initialize it.
> 
> 2. If we expose more private-members by default then memberwise initialization is useless under the current proposal in many cases.  There would be no way to prevent synthesis of parameters for more-private members.  We have to choose between allowing callers to initialize our internal state or forgoing the benefit of memberwise initialization. 
> 
> 3. If a proposal for `@nomemberwise` is put forward and adopted that would allow us to prevent synthesis of parameters for members as desired.  Unfortunately `@nomemberwise` would need to be used much more heavily than it otherwise would (i.e. to prevent synthesis of memberwise parameters for more-private members).  It would be better if `@nomemberwise` was not necessary most of the time.
> 
> 4. If callers must be able to provide memberwise arguments for more-private members directly it is still possible to allow that while taking advantage of memberwise initialization for same-or-less-private members.  You just need to declare a `memberwise init` with explicitly declared parameters for the more-private members and initialize them manually in the body.  Requiring the programmer to manually write any code that exposes more-private members is a arguably a very good thing.
> 
> I think #4 above addresses the con you mention pretty well and #2 above is a significant drawback to not enforcing the access control rule (#3 is also pretty significant IMO).
> 
> I’m sure this will be a point of discussion during review.  I’m prepared to defend the decision I made but will also keep my mind open to opposing arguments.
> 
>> 
>> I sent you a PR (my first! :-) with some nit-picky details on the latest writeup, to fix typos, clarify a few things, and reduce redundancy.  
> 
> Thanks!
> 
>> One point that I left:
>> 
>>> The *implicitly* synthesized initializer will be identical to an initializer declared *explicitly* as follows:
>>> 
>>> 1. For structs and root classes: `memberwise init(...) {}`
>>> 2. For derived classes: `memberwise init(...) { super.init() }`
>> 
>> Note that these are both equivalent, since derived class initializers default to super.init() at the bottom of their body today.  This is why you don’t have to call super.init() when deriving from NSObject, for example.
> 
> I’ll add a note to make this clear.
> 
>> 
>> 
>>>> Thanks again for pushing this forward, you can also put me down as the review manager if you’d like.
>>>> 
>>> 
>>> You’re very welcome.  It’s a privilege to be able to contribute ideas, have them taken seriously and hopefully see them lead to progress in the language.  I’ve really enjoyed the process and discussions with the core team as well as the broader community.
>>> 
>>> It’s really incredible to see the Swift team embrace the community so openly and so graciously!
>>> 
>>> Merry Christmas!
>> 
>> You too Matthew, thanks again,
>> 
>> -Chris
>> 
> 
>  _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151228/0b8c60c5/attachment.html>


More information about the swift-evolution mailing list