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

Thorsten Seitz tseitz42 at icloud.com
Sat Jan 9 13:01:38 CST 2016


I like these ideas!

Some brain storming from me as well:

The members property should probably not be a tuple but a struct, thereby providing names for the values.
I think splitting this property into two (varMembers and letMembers) with a read-only property members tying both together for convenience (members.vars, members.lets).

I’ve thrown together a playground to toy with these ideas:

import Foundation

/*
struct S {
    var s0: String
    var s1: String = "s1"
    var s2: String = "s2"
    let j: Int default 42
    private let i: Int

    // implicit property #members
    var #members: Members {
	get { return Members(#vars, #lets) }
    }

    // implicit property #vars
    var #vars: Members.Vars

    // implicit property #lets
    let #lets: Members.Lets

    init(members: Members) {
        #members = members
    }

    init(anInt: Int, anotherInt: Int, members: Members) {
        #members = members
        i = anInt > anotherInt ? anInt : anotherInt
    }

    mutating func updateFrom(other: S) {
        self.#members.vars = other.#members.vars
    }
}
*/

// faking the above
struct S {
    struct Members {
        struct Vars {
            var s0: String?
            var s1: String
            var s2: String
        }
        struct Lets {
            var j: Int
            private var i: Int?
        }
        var vars: Vars
        var lets: Lets
        init(vars: Vars, lets: Lets) {
            self.vars = vars
            self.lets = lets
        }
        init(s0: String? = nil, s1: String = "s1", s2: String = "s2", j: Int = 42, i: Int? = nil) {
            self.vars = Vars(s0: s0, s1: s1, s2: s2)
            self.lets = Lets(j: j, i: i)
        }
    }
    // having separate varMembers and letMembers allows to ensure
    // that letMembers cannot be changed
    // (this would not be possible with "var members: Members")
    var varMembers: Members.Vars
    let letMembers: Members.Lets
    
    var members: Members {
        get { return Members(vars: varMembers, lets: letMembers) }
    }
    
    var s0: String {
        get { return varMembers.s0! }
        set { varMembers.s0 = newValue }
    }
    
    var s1: String {
        get { return varMembers.s1 }
        set { varMembers.s1 = newValue }
    }
    
    var s2: String {
        get { return varMembers.s2 }
        set { varMembers.s2 = newValue }
    }
    
    var j: Int {
        get { return letMembers.j }
    }
    
    private var i: Int {
        get { return letMembers.i! }
    }
    
    init(members: Members) {
        varMembers = members.vars
        letMembers = members.lets
    }
    
    init(anInt: Int, anotherInt: Int, members: Members) {
        varMembers = members.vars
        var lets = members.lets
        lets.i = anInt > anotherInt ? anInt : anotherInt
        letMembers = lets
    }
    
    mutating func updateFrom(other: S) {
        self.varMembers = other.varMembers
        // self.letMembers = other.letMembers // type error!
    }
}

extension S: CustomStringConvertible {
    var description: String {
        return "S(s0: \"\(s0)\", s1: \"\(s1)\", s2: \"\(s2)\", j: \(j), i: \(i))"
    }
}

var s: S = S(members: S.Members(s0: "foo", s1: "bar", s2: "baz", j: 1, i: 0))
//s.members.lets.i = 1 // type error!
var s2: S = S(anInt: 1, anotherInt: 2, members: S.Members(s0: "other", j: 2))
s.updateFrom(s2)
s.s0 = "new"
var ms = s.varMembers
ms.s0 = "broken?"
s.s0 // is still "new"
// s.members.vars.s0 = "broken??" // type error!
var s3 = S(members: s2.members)


-Thorsten



> Am 08.01.2016 um 23:14 schrieb Paul Cantrell via swift-evolution <swift-evolution at swift.org>:
> 
>> 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.”
> 
>> 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.
> 
>>> 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.
> 
>> 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.
> 
> 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
> 
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

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


More information about the swift-evolution mailing list