Proposal: helpers for initializing properties of same name as parameters


(Tal Atlas) #1

There’s lots of boilerplate of initializing structs with stored properties
and initializer parameters. I’d like to create a syntax for alleviating
that in the 90% case.

struct Foo {
    let bar: String
    let baz: Int

    init(self.bar: String = "default", counter self.baz: Int) {
    }
}

This would be identical to:

struct Foo {
    let bar: String
    let baz: Int

    init(bar: String = "default", counter baz: Int) {
        self.bar = bar
        self.baz = baz
    }
}

(Dan Appel) #2

I'm not sure how I feel about this, but in either case, why limit it to
default parameters? It's useful in other situations, too.

Take this for example:
struct Foo {
    let bar: String
    let bas: Int
    let baz: Double
    init(self.bar: String, self.bas: Int, bax: Int) {
        self.baz = Double(bax)
    }
}

where the need to say

self.bar = bar
self.bas = bas

is now avoided.

···

On Fri, Dec 4, 2015 at 3:16 PM Tal Atlas <me@tal.by> wrote:

There’s lots of boilerplate of initializing structs with stored properties
and initializer parameters. I’d like to create a syntax for alleviating
that in the 90% case.

struct Foo {
    let bar: String
    let baz: Int

    init(self.bar: String = "default", counter self.baz: Int) {
    }
}

This would be identical to:

struct Foo {
    let bar: String
    let baz: Int

    init(bar: String = "default", counter baz: Int) {
        self.bar = bar
        self.baz = baz
    }
}

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

--
Dan Appel


(Brent Royal-Gordon) #3

There’s lots of boilerplate of initializing structs with stored properties and initializer parameters. I’d like to create a syntax for alleviating that in the 90% case.

struct Foo {
    let bar: String
    let baz: Int

    init(self.bar: String = "default", counter self.baz: Int) {
    }
}

I’d like to propose an alternative approach.

