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

David Owens II david at owensd.io
Fri Jan 8 14:09:16 CST 2016


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


>> 
>> The proposal essentially is doing two things:
>> 
>>     1. Create a way to generate a public API contract based on a series of fairly complicated rules, potential annotations, and member orderings within the type
> 
> Would you feel better about the proposal if it did not allow for public memberwise initializers?

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


>>     2. Generate the pass through of assignment for the parameters of the init() and their backing values.
>> 
>> The vast amount complexity comes from trying to solve #1.
> 
> This is not true.  I would still want access control enforced even if memberwise initializers could not be public.

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


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

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

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

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

    memberwise init(...) {}
}

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

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

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

Or with the below example:

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

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


>> 
>> Your example hear illustrates the complexity (slightly modified from your proposal’s usage):
>> 
>> struct Foo {
>>   let bar: String
>>   let bas: Int
>>   let baz: Double
>> 
>>   init(self bar: String, self bas: Int, bax: Int) {
>>     // self.bar = bar synthesized by the compiler
>>     // self.bas = bas synthesized by the compiler
>>     self.baz = Double(bax)
>>   }
>> }
> 
> This approach has been mentioned quite a few times.  It results in a lot of duplication without saving a lot.  This is especially true if you have a lot of properties and more than one designated initializer that could use memberwise initialization .  
> 
> IMO, if we were going to take this approach we should at least be able to omit redundant type information and default values for `var` properties where they exist.  At least then we are saving as much as we can under this approach.
> 
> struct Foo {
>   let bar: String
>   let bas: Int
>   let baz: Double
> 
>   init(self bar, self bas, bax: Int) {
>     // self.bar = bar synthesized by the compiler
>     // self.bas = bas synthesized by the compiler
>     self.baz = Double(bax)
>   }
> }

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

-David

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


More information about the swift-evolution mailing list