Initializer labelling and forwarding


(David James) #1

NOTE: this may cross-over with other proposals. I’m totally fine with converging, but just want this idea vetted first.

The idea is to reduce code duplication and improve maintainability, particularly around the constructing of objects. Objects hold fields which are subject to schema change, and therefore must be maintainable. The goal with this proposal/idea is to avoid replicating field names throughout the code, particularly when objects are being constructed.

One example is with the Builder pattern, which separates configuration of an object from it’s creation.

Here is some sample code. I have made bold the suggested changes.

Models:

struct Profile {
    var user:Person?
    var shippingAddress:Location?
    var billingAddress:Location?
}

struct Person {
    let givenName:String
    let familyName:String
}

struct Location {
    let street:String
    let town:String
    let region:String
    let country:String
    init custom (street: String, town: String, region: String, country: String) {
        // ^^ initializer with label. contrived example.
        self.street = street
        self.town = town
        self.region = region
        self.country = country
    }
}

Here is a typical builder. Notice the field name duplication, in the method signatures, bodies, and the model objects above. This is a code smell. Granted, it can be resolved in other ways, but we tend to see this kind of duplication everywhere surrounding the model layer, and it causes maintenance problems.

struct TypicalBuilder {
    var profile:Profile = Profile()
    mutating func setUser(givenName: String, familyName: String) {
        profile.user = Person(givenName: givenName, familyName: familyName)
    }
    mutating func setShippingAddress(street: String, town: String, region: String, country: String) {
        profile.shippingAddress = Location(street: street, town: town, region: region, country: country)
    }
    mutating func setBillingAddress(street: String, town: String, region: String, country: String) {
        profile.billingAddress = Location(street: street, town: town, region: region, country: country)
    }
    func build() -> Profile {
        return profile
    }
}

Here’s my idea for initializer forwarding.

struct BetterBuilder {
    var profile:Profile = Profile()
    mutating func setUser(forward: Person.init) {
        // .init without label would be equivalent to memberwise initializer
        // "forward” could be an arbitrary label
        profile.user = Person(forward)
    }
    mutating func setShippingAddress(forward: Location.init{custom}) {
        // .init with label, uses initializer signature with that label
        profile.shippingAddress = Location(forward)
    }
    mutating func setBillingAddress(forward: Location.init{custom}) {
        profile.billingAddress = Location(forward)
    }
    func build() -> Profile {
        return profile
    }
}

let builder = BetterBuilder()
builder.setUser(givenName: “David”, familyName: “James”)

It’s important to note, that the above two structs are equivalent, code completion would be the same and the call site would be same.

Syntax is up for grabs. Suggestions would be appreciated. Can this be converged with Doug Gregor’s function naming proposal <https://github.com/DougGregor/swift-evolution/blob/generalized-naming/proposals/0000-generalized-naming.md> or other proposals in the works?

Mainly, is the idea interesting? Can we expand it to include plain functions/methods as well (not just initializers).

David James


(Matthew Johnson) #2

NOTE: this may cross-over with other proposals. I’m totally fine with converging, but just want this idea vetted first.

Hi David. Did have you seen the Parameter Forwarding proposal I sent out last night? That would handle this use case, among others. Here’s a link to the proposal (there is also a mailing list thread): https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md

Thanks for providing a concrete example of how that proposal can be used!

Matthew

···

On Jan 11, 2016, at 1:58 PM, David James via swift-evolution <swift-evolution@swift.org> wrote:

The idea is to reduce code duplication and improve maintainability, particularly around the constructing of objects. Objects hold fields which are subject to schema change, and therefore must be maintainable. The goal with this proposal/idea is to avoid replicating field names throughout the code, particularly when objects are being constructed.

One example is with the Builder pattern, which separates configuration of an object from it’s creation.

Here is some sample code. I have made bold the suggested changes.

Models:

struct Profile {
    var user:Person?
    var shippingAddress:Location?
    var billingAddress:Location?
}

struct Person {
    let givenName:String
    let familyName:String
}

struct Location {
    let street:String
    let town:String
    let region:String
    let country:String
    init custom (street: String, town: String, region: String, country: String) {
        // ^^ initializer with label. contrived example.
        self.street = street
        self.town = town
        self.region = region
        self.country = country
    }
}

Here is a typical builder. Notice the field name duplication, in the method signatures, bodies, and the model objects above. This is a code smell. Granted, it can be resolved in other ways, but we tend to see this kind of duplication everywhere surrounding the model layer, and it causes maintenance problems.

struct TypicalBuilder {
    var profile:Profile = Profile()
    mutating func setUser(givenName: String, familyName: String) {
        profile.user = Person(givenName: givenName, familyName: familyName)
    }
    mutating func setShippingAddress(street: String, town: String, region: String, country: String) {
        profile.shippingAddress = Location(street: street, town: town, region: region, country: country)
    }
    mutating func setBillingAddress(street: String, town: String, region: String, country: String) {
        profile.billingAddress = Location(street: street, town: town, region: region, country: country)
    }
    func build() -> Profile {
        return profile
    }
}