Swift already generates a default initializer for structs that don’t have any initializers explicitly defined. However, the moment you write init(, that initializer disappears. It’s also never available on classes, because they have to dispatch to another initializer.

I propose that we make it possible to explicitly ask for that default initializer:

    struct Foo {
        let bar: String
        let baz: Int
        
        init() {
            bar = “default”
            baz = 0
        }

        init default // automatically generates init(bar: String, baz: Int)
    }

Moreover, I propose we extend this to classes:

    struct Foo: Bar {
        let bar: String
        let baz: Int
        
        init() {
            bar = “default”
            baz = 0
        }

        init default() // automatically generates init(bar: String, baz: Int) with super.init()
    }

In a class, the “default” statement must have parens after it; these indicate the parameters to be added for the superclass initializer. If you need to super up to an initializer that takes parameters, you add the needed parameters there:

    struct Foo {
        let bar: String
        let baz: Int
        
        init() {
            bar = “default”
            baz = 0
        }

        init default(quux: Double) // automatically generates init(quux: Double, bar: String, baz: Int) with super.init(quux: quux)
    }

If you super up to a failable initializer, of course, that will have to be “init? default(quux: Double)”.

Now, one little quirk of the current system is that Swift may actually generate *two* default initializers: a parameterless initializer when all properties have default values, and a memberwise initializer which requires all values be provided. I further propose that these be unified into one default initializer by giving default values to parameters which map to properties with default values. This gives you all the parameter sets currently supported, plus a whole range in between.

    struct Foo {
        let bar: String = “default"
        let baz: Int
        
        init() {
            bar = “default”
            baz = 0
        }

        init default // automatically generates init(bar: String = “default", baz: Int)
    }

Finally, you can continue to decorate the “init” keyword with attributes as you normally would.

  public init default
  internal init default
  private init default
  required init default
  override init default
  @objc init default
  @objc public required init default

(I don’t think convenience initializers make much sense here.)

This does not, unfortunately, eliminate boilerplate in initializers which do more than just set properties. Nor does it allow you to rename parameters, as your example does with “counter”.

···

--
Brent Royal-Gordon
Architechies


(Matthew Johnson) #4

I think this idea scratches at the surface of a problem with initializers that definitely merits some attention. Boilerplate is common in initializers which can lead to types that don’t allow as much flexibility to callers as might be desirable.

More importantly, it can also lead to “initialized” instances that are not fully or properly configured, (implicitly unwrapped?) optional members, and mutability that shouldn’t be necessary past the initial configuration stage of the instance. For example, if a type provides a family of initializers, but also has several members which are intended to be initialized / configured directly by callers a developer could choose to avoid the boilerplate by declaring the additional members as as an implicitly unwrapped optional var members. No doubt this is a bad idea. If the caller does not initialize all of the additional members there is a bomb waiting to go off. Furthermore, there is the potential for mutation after initialization that may not be expected or intended. By requiring a nontrivial amount of boilerplate to avoid this situation the language is unintentionally nudging lazy developers towards bad practices like this.

Let's start with the current proposal but go a bit further and see how much boilerplate can be removed. The type information is already known from the property declaration, and furthermore a default may also be available in the property declaration.

struct Foo {
    let bar: String
    let bas: Int = 1
    let bat: Float = 0
    let bax: String = “default"
    let baz: Double
    
    // self.bar is known to be a String but does not have a default value so it must be provided by the caller
    // self.bas is known to be an Int and implicitly has a default value of 1 specified by the property
    // so it does not need to be provided by the caller
    // self.bat is known to be a Float, but the default is overridden to be 1 instead of 0
    // external labels are provided that correspond to the names of the properties
    init(self.bar, self.bas, self.bat = 1) {
  // the initializer does not receive a value for baz and the property does not provide a default
        // so it must be initialized here before the first phase of initialization is complete
        // if all stored properties had received values through parameters or through property defaults
        // the first phase of initialization would have already been completed when the body of the initializer was entered
        self.baz = someComputedDoubleValue()
        // now all stored properties have been initialized so the first phase of initialization is complete
    }
}

This structure allows us to remove the boilerplate of the property type and default value.

Given the significant difference from regular function parameters, it may make sense to set these apart syntactically in some way, although I am not sure what would make the most sense. For example, we could just have a second parameter tuple in the initializer declaration which represents the “member parameters” to the initializer like this:

init(foo: Int)(self.bar, self.bas, self.bat = 1)

or a vertical pipe like this:

init(foo: Int | self.bar, self.bas, self.bat = 1)

Setting these “member parameters” apart syntactically could also facilitate additional mechanisms to further reduce boilerplate if we find that the same group of “member parameters" exist for several different initializers in the same type. For example, it might become possible to implement a memberwise initializer like this:

// @automembers expands to match all stored properties
init()(@automembers) {}

At this point we have eliminated virtually all of the boilerplate, encouraging developers to use concise *and* safe, yet still flexible initialization strategies.

Matthew

···

On Dec 4, 2015, at 9:05 PM, Dan Appel <dan.appel00@gmail.com> wrote:

I'm not sure how I feel about this, but in either case, why limit it to default parameters? It's useful in other situations, too.

Take this for example:
struct Foo {
    let bar: String
    let bas: Int
    let baz: Double
    init(self.bar: String, self.bas: Int, bax: Int) {
        self.baz = Double(bax)
    }
}

where the need to say

self.bar = bar
self.bas = bas

is now avoided.

On Fri, Dec 4, 2015 at 3:16 PM Tal Atlas <me@tal.by <mailto:me@tal.by>> wrote:
There’s lots of boilerplate of initializing structs with stored properties and initializer parameters. I’d like to create a syntax for alleviating that in the 90% case.

struct Foo {
    let bar: String
    let baz: Int

    init(self.bar: String = "default", counter self.baz: Int) {
    }
}

This would be identical to:

struct Foo {
    let bar: String
    let baz: Int

    init(bar: String = "default", counter baz: Int) {
        self.bar = bar
        self.baz = baz
    }
}

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


#5

I'm not entirely sure what your proposal changes, other than omitting the argument labels and us wing self to refer to the property. Could you try a trivial example that would be runnable if the change was implemented?

Thanks for your time,
Cole Kurkowski

···

On Dec 4, 2015, 21:59 -0600, Matthew Johnson<matthew@anandabits.com>, wrote:

I think this idea scratches at the surface of a problem with initializers that definitely merits some attention.Boilerplate is common in initializers which can lead to types that don’t allow as much flexibility to callers as might be desirable.

More importantly, it can also lead to “initialized” instances that are not fully or properly configured, (implicitly unwrapped?) optional members, and mutability that shouldn’t be necessary past the initial configuration stage of the instance.For example, if a type provides a family of initializers, but also has several members which are intended to be initialized / configured directly by callers a developer could choose to avoid the boilerplate by declaring the additional members as as an implicitly unwrapped optional var members.No doubt this is a bad idea.If the caller does not initialize all of the additional members there is a bomb waiting to go off.Furthermore, there is the potential for mutation after initialization that may not be expected or intended.By requiring a nontrivial amount of boilerplate to avoid this situation the language is unintentionally nudging lazy developers towards bad practices like this.

Let's start with the current proposal but go a bit further and see how much boilerplate can be removed.The type information is already known from the property declaration, and furthermore a default may also be available in the property declaration.

struct Foo {
let bar: String
let bas: Int = 1
let bat:Float = 0
let bax: String = “default"
let baz: Double

// self.bar is known to be a String but does not have a default value so it must be provided by the caller
// self.bas is known to be an Int and implicitly has a default value of 1 specified by the property
// so it does not need to be provided by the caller
// self.bat is known to be a Float, but the default is overridden to be 1 instead of 0
// external labels are provided that correspond to the names of the properties
init(self.bar, self.bas, self.bat = 1) {
// the initializer does not receive a value for baz and the property does not provide a default
// so it must be initialized here before the first phase of initialization is complete
// if all stored properties had received values through parameters or through property defaults
// the first phase of initialization would have already been completed when the body of the initializer was entered
self.baz = someComputedDoubleValue()
// now all stored properties have been initialized so the first phase of initialization is complete
}
}

This structure allows us to remove the boilerplate of the property type and default value.

Given the significant difference from regular function parameters, it may make sense to set these apart syntactically in some way, although I am not sure what would make the most sense.For example, we could just have a second parameter tuple in the initializer declaration which represents the “member parameters” to the initializer like this:

init(foo: Int)(self.bar, self.bas, self.bat = 1)

or a vertical pipe like this:

init(foo: Int | self.bar, self.bas, self.bat = 1)

Setting these “member parameters” apart syntactically could also facilitate additional mechanisms to further reduce boilerplate if we find that the same group of “member parameters" exist for several different initializers in the same type.For example, it might become possible to implement a memberwise initializer like this:

// @automembers expands to match all stored properties
init()(@automembers) {}

At this point we have eliminated virtually all of the boilerplate, encouraging developers to use concise *and* safe, yet still flexible initialization strategies.

Matthew

> On Dec 4, 2015, at 9:05 PM, Dan Appel<dan.appel00@gmail.com(mailto:dan.appel00@gmail.com)>wrote:
> I'm not sure how I feel about this, but in either case, why limit it to default parameters? It's useful in other situations, too.
>
> Take this for example:
> struct Foo {
> let bar: String
> let bas: Int
> let baz: Double
> init(self.bar: String, self.bas: Int, bax: Int) {
> self.baz = Double(bax)
> }
> }
>
> where the need to say
>
> self.bar = bar
> self.bas = bas
>
> is now avoided.
> On Fri, Dec 4, 2015 at 3:16 PM Tal Atlas<me@tal.by(mailto:me@tal.by)>wrote:
> > There’s lots of boilerplate of initializing structs with stored properties and initializer parameters. I’d like to create a syntax for alleviating that in the 90% case.
> >
> > ```swift
> > struct Foo {
> > let bar: String
> > let baz: Int
> >
> > init(self.bar: String = "default", counter self.baz: Int) {
> > }
> > }
> >
> > ```
> >
> > This would be identical to:
> > ```swift
> > struct Foo {
> > let bar: String
> > let baz: Int
> >
> > init(bar: String = "default", counter baz: Int) {
> > self.bar = bar
> > self.baz = baz
> > }
> > }
> >
> > ```
> >
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution@swift.org(mailto:swift-evolution@swift.org)
> > https://lists.swift.org/mailman/listinfo/swift-evolution
> --
> Dan Appel
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org(mailto:swift-evolution@swift.org)
> https://lists.swift.org/mailman/listinfo/swift-evolution

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


(Tal Atlas) #6

I still think that it should include the type in the initializer definition
because if you have private properties you’d need to expose the type
information somehow and that type inference doesn’t work that way anywhere
else in swift, so explicitly requiring the type information in the
initializer is consistent with the rest of swift.

···

On Sat, Dec 5, 2015 at 12:20 AM <crk@fastmail.com> wrote:

I'm not entirely sure what your proposal changes, other than omitting the
argument labels and us wing self to refer to the property. Could you try a
trivial example that would be runnable if the change was implemented?

Thanks for your time,
Cole Kurkowski

On Dec 4, 2015, 21:59 -0600, Matthew Johnson <matthew@anandabits.com>, > wrote:

I think this idea scratches at the surface of a problem with initializers
that definitely merits some attention. Boilerplate is common in
initializers which can lead to types that don’t allow as much flexibility
to callers as might be desirable.

More importantly, it can also lead to “initialized” instances that are not
fully or properly configured, (implicitly unwrapped?) optional members, and
mutability that shouldn’t be necessary past the initial configuration stage
of the instance. For example, if a type provides a family of initializers,
but also has several members which are intended to be initialized /
configured directly by callers a developer could choose to avoid the
boilerplate by declaring the additional members as as an implicitly
unwrapped optional var members. No doubt this is a bad idea. If the
caller does not initialize all of the additional members there is a bomb
waiting to go off. Furthermore, there is the potential for mutation after
initialization that may not be expected or intended. By requiring a
nontrivial amount of boilerplate to avoid this situation the language is
unintentionally nudging lazy developers towards bad practices like this.

Let's start with the current proposal but go a bit further and see how
much boilerplate can be removed. The type information is already known
from the property declaration, and furthermore a default may also be
available in the property declaration.

struct Foo {
    let bar: String
    let bas: Int = 1
    let bat: Float = 0
    let bax: String = “default"
    let baz: Double

    // self.bar is known to be a String but does not have a default value
so it must be provided by the caller
    // self.bas is known to be an Int and implicitly has a default value
of 1 specified by the property
    // so it does not need to be provided by the caller
    // self.bat is known to be a Float, but the default is overridden to
be 1 instead of 0
    // external labels are provided that correspond to the names of the
properties
    init(self.bar, self.bas, self.bat = 1) {
// the initializer does not receive a value for baz and the property does
not provide a default
        // so it must be initialized here before the first phase of
initialization is complete
        // if all stored properties had received values through parameters
or through property defaults
        // the first phase of initialization would have already been
completed when the body of the initializer was entered
        self.baz = someComputedDoubleValue()
        // now all stored properties have been initialized so the first
phase of initialization is complete
    }
}

This structure allows us to remove the boilerplate of the property type
and default value.

Given the significant difference from regular function parameters, it may
make sense to set these apart syntactically in some way, although I am not
sure what would make the most sense. For example, we could just have a
second parameter tuple in the initializer declaration which represents the
“member parameters” to the initializer like this:

init(foo: Int)(self.bar, self.bas, self.bat = 1)

or a vertical pipe like this:

init(foo: Int | self.bar, self.bas, self.bat = 1)

Setting these “member parameters” apart syntactically could also
facilitate additional mechanisms to further reduce boilerplate if we find
that the same group of “member parameters" exist for several different
initializers in the same type. For example, it might become possible to
implement a memberwise initializer like this:

// @automembers expands to match all stored properties
init()(@automembers) {}

At this point we have eliminated virtually all of the boilerplate,
encouraging developers to use concise *and* safe, yet still flexible
initialization strategies.

Matthew

On Dec 4, 2015, at 9:05 PM, Dan Appel <dan.appel00@gmail.com> wrote:

I'm not sure how I feel about this, but in either case, why limit it to
default parameters? It's useful in other situations, too.

Take this for example:
struct Foo {
    let bar: String
    let bas: Int
    let baz: Double
    init(self.bar: String, self.bas: Int, bax: Int) {
        self.baz = Double(bax)
    }
}

where the need to say

self.bar = bar
self.bas = bas

is now avoided.

On Fri, Dec 4, 2015 at 3:16 PM Tal Atlas <me@tal.by> wrote:

There’s lots of boilerplate of initializing structs with stored
properties and initializer parameters. I’d like to create a syntax for
alleviating that in the 90% case.

struct Foo {
    let bar: String
    let baz: Int

    init(self.bar: String = "default", counter self.baz: Int) {
    }
}

This would be identical to:

struct Foo {
    let bar: String
    let baz: Int

    init(bar: String = "default", counter baz: Int) {
        self.bar = bar
        self.baz = baz
    }
}

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

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

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

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


(Matthew Johnson) #7

Did you read through by reply to the original proposal? I believe it provides the convenience you're looking for here while still allowing for the flexibility to avoid boilerplate in initializers that do more than just set properties.

The syntax I used is a bit different, but not too much for the default initializer case (and isn't as important as expressive power IMO).

If you believe we should have a feature focused strictly on memberwise initializers and not also allow for less boilerplate in more sophisticated initializers I am interested to hear your rationale.

···

Sent from my iPhone

On Dec 5, 2015, at 3:03 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

There’s lots of boilerplate of initializing structs with stored properties and initializer parameters. I’d like to create a syntax for alleviating that in the 90% case.

struct Foo {
   let bar: String
   let baz: Int

   init(self.bar: String = "default", counter self.baz: Int) {
   }
}

I’d like to propose an alternative approach.

Swift already generates a default initializer for structs that don’t have any initializers explicitly defined. However, the moment you write init(, that initializer disappears. It’s also never available on classes, because they have to dispatch to another initializer.

I propose that we make it possible to explicitly ask for that default initializer:

   struct Foo {
       let bar: String
       let baz: Int

       init() {
           bar = “default”
           baz = 0
       }

       init default // automatically generates init(bar: String, baz: Int)
   }

Moreover, I propose we extend this to classes:

   struct Foo: Bar {
       let bar: String
       let baz: Int

       init() {
           bar = “default”
           baz = 0
       }

       init default() // automatically generates init(bar: String, baz: Int) with super.init()
   }

In a class, the “default” statement must have parens after it; these indicate the parameters to be added for the superclass initializer. If you need to super up to an initializer that takes parameters, you add the needed parameters there:

   struct Foo {
       let bar: String
       let baz: Int

       init() {
           bar = “default”
           baz = 0
       }

       init default(quux: Double) // automatically generates init(quux: Double, bar: String, baz: Int) with super.init(quux: quux)
   }

If you super up to a failable initializer, of course, that will have to be “init? default(quux: Double)”.

Now, one little quirk of the current system is that Swift may actually generate *two* default initializers: a parameterless initializer when all properties have default values, and a memberwise initializer which requires all values be provided. I further propose that these be unified into one default initializer by giving default values to parameters which map to properties with default values. This gives you all the parameter sets currently supported, plus a whole range in between.

   struct Foo {
       let bar: String = “default"
       let baz: Int

       init() {
           bar = “default”
           baz = 0
       }

       init default // automatically generates init(bar: String = “default", baz: Int)
   }

Finally, you can continue to decorate the “init” keyword with attributes as you normally would.

   public init default
   internal init default
   private init default
   required init default
   override init default
   @objc init default
   @objc public required init default

(I don’t think convenience initializers make much sense here.)

This does not, unfortunately, eliminate boilerplate in initializers which do more than just set properties. Nor does it allow you to rename parameters, as your example does with “counter”.

--
Brent Royal-Gordon
Architechies

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


(Chris Lattner) #8

There’s lots of boilerplate of initializing structs with stored properties and initializer parameters. I’d like to create a syntax for alleviating that in the 90% case.

As a general comment, at this point, I’d personally prefer to stay away from purely syntactic sugar syntax optimizations, unless they are very broadly applicable across the language. There are a ton of core things still missing from the swift language and type system, and I’d prefer that we stay focused on that. Post-Swift 3 we can consider adding a hygienic macro system, and the baking out of other language features may lead to the eliminate for some of the sugar that would otherwise be added.

I’d like to propose an alternative approach.

Swift already generates a default initializer for structs that don’t have any initializers explicitly defined. However, the moment you write init(, that initializer disappears. It’s also never available on classes, because they have to dispatch to another initializer.

Yes, this is really unfortunate. The default memberwise initializer behavior of Swift has at least these deficiencies (IMO):

1) Defining a custom init in a struct disables the memberwise initializer, and there is no easy way to get it back.
2) Access control + the memberwise init often requires you to implement it yourself.
3) We don’t get memberwise inits for classes.
4) var properties with default initializers should have their parameter to the synthesized initializer defaulted.
5) lazy properties with memberwise initializers have problems (the memberwise init eagerly touches it).

