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

Matthew Johnson matthew at anandabits.com
Fri Jan 8 17:41:39 CST 2016


> On Jan 8, 2016, at 4:14 PM, Paul Cantrell <cantrell at pobox.com> wrote:
> 
>> On Jan 8, 2016, at 11:31 AM, Matthew Johnson <matthew at anandabits.com <mailto:matthew at anandabits.com>> wrote:
>> 
>>> On Jan 8, 2016, at 11:03 AM, Paul Cantrell via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>> 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.”

How exactly would you do this?  I don’t understand what you think that would look like.  Making it more capable is what my proposal is attempting to do so I am confused by this statement.  This proposal changes it in the following 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

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

See the list above.  I don’t think the behavior changes beyond the implicit memberwise init as they are perceived.

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

I do have some new ideas in the direction they discussed.  I’m going to work on fleshing those out tonight.

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

I really don’t think the “members” computed tuple property is a workable solution for initializing `let` properties.  It would be really strange to allow it to do so.  Any such property that was allowed to do so would not work outside an initializer anyway as it would try to mutate a `let` when you used it.

I have also been thinking a lot about approaches that would be similar in some sense and build on a general purpose parameter forwarding mechanism.  I have some ideas that I am going to work on fleshing out tonight.


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

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


More information about the swift-evolution mailing list