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

Matthew Johnson matthew at anandabits.com
Fri Jan 8 13:21:50 CST 2016


> On Jan 8, 2016, at 12:51 PM, David Owens II <david at owensd.io> wrote:
> 
> 
>> On Jan 8, 2016, at 9:31 AM, Matthew Johnson via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>>> 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)
  }
}

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

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


More information about the swift-evolution mailing list