One of the things missing is the ability to “utter” the memberwise initializer, I’d suggest syntax along the lines of:

  memberwise init() // memberwise is a "decl modifier"

So you could say:

  public memberwise init()

to specifically say you want the default one given public access control (for example).

It would be really great for someone to tackle these problems holistically!

-Chris

···

On Dec 5, 2015, at 1:03 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:


(Matthew Johnson) #9

Even though a private property is not visible to compilers it is visible to the compiler. If the developer makes a request to expose that property in an initializer the compiler could easily insert the type information in the signature of the initializer.

That said, the most important point I want to emphasize is the problems caused by boilerplate in initializers. I showed one way this problem could be solved building on the previous proposal syntactically. The exact solution and syntax don’t really matter that much to me but I would like to see Swift better support flexible and safe initialization without requiring boilerplate in one way or another.

If there is interest I will try to track down some examples to make the discussion more concrete.

···

On Dec 5, 2015, at 9:23 AM, Tal Atlas <me@tal.by> wrote:

I still think that it should include the type in the initializer definition because if you have private properties you’d need to expose the type information somehow and that type inference doesn’t work that way anywhere else in swift, so explicitly requiring the type information in the initializer is consistent with the rest of swift.

On Sat, Dec 5, 2015 at 12:20 AM <crk@fastmail.com <mailto:crk@fastmail.com>> wrote:
I'm not entirely sure what your proposal changes, other than omitting the argument labels and us wing self to refer to the property. Could you try a trivial example that would be runnable if the change was implemented?