Here’s my idea for initializer forwarding.

struct BetterBuilder {
    var profile:Profile = Profile()
    mutating func setUser(forward: Person.init) {
        // .init without label would be equivalent to memberwise initializer
        // "forward” could be an arbitrary label
        profile.user = Person(forward)
    }
    mutating func setShippingAddress(forward: Location.init{custom}) {
        // .init with label, uses initializer signature with that label
        profile.shippingAddress = Location(forward)
    }
    mutating func setBillingAddress(forward: Location.init{custom}) {
        profile.billingAddress = Location(forward)
    }
    func build() -> Profile {
        return profile
    }
}

let builder = BetterBuilder()
builder.setUser(givenName: “David”, familyName: “James”)

It’s important to note, that the above two structs are equivalent, code completion would be the same and the call site would be same.

Syntax is up for grabs. Suggestions would be appreciated. Can this be converged with Doug Gregor’s function naming proposal <https://github.com/DougGregor/swift-evolution/blob/generalized-naming/proposals/0000-generalized-naming.md> or other proposals in the works?

Mainly, is the idea interesting? Can we expand it to include plain functions/methods as well (not just initializers).

David James

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David James) #3

On the one hand, I now see the naivety of my example, but I want to emphasize the idea behind it, and possibly behind some your proposals Matthew, and that is: how can we reduce code duplication and make code more maintainable?. Managing the proliferation of state in programs causes the majority of our headaches. If Swift can help mitigate this, we should put our thinking caps on and figure out a way.

···

On Jan 11, 2016, at 10:40 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 11, 2016, at 1:58 PM, David James via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

NOTE: this may cross-over with other proposals. I’m totally fine with converging, but just want this idea vetted first.

Hi David. Did have you seen the Parameter Forwarding proposal I sent out last night? That would handle this use case, among others. Here’s a link to the proposal (there is also a mailing list thread): https://github.com/anandabits/swift-evolution/blob/parameter-forwarding/proposals/NNNN-parameter-forwarding.md

Thanks for providing a concrete example of how that proposal can be used!

Matthew

The idea is to reduce code duplication and improve maintainability, particularly around the constructing of objects. Objects hold fields which are subject to schema change, and therefore must be maintainable. The goal with this proposal/idea is to avoid replicating field names throughout the code, particularly when objects are being constructed.

One example is with the Builder pattern, which separates configuration of an object from it’s creation.

Here is some sample code. I have made bold the suggested changes.

Models:

struct Profile {
    var user:Person?
    var shippingAddress:Location?
    var billingAddress:Location?
}

struct Person {
    let givenName:String
    let familyName:String
}

struct Location {
    let street:String
    let town:String
    let region:String
    let country:String
    init custom (street: String, town: String, region: String, country: String) {
        // ^^ initializer with label. contrived example.
        self.street = street
        self.town = town
        self.region = region
        self.country = country
    }
}

Here is a typical builder. Notice the field name duplication, in the method signatures, bodies, and the model objects above. This is a code smell. Granted, it can be resolved in other ways, but we tend to see this kind of duplication everywhere surrounding the model layer, and it causes maintenance problems.

struct TypicalBuilder {
    var profile:Profile = Profile()
    mutating func setUser(givenName: String, familyName: String) {
        profile.user = Person(givenName: givenName, familyName: familyName)
    }
    mutating func setShippingAddress(street: String, town: String, region: String, country: String) {
        profile.shippingAddress = Location(street: street, town: town, region: region, country: country)
    }
    mutating func setBillingAddress(street: String, town: String, region: String, country: String) {
        profile.billingAddress = Location(street: street, town: town, region: region, country: country)
    }
    func build() -> Profile {
        return profile
    }
}

Here’s my idea for initializer forwarding.

struct BetterBuilder {
    var profile:Profile = Profile()
    mutating func setUser(forward: Person.init) {
        // .init without label would be equivalent to memberwise initializer
        // "forward” could be an arbitrary label
        profile.user = Person(forward)
    }
    mutating func setShippingAddress(forward: Location.init{custom}) {
        // .init with label, uses initializer signature with that label
        profile.shippingAddress = Location(forward)
    }
    mutating func setBillingAddress(forward: Location.init{custom}) {
        profile.billingAddress = Location(forward)
    }
    func build() -> Profile {
        return profile
    }
}

let builder = BetterBuilder()
builder.setUser(givenName: “David”, familyName: “James”)

It’s important to note, that the above two structs are equivalent, code completion would be the same and the call site would be same.

Syntax is up for grabs. Suggestions would be appreciated. Can this be converged with Doug Gregor’s function naming proposal <https://github.com/DougGregor/swift-evolution/blob/generalized-naming/proposals/0000-generalized-naming.md> or other proposals in the works?

Mainly, is the idea interesting? Can we expand it to include plain functions/methods as well (not just initializers).

David James

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

David James