[swift-evolution] [Review] SE-0018 Flexible Memberwise Initialization

Matthew Johnson matthew at anandabits.com
Fri Jan 8 17:07:05 CST 2016


> On Jan 8, 2016, at 2:09 PM, David Owens II <david at owensd.io> wrote:
> 
> 
>> On Jan 8, 2016, at 11:21 AM, Matthew Johnson <matthew at anandabits.com <mailto:matthew at anandabits.com>> wrote:
>> 
>> 
>> Actually it is internal, not private, and it exposes private properties via that internal initializer.  It’s only in your own code, but I don't think it should be violating access control in that way.
> 
> Not for me in Xcode 7.2. Has this changed? Maybe it’s my app target? The implicit init() is only visible for me within the same file the struct is defined in.

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

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

> 
> 
>>> 
>>> The proposal essentially is doing two things:
>>> 
>>>     1. Create a way to generate a public API contract based on a series of fairly complicated rules, potential annotations, and member orderings within the type
>> 
>> Would you feel better about the proposal if it did not allow for public memberwise initializers?
> 
> Marginally. My main concern is the complexity of the rules, especially when looking at the direction many of the futures take. There are all of these annotations that get put all over that litter the type
> definition simply to support memberwise inits.

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

The proposal only changes current state in a few ways:

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

Are there specific changes in this list that you dislike?

> 
> 
>>>     2. Generate the pass through of assignment for the parameters of the init() and their backing values.
>>> 
>>> The vast amount complexity comes from trying to solve #1.
>> 
>> This is not true.  I would still want access control enforced even if memberwise initializers could not be public.
> 
> There’s no concern with access control as it’s explicitly handled. I can expose whatever I want as the API and route it however I want in code. 

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

> 
> 
>>> 
>>> As for this:
>>> 
>>>>> (Aside, a small nitpick, but it really bugs me: initialization has O(M+N) complexity, not O(M×N) complexity. One doesn’t initialize every member with every parameter.)
>>>> 
>>>> MxN is members x initializers.
>>> 
>>> 
>>> Paul, this has also bugged me too; I do not see how it is accurate. There aren’t N initializers, there is one initializer that must fully instantiate the type. Each type may have additional convenience initializers, but these are not required. Further, they cannot make use of the placeholder. There is a “futures” that talks about it, but that’s out of scope of the original proposal. 
>> 
>> Swift allows more than one designated initializer.  N never large but it more than 1 in enough cases to matter.  The point is that it results in enough boilerplate that I believe it affects how many people design their initializers.  
> 
> Sure, but your base proposal does not allow a way for there to be more than one memberwise designated initializer. So in the base case, your proposal doesn’t solve what you call the MxN problem. In the futures, you describe ways to annotate inits() so that members aren’t considered in the signature.

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

> 
> struct Point {
>     let x: Int
>     let y: Int
>     let z: Int
>     
>     @nomemberwise(y, z)
>     memberwise init(...) {
>         y = 0
>         z = 0
>     }
> 
>     @nomemberwise(z)
>     memberwise init(...) {
>         z = 0
>     }
> 
>     memberwise init(...) {}
> }
> 
> Let’s say that for simplicity the @nomemberwise() attribute takes a list of parameters. This is one version of the code that can be written to support zero-ing out by default.
> 
> Or, we can do this (what Swift has today):
> 
> struct Point {
>     let x: Int
>     let y: Int
>     let z: Int
>     
>     init(x: Int, y: Int = 0, z: Int = 0) {
>         self.x = x
>         self.y = y
>         self.z = z
>     }
> }
> 
> Or with the below example:
> 
> struct Point {
>     let x: Int
>     let y: Int
>     let z: Int
>     
>     init(self x: Int, self y: Int = 0, self z: Int = 0) {}
> }
> 
> Even in all of your futures, I don’t see how you fix the "MxN problem" for let without bringing back the assignment in the type declaration, which was generally not well received.
> 
> 
>>> 
>>> Your example hear illustrates the complexity (slightly modified from your proposal’s usage):
>>> 
>>> struct Foo {
>>>   let bar: String
>>>   let bas: Int
>>>   let baz: Double
>>> 
>>>   init(self bar: String, self bas: Int, bax: Int) {
>>>     // self.bar = bar synthesized by the compiler
>>>     // self.bas = bas synthesized by the compiler
>>>     self.baz = Double(bax)
>>>   }
>>> }
>> 
>> This approach has been mentioned quite a few times.  It results in a lot of duplication without saving a lot.  This is especially true if you have a lot of properties and more than one designated initializer that could use memberwise initialization .  
>> 
>> IMO, if we were going to take this approach we should at least be able to omit redundant type information and default values for `var` properties where they exist.  At least then we are saving as much as we can under this approach.
>> 
>> struct Foo {
>>   let bar: String
>>   let bas: Int
>>   let baz: Double
>> 
>>   init(self bar, self bas, bax: Int) {
>>     // self.bar = bar synthesized by the compiler
>>     // self.bas = bas synthesized by the compiler
>>     self.baz = Double(bax)
>>   }
>> }
> 
> I think it keeps coming up because it’s far simpler. While there is duplication in the type signature, the code is still smaller, more flexible, and more applicable than being limited to only initialization. Further, and I think this is what is far more important, I only need to look at single place to understand what is going on with initialization for any particular call. I don’t need to find out what members are annotated, or create the list of head of members and exclude certain ones if @nomemberize() is used. Each member being initialized as a configuration entity from the user is right there, no questions asked.

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

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

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

Matthew

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160108/8059853e/attachment.html>


More information about the swift-evolution mailing list