Thanks for your time,
Cole Kurkowski

On Dec 4, 2015, 21:59 -0600, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>>, wrote:

I think this idea scratches at the surface of a problem with initializers that definitely merits some attention. Boilerplate is common in initializers which can lead to types that don’t allow as much flexibility to callers as might be desirable.

More importantly, it can also lead to “initialized” instances that are not fully or properly configured, (implicitly unwrapped?) optional members, and mutability that shouldn’t be necessary past the initial configuration stage of the instance. For example, if a type provides a family of initializers, but also has several members which are intended to be initialized / configured directly by callers a developer could choose to avoid the boilerplate by declaring the additional members as as an implicitly unwrapped optional var members. No doubt this is a bad idea. If the caller does not initialize all of the additional members there is a bomb waiting to go off. Furthermore, there is the potential for mutation after initialization that may not be expected or intended. By requiring a nontrivial amount of boilerplate to avoid this situation the language is unintentionally nudging lazy developers towards bad practices like this.

Let's start with the current proposal but go a bit further and see how much boilerplate can be removed. The type information is already known from the property declaration, and furthermore a default may also be available in the property declaration.

struct Foo {
    let bar: String
    let bas: Int = 1
    let bat: Float = 0
    let bax: String = “default"
    let baz: Double
    
    // self.bar is known to be a String but does not have a default value so it must be provided by the caller
    // self.bas is known to be an Int and implicitly has a default value of 1 specified by the property
    // so it does not need to be provided by the caller
    // self.bat is known to be a Float, but the default is overridden to be 1 instead of 0
    // external labels are provided that correspond to the names of the properties
    init(self.bar, self.bas, self.bat = 1) {
// the initializer does not receive a value for baz and the property does not provide a default
        // so it must be initialized here before the first phase of initialization is complete
        // if all stored properties had received values through parameters or through property defaults
        // the first phase of initialization would have already been completed when the body of the initializer was entered
        self.baz = someComputedDoubleValue()
        // now all stored properties have been initialized so the first phase of initialization is complete
    }
}

This structure allows us to remove the boilerplate of the property type and default value.

Given the significant difference from regular function parameters, it may make sense to set these apart syntactically in some way, although I am not sure what would make the most sense. For example, we could just have a second parameter tuple in the initializer declaration which represents the “member parameters” to the initializer like this:

init(foo: Int)(self.bar, self.bas, self.bat = 1)

or a vertical pipe like this:

init(foo: Int | self.bar, self.bas, self.bat = 1)

Setting these “member parameters” apart syntactically could also facilitate additional mechanisms to further reduce boilerplate if we find that the same group of “member parameters" exist for several different initializers in the same type. For example, it might become possible to implement a memberwise initializer like this:

// @automembers expands to match all stored properties
init()(@automembers) {}

At this point we have eliminated virtually all of the boilerplate, encouraging developers to use concise *and* safe, yet still flexible initialization strategies.

Matthew

On Dec 4, 2015, at 9:05 PM, Dan Appel <dan.appel00@gmail.com <mailto:dan.appel00@gmail.com>> wrote:

I'm not sure how I feel about this, but in either case, why limit it to default parameters? It's useful in other situations, too.

Take this for example:
struct Foo {
    let bar: String
    let bas: Int
    let baz: Double
    init(self.bar: String, self.bas: Int, bax: Int) {
        self.baz = Double(bax)
    }
}

where the need to say

self.bar = bar
self.bas = bas

is now avoided.

On Fri, Dec 4, 2015 at 3:16 PM Tal Atlas <me@tal.by <mailto:me@tal.by>> wrote:
There’s lots of boilerplate of initializing structs with stored properties and initializer parameters. I’d like to create a syntax for alleviating that in the 90% case.

struct Foo {
    let bar: String
    let baz: Int

    init(self.bar: String = "default", counter self.baz: Int) {
    }
}

This would be identical to:

struct Foo {
    let bar: String
    let baz: Int

    init(bar: String = "default", counter baz: Int) {
        self.bar = bar
        self.baz = baz
    }
}

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

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

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


(Brent Royal-Gordon) #10

Did you read through by reply to the original proposal? I believe it provides the convenience you're looking for here while still allowing for the flexibility to avoid boilerplate in initializers that do more than just set properties.

I did, and I didn’t really like it. One of the syntaxes it proposed was strange and ad-hoc; the other was a fairly arbitrary overloading of parentheses, although it doesn’t actually conflict with anything since you can’t overload “calling parentheses” on a non-function.

It’s important to ensure that, when the implementation evolves, you can replace the initializer transparently. For instance, suppose I have a type which represents a player in a game. It uses a stored property called `points`:

    class Player {
        var name: String
        private(set) var points: Int

        func addPoints(additionalPoints: Int) {
            points += additionalPoints
        }
        
        // Creates init(name: String, score: Int)
        init default()
    }

Later, I decide to add syncing, and realize I need to change this model. I need to add a sync identifier, and I want to change `points` to be computed from a series of `PointChange` objects. (This is a common strategy to avoid sync conflicts.) However, I don’t want to disrupt any of my existing code when I do this.

    class Player {
        let syncIdentifier = NSUUID()
        var name: String
        private var pointChanges: Set<PointChange>
        
        var points: Int {
            get { return pointChanges.reduce(0, combine: +) }
        }
        
        func addPoints(additionalPoints: Int) {
            pointChanges.insert(PointChange(offset: additionalPoints)
        }
        
        // We can no longer use init default(), but we can still create an initializer with the same signature
        init(name: String, points: Int) {
            self.name = name
            pointChanges = [ PointChange(offset: score) ]
        }
    }

By *not* separating the properties into a different syntactical construct from the constructor parameters, I can update my type’s implementation without affecting its interface. If properties were separated syntactically from constructor parameters as you proposed, it would not be possible to change this seamlessly.

If you believe we should have a feature focused strictly on memberwise initializers and not also allow for less boilerplate in more sophisticated initializers I am interested to hear your rationale.

Honestly, I would like to fix the boilerplate issue in more sophisticated initializers, but I don’t see a clean way to do that. The other proposals I’ve seen in this thread, along the lines of:

  init(self.name, self.points) {}

Frankly do too much violence to the parameter list. Swift’s declarations have a lovely feature where the declaration basically looks like the usage, except that the concrete values in the usage are replaced by types in the declaration. These proposals destroy that property—when I look at the declaration, I can no longer envision what the call site will look like.

Basically, I focused on default initializers because they’re *really* easy to solve and cover a good portion of the common case. I really have no idea how to solve the larger problem, and I haven’t liked any of the other ideas in this thread, but I do know how to solve this more limited problem.

···

--
Brent Royal-Gordon
Architechies


(David Waite) #11

A possible syntax, then:

  init(set name:String, set score:Int) { }

set would be a fourth parameter modifier alongside let, var, and inout - only valid on initializers (not as useful and likely confusing in other contexts). The local name has to match a parameter on the type. Like let/var (but unlike inout) usage of ‘set’ on a initializer parameter does not affect the caller or prototype conformance.

-DW

···

On Dec 5, 2015, at 3:31 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Did you read through by reply to the original proposal? I believe it provides the convenience you're looking for here while still allowing for the flexibility to avoid boilerplate in initializers that do more than just set properties.

I did, and I didn’t really like it. One of the syntaxes it proposed was strange and ad-hoc; the other was a fairly arbitrary overloading of parentheses, although it doesn’t actually conflict with anything since you can’t overload “calling parentheses” on a non-function.

It’s important to ensure that, when the implementation evolves, you can replace the initializer transparently. For instance, suppose I have a type which represents a player in a game. It uses a stored property called `points`:


(Matthew Johnson) #12

Thank you for taking the time to provide this feedback!

I was trying to focus more on the problem we need to solve and just quickly sketch something that would solve it by building on the previous proposal. I agree the syntax (and likely semantics) left a lot to be desired and should be vastly improved. I was primarily demonstrating that is *possible* to solve the problem, we just need to find the right solution.

I’m glad to hear you also believe it’s a problem worth solving and just didn’t like specific details of the solution. I do think it’s worth solving the more general problem even though a good solution may be more difficult to find. If we adopt a simpler solution like the one you propose for default initializers it is likely to significantly constrain our options for solving the more general problem (in order to play nice with the simpler solution you propose).

I also believe that this problem is intimately related to the problem addressed in the “setup closures” thread as a general solution would supersede the need for “setup closures".

I would like to try coming at this problem from a different angle and see if that solution feels any better to us. Let’s consider an @initializable attribute for members:

@initializable members that are set by the initializer are not generated as parameters by the compiler as the body of the initializer is taking responsibility for initializing that member. The initializer is free to manually declare the a parameter with the name of the member it is initializing and use that if desired but it is not required to do so. This will ensure boilerplate still works as expected when a programmer provides it.

@initializable members not set in the implementation of an initializer are generated as parameters automatically by the compiler at the end of the argument list (but before any variadic or trailing closure arguments) in the order the members are declared in the type itself. If the member declares a default value, the compiler uses that as the default value for the argument. Ideally the caller can re-order the labeled arguments as desired.

Private members are allowed to be @initializable and may be exposed through internal or public initializers. The member itself is still hidden outside the scope of the file but any internal or public initializers should still be able to accept an initial value from a caller if desired, just as is possible with manual boilerplate today.

The general form of an initializer with automatically generated parameters would look something like this:

init() {
    // Compiler generated code to set the members for which it generated parameters.

    // The initializer is now free to access the value proved by the caller by accessing the member which has now been initialized by the compiler generated code.

    // The initializer sets any members it needs to directly.
    // This includes all members not marked @initializable that do not have default values.
}

There are probably some additional details to work out in relative to two-phase initialization of classes, but I think this conveys the basic idea.

Here is an example:

struct S {
    @initializable let i: Int
    @initializable private let s: String = “s”
    let f: Float = 1
    let d: Double

    // i must be provided by the caller
    // s may be provided by the caller but is not required as there is a default value of “s”
    // f may be initialized directly but does not need to be as there is a default value of 1
    // d must be initialized directly by the initializer
    // callers see init(i: Int, s: String = “s")
    init() {
        // the initializer is free to read both i and s as the compiler automatically initializes them
        // with the value provided by the caller or the default value before the body of the initializer is entered
        d = Double(i)
        // f is initialized by the compiler to its default value
    }

    // s may be provided by the caller but is not required as there is a default value of “s”
    // callers see init(i: Int, j: Int, d: Double, s: String = “s")
    init(i: Int, j: Int, d: Double) {
        // the initializer is free to read s as the compiler automatically initializes it automatically
        // with the value provided by the caller or the default value before the body of the initializer is entered
        // this initializer may not read the value of i because the compiler does not automatically generate
        // an initializer for it as is initialized directly

        // the i parameter does not need to be used to directly initialize self.i and can be used to perform computation
        // if desired in order to provide backwards compatibility with existing code
        self.i = i + j
        self.d = d
        // f is initialized by the compiler to its default value
    }
}

One drawback to a solution like this is that it could result in the compiler generating parameters for an initializer that the developer did not intend but “forgot" to initialize directly.

When it does so for a member that does not contain a default value, tests and calling code should fail to compile calling attention to the problem. In this case the problem is not too severe.

On the other hand, if the member declares a default value calling code would still compile and the member would be initialized to the default value. This is identical to the behavior of member not marked @initializable that a developer “forgets” to initialize if it declares a default value (i.e. the correct behavior for the initializer would require a value different than the default). The primary difference is the compiler generated parameter for the @initializable member that the implementer “forgot” to initialize, potentially allowing a caller to provide a value and initialize the instance in an incorrect way that is *different* than the default value which should not have been used by the initializer.

Another drawback to this approach is that it is not explicitly clear when reading an initializer’s parameter list. One way to mitigate this would be to require the initializer itself to be marked with @initializable in order to opt-in to the compiler generated parameters. This at least alerts the reader that they exist. It provides less information than the "init default()” approach as some @initializable members might be initialized directly in the initializer body which would suppress the compiler generated parameter. However, the semantics of @initializable are pretty straightforward so I don’t think this would cause too much confusion.

Documentation generated for the initializer, autocomplete, etc would all expose the full parameter including the parameters the compiler generated for @initializable members.

I would like to revisit the Player example to see how it would fare using this approach:

   class Player {
       @initializable var name: String
       @initializable private(set) var points: Int

       func addPoints(additionalPoints: Int) {
           points += additionalPoints
       }

       // the previous post stated ‘Creates init(name: String, score: Int)' which does not match the members
       // this has been modified to match the members as was likely intended
       // Creates init(name: String, points: Int)
       init() {}
   }

later in Player’s life:

   class Player {
       let syncIdentifier = NSUUID()
       var name: String
       private var pointChanges: Set<PointChange>

       var points: Int {
           get { return pointChanges.reduce(0, combine: +) }
       }

       func addPoints(additionalPoints: Int) {
           pointChanges.insert(PointChange(offset: additionalPoints)
       }

       // We can no longer use the compiler generated parameters, but we can still create an initializer with the same signature.
       // Furthermore, if we do not need to be concerned about the declaration order of keyword arguments
       // we can now leave off the boilerplate for “name” and allow the compiler to generate it
       // without requiring any changes to call sites.
       init(name: String, points: Int) {
           self.name = name
           pointChanges = [ PointChange(offset: points) ] // the original post had score here which did not match the parameter name
       }
   }

Does this solution look any better to you than the previous idea?

···

On Dec 5, 2015, at 4:31 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

Did you read through by reply to the original proposal? I believe it provides the convenience you're looking for here while still allowing for the flexibility to avoid boilerplate in initializers that do more than just set properties.

I did, and I didn’t really like it. One of the syntaxes it proposed was strange and ad-hoc; the other was a fairly arbitrary overloading of parentheses, although it doesn’t actually conflict with anything since you can’t overload “calling parentheses” on a non-function.

It’s important to ensure that, when the implementation evolves, you can replace the initializer transparently. For instance, suppose I have a type which represents a player in a game. It uses a stored property called `points`:

   class Player {
       var name: String
       private(set) var points: Int

       func addPoints(additionalPoints: Int) {
           points += additionalPoints
       }

       // Creates init(name: String, score: Int)
       init default()
   }

Later, I decide to add syncing, and realize I need to change this model. I need to add a sync identifier, and I want to change `points` to be computed from a series of `PointChange` objects. (This is a common strategy to avoid sync conflicts.) However, I don’t want to disrupt any of my existing code when I do this.

   class Player {
       let syncIdentifier = NSUUID()
       var name: String
       private var pointChanges: Set<PointChange>

       var points: Int {
           get { return pointChanges.reduce(0, combine: +) }
       }

       func addPoints(additionalPoints: Int) {
           pointChanges.insert(PointChange(offset: additionalPoints)
       }

       // We can no longer use init default(), but we can still create an initializer with the same signature
       init(name: String, points: Int) {
           self.name = name
           pointChanges = [ PointChange(offset: score) ]
       }
   }

By *not* separating the properties into a different syntactical construct from the constructor parameters, I can update my type’s implementation without affecting its interface. If properties were separated syntactically from constructor parameters as you proposed, it would not be possible to change this seamlessly.

If you believe we should have a feature focused strictly on memberwise initializers and not also allow for less boilerplate in more sophisticated initializers I am interested to hear your rationale.

Honestly, I would like to fix the boilerplate issue in more sophisticated initializers, but I don’t see a clean way to do that. The other proposals I’ve seen in this thread, along the lines of:

  init(self.name, self.points) {}

Frankly do too much violence to the parameter list. Swift’s declarations have a lovely feature where the declaration basically looks like the usage, except that the concrete values in the usage are replaced by types in the declaration. These proposals destroy that property—when I look at the declaration, I can no longer envision what the call site will look like.

Basically, I focused on default initializers because they’re *really* easy to solve and cover a good portion of the common case. I really have no idea how to solve the larger problem, and I haven’t liked any of the other ideas in this thread, but I do know how to solve this more limited problem.

--
Brent Royal-Gordon
Architechies


(Matthew Johnson) #13

Thanks for chiming in on this thread. I definitely understand the desire to wait on features like this until post-Swfit 3 and possibly post-hygienic macros as well.

I would add to your list of memberwise initializer deficiencies the fact that it is “all or nothing”. I’m wondering what you think of generalizing our “utterance” of the memberwise initializer in a way that allows the flexibility for the initializer itself to handle initialization of some of the members while allowing the compiler to generate the implementation for other members. I described one idea for doing this in a post last night where I described an @initializable member attribute. I’m not tied to that specific solution, but I do think it is highly desirable to have complier-generated memberwise initialization that is more flexible than an “all or nothing” solution. What are your thoughts?

Matthew

···

On Dec 6, 2015, at 12:34 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 5, 2015, at 1:03 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

There’s lots of boilerplate of initializing structs with stored properties and initializer parameters. I’d like to create a syntax for alleviating that in the 90% case.

As a general comment, at this point, I’d personally prefer to stay away from purely syntactic sugar syntax optimizations, unless they are very broadly applicable across the language. There are a ton of core things still missing from the swift language and type system, and I’d prefer that we stay focused on that. Post-Swift 3 we can consider adding a hygienic macro system, and the baking out of other language features may lead to the eliminate for some of the sugar that would otherwise be added.

I’d like to propose an alternative approach.

Swift already generates a default initializer for structs that don’t have any initializers explicitly defined. However, the moment you write init(, that initializer disappears. It’s also never available on classes, because they have to dispatch to another initializer.

Yes, this is really unfortunate. The default memberwise initializer behavior of Swift has at least these deficiencies (IMO):

1) Defining a custom init in a struct disables the memberwise initializer, and there is no easy way to get it back.
2) Access control + the memberwise init often requires you to implement it yourself.
3) We don’t get memberwise inits for classes.
4) var properties with default initializers should have their parameter to the synthesized initializer defaulted.
5) lazy properties with memberwise initializers have problems (the memberwise init eagerly touches it).

One of the things missing is the ability to “utter” the memberwise initializer, I’d suggest syntax along the lines of:

memberwise init() // memberwise is a "decl modifier"

So you could say:

public memberwise init()

to specifically say you want the default one given public access control (for example).

It would be really great for someone to tackle these problems holistically!

-Chris

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


(Daniel Steinberg) #14

Yes! It feels silly to have to re-implement the default initializer just to make it public. I would love to see this.

Daniel

···

On Dec 6, 2015, at 1:34 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

So you could say:

public memberwise init()

to specifically say you want the default one given public access control (for example).


(Tal Atlas) #15

This syntax is confusing with that of defining the external keyword for the
parameter.

···

On Sat, Dec 5, 2015 at 9:24 PM David Waite via swift-evolution < swift-evolution@swift.org> wrote:

A possible syntax, then:

        init(set name:String, set score:Int) { }

set would be a fourth parameter modifier alongside let, var, and inout -
only valid on initializers (not as useful and likely confusing in other
contexts). The local name has to match a parameter on the type. Like
let/var (but unlike inout) usage of ‘set’ on a initializer parameter does
not affect the caller or prototype conformance.

-DW

> On Dec 5, 2015, at 3:31 PM, Brent Royal-Gordon via swift-evolution < > swift-evolution@swift.org> wrote:
>
>> Did you read through by reply to the original proposal? I believe it
provides the convenience you're looking for here while still allowing for
the flexibility to avoid boilerplate in initializers that do more than just
set properties.
>
> I did, and I didn’t really like it. One of the syntaxes it proposed was
strange and ad-hoc; the other was a fairly arbitrary overloading of
parentheses, although it doesn’t actually conflict with anything since you
can’t overload “calling parentheses” on a non-function.
>
> It’s important to ensure that, when the implementation evolves, you can
replace the initializer transparently. For instance, suppose I have a type
which represents a player in a game. It uses a stored property called
`points`:

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


(Chris Lattner) #16

That would be great of course, but it depends on the details. We’d need someone to make a specific proposal to hash out whether and how it can work.

-Chris

···

On Dec 6, 2015, at 7:41 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Thanks for chiming in on this thread. I definitely understand the desire to wait on features like this until post-Swfit 3 and possibly post-hygienic macros as well.

I would add to your list of memberwise initializer deficiencies the fact that it is “all or nothing”. I’m wondering what you think of generalizing our “utterance” of the memberwise initializer in a way that allows the flexibility for the initializer itself to handle initialization of some of the members while allowing the compiler to generate the implementation for other members. I described one idea for doing this in a post last night where I described an @initializable member attribute. I’m not tied to that specific solution, but I do think it is highly desirable to have complier-generated memberwise initialization that is more flexible than an “all or nothing” solution. What are your thoughts?


(Jacob Bandes-Storch) #17

The ability to use `var` in parameter lists / pattern matching is being
removed:
https://github.com/apple/swift-evolution/blob/7dc4dc47c43647dd1e60fe17074959dc3f056468/proposals/0003-remove-var-parameters-patterns.md

Jacob

···

On Sat, Dec 5, 2015 at 6:46 PM, David Waite via swift-evolution < swift-evolution@swift.org> wrote:

I don’t believe any more so than for let or var, which this effectively is
a complement for.

func foo(var externalName internalName:String) { print(internalName) }

-DW

On Dec 5, 2015, at 7:25 PM, Tal Atlas <me@tal.by> wrote:

This syntax is confusing with that of defining the external keyword for
the parameter.

On Sat, Dec 5, 2015 at 9:24 PM David Waite via swift-evolution < > swift-evolution@swift.org> wrote:

A possible syntax, then:

        init(set name:String, set score:Int) { }

set would be a fourth parameter modifier alongside let, var, and inout -
only valid on initializers (not as useful and likely confusing in other
contexts). The local name has to match a parameter on the type. Like
let/var (but unlike inout) usage of ‘set’ on a initializer parameter does
not affect the caller or prototype conformance.

-DW

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


(Brent Royal-Gordon) #18

I don’t believe any more so than for let or var, which this effectively is a complement for.

There is no Let or Var type in the standard library, but there is a Set type. “Set” has many meanings, and some of them are relatively likely to be used as parameter keywords.

···

--
Brent Royal-Gordon
Architechies


(David Waite) #19

I don’t believe any more so than for let or var, which this effectively is a complement for.

func foo(var externalName internalName:String) { print(internalName) }

-DW

···

On Dec 5, 2015, at 7:25 PM, Tal Atlas <me@tal.by> wrote:

This syntax is confusing with that of defining the external keyword for the parameter.

On Sat, Dec 5, 2015 at 9:24 PM David Waite via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
A possible syntax, then:

        init(set name:String, set score:Int) { }

set would be a fourth parameter modifier alongside let, var, and inout - only valid on initializers (not as useful and likely confusing in other contexts). The local name has to match a parameter on the type. Like let/var (but unlike inout) usage of ‘set’ on a initializer parameter does not affect the caller or prototype conformance.

-DW


(David Waite) #20

There is also a set keyword in the language already, although today its use is limited such that it may still be used as an identifier in non-conflicting contexts.

-DW

···

On Dec 5, 2015, at 7:51 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

I don’t believe any more so than for let or var, which this effectively is a complement for.

There is no Let or Var type in the standard library, but there is a Set type. “Set” has many meanings, and some of them are relatively likely to be used as parameter keywords.

--
Brent Royal-Gordon
Architechies