[Proposal Draft] Flexible memberwise initialization

My proposal is specifically suggesting that we treat “initial value” as a default rather than an initialization that always happens. IMO the current behavior is limiting and problematic in a number of ways.

If we make the change I am suggesting double initialization / assignment will not happen.

Maybe I should have stated the question more clearly:

What do you think the downsides are of allowing synthesized memberwise initialization to supercede an “initial / default value”?

If initializers cannot initialize the member to something other than the “initial value” then “initial values” are largely useless for immutable members. Every instance gets the same value, thus wasting space. Of course there are exceptions to this such as computed initializers, reference types, etc. where this isn’t exactly true, but in a large number of cases what is really desired is a default value that is used when the initializer doesn’t initialize the member with a different value.

Matthew

···

On Dec 22, 2015, at 11:22 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 22.12.2015 um 05:43 schrieb Matthew Johnson via swift-evolution <swift-evolution@swift.org>:

What do you think the downsides are of synthesizing memberwise initialization for properties with an “initial value”?

When using the memberwise initializer those properties are always assigned a value twice. First the initial value and then the value provided by the initializer. The first value is constructed and assigned unnecessarily and in corner cases this might even be problematical, e.g. if creating a value of a certain kind has a side effect like incrementing an instance counter or logging something.

-Thorsten

Hi,

Regardless of anything else in the proposal, I strongly dislike its attempted subversion of `let` properties.

struct A {
  let member = 1 // It’s done, over. If that’s not what you want, don’t initialize it here.
}

Cheers,
Guillaume Lessard

Yes, I was actually going to suggest proposing this as a general way to address this concern. Glad to hear it’s already in the works!

As for the proposal, it offers you the ability to retain full control over initialization. You can even use it in v1 and switch to a manual implementation of initializers in v2 if that is necessary to uphold the existing contract. Use it when it provides value and avoid it when it doesn’t.

Matthew

···

On Dec 22, 2015, at 12:25 PM, David Owens II <david@owensd.io> wrote:

On Dec 22, 2015, at 10:15 AM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:

We would like to integrate this into the package manager as well, so it helps “enforce” proper semantic versioning.

This would be awesome to have, especially integrated in. The way we handle this internally is less than ideal, so have a standard way would be great.

-David

Problem 2: This can cause very surprising performance issues, because it forces the let property to be stored. With the previous example, it is a goal for us to be able to compile:

public class X {
  let a = 42
}

into the equivalent of:

public class X {
  var a : Int { return 42 }
}

because people like to use local lets as manifest constants (avoiding “magic numbers”). With your proposal, we’d lose this capability, and we’d have to store them any time there is a memberwise initializer.

Neither of these problems apply to vars, which is why I think we can support vars in this model, but not lets.

Sorry to sidetrack the discussion, but does that mean that using `let` properties in a type where memory layout is important is possibly not forward-compatible?

Félix

Hi Chris,

I have given your feedback a lot of thought and have taken another run at this proposal from a slightly different angle. I believe it is a significant improvement.

Hi Matthew,

I continue to really like the approach and direction. Here’s an attempt to respond to both of your responses, I hope this comes across somewhat coherent:

Excellent, thanks! I have completed a third draft of the proposal. It may (probably does) still require further refinement but I believe it is another solid step forward.

Here’s the complete draft: https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md

Here’s the commit diff in case that is more helpful: Third draft · anandabits/swift-evolution@8287b67 · GitHub <Third draft · anandabits/swift-evolution@8287b67 · GitHub;

Discussion on a couple of topics continues inline below as well.

I hope you’re willing to entertain on some discussion on some aspects of the proposal that you are not immediately sold on. :)

Yes, absolutely.

In the case of let properties, I’m uncomfortable with this behavior and it contradicts our current init rules (the synthesized code isn’t legal). Please change the example to var properties, and then it’s can fit with the model :-).

I understand that this is not legal under the current init rules because the compiler currently produces a synthesized initialization of the properties to their initial values at the beginning of the initializer. I actually don’t like this behavior because it doesn’t allow an initializer body to initialize a let property with an "initial value”.

I’m pretty sure I’m not alone in this. People want to use immutable properties with “default values” (I believe this is what most Swift developers are calling what the Swift team refers to as “initial values”) without a requirement that all instances actually have that value. It was actually pretty surprising to me, and I’m sure to others as well, to discover this limitation. I actually thought it was a limitation of the current implementation rather than something that was intentionally designed. I’m surprised to hear otherwise.

I completely agree with your desire to support this, and I’m sure that if we did, that a ton of people would use it and love it. However, I really don’t think this is a good idea.

There are two major problems:

Problem 1: supporting this would *prevent* us from allowing memberwise initializers to be public. A really important feature of the memberwise design is that it is “just sugar” and that people can write the initializer out long hand if needed (e.g. if they want to add or reorder members without breaking API). With your proposed design, I could write:

public class X {
  let a = 42
  public memberwise init(...) {}
}

and use it with: X(a: 17). However, there is no way in swift to write that initializer out longhand. This is a critical problem to me.

Maybe it wasn’t clear, but I was suggesting that this particular rule be changed for all initializers. That would have made it possible to write the initializer out longhand. But I have pulled that part of the proposal so it doesn’t matter now.

Problem 2: This can cause very surprising performance issues, because it forces the let property to be stored. With the previous example, it is a goal for us to be able to compile:

public class X {
  let a = 42
}

into the equivalent of:

public class X {
  var a : Int { return 42 }
}

because people like to use local lets as manifest constants (avoiding “magic numbers”). With your proposal, we’d lose this capability, and we’d have to store them any time there is a memberwise initializer.

I would have never considered writing a type with an instance member with constant value that cannot be assigned a different value in the initializer as I would have considered it wasted space and possibly worthy of a compiler error. I would have written:

public class X {
  static let a = 42
}

Clearly I was underestimating the optimizer! I can see value in the ability to refer to the member without a prefix. Given the (guaranteed?) optimization it definitely makes sense.

Neither of these problems apply to vars, which is why I think we can support vars in this model, but not lets.

As you said you agree with the desire to support this behavior, maybe you would be comfortable with a different approach. Rather than using the initial value we could use an attribute:

public class X {
  @default(42) let a
}

This would allow us to still support the desired memberwise initialization behavior without conflicting with the current “initial value” behavior. I’m have updated the proposal to specify the behavior this way. If you don’t like it let’s continue the discussion.

It could be called @default, @memberdefault, @memberwisedefault, etc. The name doesn’t really matter to me.

It is irrelevant to the current proposal, but this attribute might also be useful in longhand cases allowing the “magic value” to be specified in a single location:

struct S {
  let value: Int?
}

public class X {
  @default(42) let a // same potential utility if this is a var

   // default is a keyword that is only valid in an expression
   // on the rhs of a member initialization statement
   // or possibly an initializer parameter with a name corresponding to a stored property

   init(s: S) {
      self.s = s.value ?? default
   }
   init(int a = default) { … }
}

As an alternative to specifying a value directly in the attribute, it could require a `let` member name, although my initial reaction is that this is more verbose and less good:

public class X {
   let defaultForA = 42
  @default(defaultForA) let a
}

That said, I think the interaction of explicit initializers and memberwise initializers begs discussion. It would be a much simpler model to only get memberwise parameters for properties without an explicit init. Have you considered this model, what are the tradeoffs with allowing vars to overwrite them? Allowing an explicit init to be overwritten by the memberwise initializer seems potentially really confusing, and since you allow explicit arguments on inits, this could always be handled manually if someone really really wanted it. For example, they could write:

memberwise init(s : String) {
  self.s = s
}

If they wanted to get the sugar of memberwise inits (presumably for other properties without an explicit init) but still allow one to be overwritten.

Personally, I think there is a lot of value in allowing memberwise initialization for properties that do contain an initial value. Imagine a hypothetical Swift version of Cocoa Touch. UILabel might have initial values for text, font, textColor, etc but still want to allow clients to provide memberwise initialization arguments to override the default value. I think there are many cases like this both in UI code and elsewhere.

I think I confused the issue. If we have to support properties that have a default value, then the model I’m advocating for is that this:

class C {
  let x : Int
  var y : Int = foo()

  memberwise init(...) {}
}

compile into:

init(x : Int, y : Int = foo()) {
  self.x = x
  self.y = y
}

Pertinent points of this are that lets without a default value would still turn into arguments, and that any side effects of the var initializer would be squished. Another potential model is to compile it to:

init(x : Int, y : Int) {
  self.x = x
  self.y = foo()
}

which is guaranteed to run the side effect, but requires y to be specified. I do not think it is a good model to compile it to:

init(x : Int, y : Int? = nil) {
  self.x = x
  self.y = y ?? foo()
}

because that would allow passing in an Int? as the argument. The final model (which I know you don’t like) is for memberwise initializers to *only* apply to properties without a default value.

This doesn’t seem like the right behavior to me. The compiler shouldn’t be in the business of scanning the body of the init to decide what members are explicitly initialized. I’d suggest that the model simply be that the contents of the {} on a memberwise init get injected right after the memberwise initializations that are done. This mirrors how properties with default values work.

The model does inject the synthesized memberwise initialization just prior to the body of the initializer.

As written the proposal does scan the body of the init in order to determine which properties receive memberwise initialization. The idea here is that it provides additional flexibility as it allows specific initializers to “opt-out” of memberwise initialization synthesis for some properties while receiving it for others.

This is a very problematic model for me, because it can lead to serious surprises in behavior, and in the case of lets, shares the problems above with not allowing one to define the long-hand form explicitly.

I’m glad I found an alternative that you like better. :)

I don’t think the proposal changes lazy properties.

I agree, I was saying that I like that :)

@nomemberwise is an interesting extension, but since it is a pure extension over the basic model, I’d suggest moving this into a “possible future extensions” section. The proposal doesn’t need this feature to stand on its own.

Allowing type authors to restrict memberwise initialization to a subset of properties is an important aspect of “flexible” memberwise initialization IMO. In my mind, this is about allowing the author of a type to segment properties that are “user configurable” from properties that are implementation details.

I understand, but it is a pure extension to the basic proposal. The proposal is complex enough as it is, so inessential parts should be split out for discussion and implementation. I’m not saying that we shouldn’t do @nomemberwise in (e.g.) the swift 3 timeframe, I’m saying that it should be a separate discussion informed by the design and implementation process of the base proposal.

That makes sense. I have updated the proposal to reflect this. Incremental change, right? :)

This also seems unclear to me. We’re generally very concerned about tightly coupling derived classes to their bases (in an API evolution scenario, the two classes may be in different modules owned by different clients). Further, the base class may have multiple inits, and it wouldn’t be clear which one to get the arguments from.

I know there are a lot of complexities here. It is certainly possible I am missing some showstoppers, especially related to resilience, etc.

User code would of course need to provide sufficient parameters to disambiguate the call to the base class initializer.

The goal in this section is to enable memberwise initialization to be used in a class hierarchy. I think UIKit is a good case to consider for the sake of discussion. In a hypothetical Swift version of UIKit we would want to allow memberwise initialization of appearance attributes regardless of where they are declared in the class hierarchy. (I would hope a designed-for-Swift UIKit would not rely so heavily on inheritance, but nevertheless I think it makes good case study for discussing flexible memberwise initialization).

The same mechanism that handles inheritance should also be able to handle delegating and convenience initializers. The idea is to write a delegating or convenience initializer that wraps the non-memberwise portion of a memberwise initializer while still allowing the memberwise initialization to “flow through” and be visible to callers of the delagating / convenience initializer.

I attempted to outline a basic strategy for handling propagation of memeberwise initialization through the inheritance hierarchy as well as other cases of initializer delegation in the detailed design. It is definitely not complete, almost certainly has flaws and omissions, etc. I’m hoping we can flesh out the details through community discussion.

I hope you will agree that it is important to support inheritable memberwise initialization and that we do need to address this in some way.

I don’t agree. memberwise is a sugar feature intended to handle the most common scenarios. One of the major reasons we don’t support memberwise inits in classes today is that we have no ability to know what superclass init to call (and root classes aren’t interesting enough to provide a solution that only works for them).

Given that your proposal allows user code to be added to the synthesized init, I’d really strongly prefer that this be a compile time error, because super.init was never invoked (ok ok, if the superclass has an "init()” as its only DI then yes, we can synthesize it by default like we do today).

class Derived: Base {
    let derivedProperty: Int

    // user declares:
    memberwise init() {}
    // compiler synthesizes (adding forwarded memberwise parameters):
    init(baseProperty: String, derivedProperty: Int) {
        self.derivedProperry = derivedProperty
        super.init(baseProperty: baseProperty)
    }
}

I absolutely agree that the compiler should not be in the business of making a guess about what superclass initializer needs to be called!

This was actually a mistake in the proposal and isn’t what I intended. I’m not sure how I missed it and am embarrassed by the oversight! The intention is to require subclasses to make a call to the superclass initializer that is unambiguous on its own. So the example as it exists should produce a compiler error just like you thought!

The example was intended to include a call to provide all of the arguments necessary to disambiguate prior to memberwise argument forwarding. In this case of the present example none are necessary. I am including the corrected example here and have also updated the proposal. It should have read like this:

class Base {
    let baseProperty: String

    // user declares:
    memberwise init(...) {}
    // compiler synthesizes:
    init(baseProperty: String) {
        self.baseProperty = baseProperty
    }
}

class Derived: Base {
    let derivedProperty: Int

    // user declares:
    memberwise init(...) { super.init(...) }
    // compiler synthesizes (adding forwarded memberwise parameters):
    init(baseProperty: String, derivedProperty: Int) {
        self.derivedProperry = derivedProperty
        super.init(baseProperty: baseProperty)
    }
}
<GitHub - anandabits/swift-evolution at flexible-memberwise-initialization;

This is obviously a trivial example and far more complex scenarios are possible. Are you willing to continue exploring inheritance support in the manner I intended, where no guesswork by the compiler is necessary? The solution would need to make everything unambiguous in user code alone.

I understand there also potentially resilience concerns around supporting inheritance. If you’re willing to dive into specifics I may (or may not) be able to find solutions. If I am not able to identify solutions, at least I have tried and understand the specific issues involved. :)

If you’re willing to do this I would be happy to work through any scenarios necessary regardless of how complex they get. :)

I personally don’t like inheritance very much and consider it a tool of last resort, but I do think it is best for a feature like flexible memberwise initialization in a language that offers inheritance should support it if at all possible.

Instead, the user should have to write:

memberwise init(baseProperty : Int, ...) {
  super.init(baseProperty: baseProperty)
}

This doesn’t reduce the utility of this feature, and it preserves separability of class from superclass.

Ideally we could avoid writing this manually. We still have an M x N problem (M inherited properties, N subclass initializers).

Writing that manually would’t reduce the utility of the feature in cases where it is applicable, but it does severely limit its applicability to inheritance hierarchies.

Thank you again for pushing this forward!

-Chris

You’re welcome! I know this proposal is “just” syntactic sugar, but I believe it will have an impact on initialization designs in practice.

I really appreciate the time you’re taking to get into the details and help refine the proposal!

Matthew

···

On Dec 22, 2015, at 1:29 PM, Chris Lattner <clattner@apple.com> wrote:

On Dec 22, 2015, at 8:46 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Ah, ok!

I'm a bit uneasy about overloading the initial-value-syntax with a new meaning, though.

-Thorsten

···

Am 22.12.2015 um 18:30 schrieb Matthew Johnson <matthew@anandabits.com>:

My proposal is specifically suggesting that we treat “initial value” as a default rather than an initialization that always happens. IMO the current behavior is limiting and problematic in a number of ways.

If we make the change I am suggesting double initialization / assignment will not happen.

I was guessing that the current proposal does not change anything re. default and current member wise initializers and so in addition to suggesting Scala syntax I was also suggesting the transformation shown, or its equivalent. The advantage of having a member wise init that has default arguments and argument labels are considerable:

1. Allows lets as well as vars

The proposal does allow lets as well as vars. It is very unfortunate that defaults for lets cannot be supported immediately. Doing that is a highly desired enhancement. If you read Chris Lattner’s comments in the history of this thread you will have more background on the issues involved.

2. Allows partial custom initialization

I don’t know for sure what you mean by this but the proposal does allow partial custom initialization in the way I think of that phrase.

3. Eliminates need for other mechanisms, i.e. default and existing member wise initialization

These facilities could be added to `memberwise init(...)` as well. In particular, if a member wise init was present then an initialized property could have a label, e.g.:

I do not think allowing custom labels for memberwise initialization parameters is a good idea. The caller is initializing a member directly. It is more clear if the name of the parameter matches the name of the parameter.

     class C {
         let example name: Type = initial
         memberwise init(...)
     }

Would become the equivalent of:

     class C {
         let name: Type
         init(example name: Type = initial) {
             self.name <http://self.name/&gt; = name
         }
      }

The Scala syntax is just a shorter alternative, ideally there be a discussion of the pros and cons of the two syntax that included the possibility of the wider set of objectives as outlined in the numbered points above.

It was not a goal of this proposal to provide the most concise syntax for simple cases. The goal of this proposal is to provide a flexible and scalable memberwise initialization facility. Specific goals included supporting more than one memberwise initializer as well as allowing some properties to be memberwise initialized and some (private state, etc) to be initialized by other means.

An independent proposal focused on making simple cases as concise as possible is something that could also be pursued.

···

On Jan 4, 2016, at 11:31 PM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

On Tuesday, 5 January 2016, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 4, 2016, at 5:48 PM, Howard Lovatt <howard.lovatt@gmail.com <javascript:_e(%7B%7D,'cvml','howard.lovatt@gmail.com');>> wrote:

Yes you can get close, but:

1. Its weird that you can only do this in an extension.

This is the way the current implicit initializer works. It is not synthesized if you define any initializers in the body of the type. There are good reasons it works this way and the current proposal does not change those rules.

2. Its not quite the same as the proposal the current member-wise initialiser does not make the init arguments optional (the arguments themselves do not have defaults), i.e. with your example `let defaultOriginRect = Rect(size: Size(width: 5.0, height: 5.0))` fails whereas it would work for the proposal (this could also be true if the existing struct memberwise init and the new `memberwise init(..)` where changed to provide init argument defaults).

The implicit memberwise initializer currently in the language does not provide defaults for parameters. This proposal changes that behavior and provides defaults if the the member is a `var` and has an initial value.

Unfortunately I was not able to find a solution to allow synthesized parameters for `let` members to have default values. This is because the current semantics for `let` members do not allow the member to be initialized to anything other than the initial value if one is provided. I am hoping a solution to this will be identified in the future and have suggested one possible mechanism `@default` in the future enhancements section.

3. Only ‘really' works for structs, the compiler doesn’t write a member-wise initialiser for classes (just a default initializer).

That is true about the current behavior of the language but is not true with regards to the current proposal.

4. Still need the compiler to provide both default and member-wise initialisers, whereas this proposal would allow the existing default and member-wise initialisers to be deprecated and just the new member-wise initialiser would remain which would simplify the language and make it clear what was happening (this could also be true if a `memberwise init(..)` where added and existing compiler written inits removed).

This proposal does not change anything with regard to the default initializer.

On 5 Jan 2016, at 10:16 AM, Matthew Johnson <matthew@anandabits.com <javascript:_e(%7B%7D,'cvml','matthew@anandabits.com');>> wrote:

struct Rect { var origin: Point = Point(), size: Size = Size() }
extension Rect {
       init(center: Point, size: Size) {
           let originX = center.x - (size.width / 2)
           let originY = center.y - (size.height / 2)
           self.init(origin: Point(x: originX, y: originY), size: size)
       }
}

--
  -- Howard.

Here is an expanded proposal for the syntax for a Scala style memberwise syntax and equivalent code, specification is via an example rather than formal syntax since this is easier to follow. Note it is like Scala’s syntax but it is ‘Swiftified” (in particular still retains `init` keyword).

    class Ex public init(
        superParam: sPType = sPInitial,
        private label privateParam: pPType = pPInitial,
        calculatedParam: cPType = cPInitial
    ): SuperType(superParam) {
        calculatedParam: cPType {
            get {…}
            set {…}
        }
    }

This gets translated to:

    class Ex: SuperType(superParam) { {
        private privateParam: pPType = pPInitial,
        public init(superParam: sPType = sPInitial, label privateParam: pPType = pPInitial, calculatedParam: cPType = cPInitial) {
            // 1. Call super
            super.init(superParam)
            // 2. Initialize generated parameters and existing parameters
            self.privateParame = privateParam
            self.calculatedParam = calculatedParam
        }
        calculatedParam: cPType {
            get {…}
            set {…}
        }
    }

This translation is not valid Swift for several reasons. There are also several aspects of this translation that are somewhat vague and a detailed design is necessary to evaluate how you envision it working.

Because the syntax is so short it is part of this proposal to remove both the current default and memberwise initialisers thus simplifying the language overall (remove two features, add one) and at the same time gain power, which is a rare situation and therefore I would suggest optimal.

It is also more powerful than the proposed `memberwise init(..)` and/or existing automatic `inits` in the following ways:

1. Allows `lets` to have a default value.
2. Allows other properties including computed properties to have a default value.
3. Allows any `super.init` to be called (not restricted to `super.init()`).
4. Allows control of which properties participate in the `init` (they are listed in the brackets and are not in a super call and are not an existing property).
5. Allows private properties to be initialised.
6. Allows properties including private properties to have a label instead of their actual name and hence not expose internals (also allows migration of implementation whilst retaining external interface).
7. Allows calls to the generated `init` that don’t specify all members, i.e. for `struct Ex init(i: Int = 0, s: String = “") {}` the following are allowed `Ex()`, `Ex(i: 1)`, `Ex(s: “A”)`, and `Ex(i: 2, s: “B”)`.
8. Allows visibility of automatically generated `init` to be controlled.
9. Supports property behaviours.
10. Does not require a new keyword.

Several of these points are also true of my proposal and several others would be true if what I believe are the most important future enhancements are added.

The downsides of the proposal relative to `memberwise init(..)` and/or existing automatic `inits` are:

1. That people would need to be careful when laying out their code otherwise the first line could become long (a bit of pretty printing solves this).
2. Existing structs/classes that have automatically generated inits would need to be refactored, e.g. `CGRect` would become `struct CGRect init(var origin: CGPoint, var size: CGSize) {}` (a migration tool would help here).

Other than the downsides listed above the proposal does everything the current proposal and current implementation does and more (more also listed above) and is simpler to both explain and implement.

This is not true. There are additional differences.

The above more than addresses the reasons given in the current proposal for not using the Scala syntax and demonstrates superiority in many areas.

I disagree. The current proposal clearly states reasons not addressed here.

I don’t wish to

···

On Jan 6, 2016, at 4:52 PM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

However if it were the current proposal or nothing I would go with the current proposal since something is better than nothing.

On Wednesday, 6 January 2016, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 5, 2016, at 12:12 PM, Tino Heth <2th@gmx.de <javascript:_e(%7B%7D,'cvml','2th@gmx.de');>> wrote:

The “type parameter list” syntax is sugar that could be implemented as a layer on top of the current proposal or could be implemented orthogonally.

Hi Howard,
I've heard this argument before, so I'll repeat my answer as well:
Both offers don't make sense, it's either one way or the other (or something completely different).

I don’t think it’s clear whether both make sense or not. They are not mutually exclusive and are aimed at solving related, but different problems. If we adopt the current proposal, the Kotlin / Scala syntax *might* make sense for some use cases. It would depend upon whether the current proposal is considered too verbose in enough cases or not. This may or may not turn out to be the case.

The reason my proposal looks the way it does is because there are specific goals it intends to achieve and problems it is intended to solve. These goals and problems are not adequately addressed by the Kotlin / Scala syntax.

If diversity starts here, why not have "const" and "val" beside "let", or allow "fn" and "lambda"?

@Matthew: Please, if you support something, then say so - using clear words, not phrases like "could be implemented”.

This phrasing is plenty clear IMO. I am stating a possibility. I do not have a position on whether or not it is a good idea at the moment.

I have made some modifications to the proposal over the last few days. These changes have been partly motivated by considering the conversation we have had. You may wish to give it another look. If so, please look here: https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/0018-flexible-memberwise-initialization.md\. There is currently an open PR for the latest change so it is not in the main Swift evolution repo yet.

The most recent change includes a discussion of why it does not use the Scala / Kotlin syntax. You may not like the choice but I hope you can at least understand the rationale (even if you disagree with it).

--
  -- Howard.

I find it surprising that you key the initialization of 'var's based on whether their setter is visible or not. Initialization is not the same as setting, and memberwise initializers within the definition have private access to the member anyway; this is why `let` properties can be initialized, after all. `private(set)` is also a way for an API to communicate that a property is read-only without promising that it is or will always be immutable, so I think it's important that a 'private(set) var' be as capable as a 'let' to the maximum degree possible.

-Joe

Hi Joe,

Thanks for bringing this topic up and moving the discussion to the list (it’s hard to get into details on Twitter).

I am definitely sympathetic to the points you raise. The problem is that there is no good solution to this without involving at least one of the future enhancements. Chris feels strongly that we need to focus on the core functionality for the initial proposal so we must choose a solution without them.

Using the `var` setter visibility is the least bad option in my mind. There are many times when a var might represent internal state that a user is allowed to read, but should never be allowed to specify, whether via initialization or otherwise. These will have `private(set)` visibility.

If we allow memberwise initialization to expose them it will be a useless feature for types that contain a var like this. There will not be any way to specify that they should not participate in memberwise initialization without at least one of the future enhancements. On the other hand, if you do wish to expose them via the initializer it is easy to add a parameter and initialize the `var` manually.

This is also a safer solution. The author of the type has specifically stated that users should not be setting the value of the `var`. Maybe that doesn’t apply to initialization, but it is not the right decision to assume that IMO, at least without the ability to specify `init` visibility independently if desired.

In the case of `let`, if we do not use the only access control modifier they are allowed to have they would not be able to participate in memberwise initialization at all.

I think the proposal makes the least bad choice we can make without expanding it to include the ability to specify init visibility (which I would support if the core team was willing to do that).

I'm not sure what you mean by init visibility.

The proposal suggests a possible future enhancement for specifying access control for init distinct from get and set like: 'private public(init)'. This would be available to both 'let' and 'var' properties and would allow a single memberwise initialization rule to be used for all properties.

I feel like all these arguments could also be made for 'let'; what makes a get-only var different from the initializer's perspective?

This is true in some sense. The difference is that 'let' properties only have access control for a getter making it the only option we have if they are allowed to participate in memberwise initialization.

Here's an example. Think of a progress property. That will be a far with a public getter but a private setter. It would clearly be wrong if that were exposed by an initializer. If we use the getter for a 'var' in memberwise initialization it would be exposed to all memberwise initializers under the current proposal (without any of the enhancements).

Types with properties like this are not uncommon. Memberwise initialization would be useless with these types if the getter visibility was used.

There are several ways to solve this but Chris did not want to include any of them in the initial proposal. I went with what I feel is the least bad option remaining, which is to use the setter visibility for 'var' properties.

It is least bad because it will actually be the right thing a lot of the time and because you can still use memberwise initialization for other properties when it doesn't do what you need. Using the getter would be right some of the time (less often I think, but that's just a guess) but when it isn't you would have to avoid memberwise initialization for the entire type.

It is different than 'let' properties because it has to be if we want to do the least bad thing for 'var' properties and we also want to allow 'let' properties to participate in memberwise initialization.

Matthew

···

Sent from my iPad

On Jan 6, 2016, at 7:57 PM, Joe Groff <jgroff@apple.com> wrote:

On Jan 6, 2016, at 3:39 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 6, 2016, at 5:08 PM, Joe Groff <jgroff@apple.com> wrote:

-Joe

Yes I did mess up the translation, that will teach me to do it at night on
an iPad (therefore no compiler). The correct translation is:

Types and let's needed for example:

    public class SuperType {
        init(_: SPType) {}
    }
    public struct SPType {}
    public struct PPType {}
    public struct CPType {}
    let sPInitial = SPType()
    let pPInitial = PPType()
    let cPInitial = CPType()

Example:

    public class Ex public init(
        superParam: SPType = sPInitial,
        private label privateParam: PPType = pPInitial,
        calculatedParam: CPType = cPInitial
    ): SuperType(superParam) {
        var calculatedParam: CPType {
            get { return CPType() }
            set {}
        }
    }

Would be translated to:

    public class Ex: SuperType {
        private let privateParam: PPType
        public init(superParam: SPType = sPInitial, label
privateParam: PPType = pPInitial, calculatedParam: CPType = cPInitial) {
            // 1. Initialize generated stored properties and existing
stored properties, but not calculated properties
            self.privateParam = privateParam
            // 2. Call super
            super.init(superParam)
            // 3. Initialize calculated properties
            self.calculatedParam = calculatedParam
        }
        var calculatedParam: CPType {
            get { return CPType() }
            set {}
        }
    }

You say "There are also several aspects of this translation that are
somewhat vague"; without more detail it is difficult to know what you mean,
can you elaborate please. The proposal is meant to be equivalent to a
textual translation so I was hoping that an example would be sufficient;
happy to elaborate if you say what you don't get.

You say "Several of these points are also true of my proposal and several
others would be true if what I believe are the most important future
enhancements are added." Again can you elaborate, difficult to respond
without knowing what points and why. I am not trying to be awkward but I am
unsure what you mean. As a general note there is a lot in the current
proposal that says this could be added in the future, I was responding to
the proposal as proposed not what might be added in the future. Also if
there are some aspects of the proposal that really need to be added then
they should be part of the proposal.

Pretty much along the same lines, you say "This is not true. There are
additional differences." What additional differences?

And again, you say "I disagree. The current proposal clearly states
reasons not addressed here." Can you expand? Obviously I thought I had
covered everything otherwise I wouldn't have said that the points are
covered. So can you point me in the direction of your concerns?

···

On Thursday, 7 January 2016, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 6, 2016, at 4:52 PM, Howard Lovatt <howard.lovatt@gmail.com > <javascript:_e(%7B%7D,'cvml','howard.lovatt@gmail.com');>> wrote:

Here is an expanded proposal for the syntax for a Scala style memberwise
syntax and equivalent code, specification is via an example rather than
formal syntax since this is easier to follow. Note it is like Scala’s
syntax but it is ‘Swiftified” (in particular still retains `init` keyword).

    class Ex public init(
        superParam: sPType = sPInitial,
        private label privateParam: pPType = pPInitial,
        calculatedParam: cPType = cPInitial
    ): SuperType(superParam) {
        calculatedParam: cPType {
            get {…}
            set {…}
        }
    }

This gets translated to:

    class Ex: SuperType(superParam) { {
        private privateParam: pPType = pPInitial,
        public init(superParam: sPType = sPInitial, label privateParam:
pPType = pPInitial, calculatedParam: cPType = cPInitial) {
            // 1. Call super
            super.init(superParam)
            // 2. Initialize generated parameters and existing parameters
            self.privateParame = privateParam
            self.calculatedParam = calculatedParam
        }
        calculatedParam: cPType {
            get {…}
            set {…}
        }
    }

This translation is not valid Swift for several reasons. There are also
several aspects of this translation that are somewhat vague and a detailed
design is necessary to evaluate how you envision it working.

Because the syntax is so short it is part of this proposal to remove both
the current default and memberwise initialisers thus simplifying the
language overall (remove two features, add one) and at the same time gain
power, which is a rare situation and therefore I would suggest optimal.

It is also more powerful than the proposed `memberwise init(..)` and/or
existing automatic `inits` in the following ways:

1. Allows `lets` to have a default value.
2. Allows other properties including computed properties to have a default
value.
3. Allows any `super.init` to be called (not restricted to `super.init()`).
4. Allows control of which properties participate in the `init` (they are
listed in the brackets and are not in a super call and are not
an existing property).
5. Allows private properties to be initialised.
6. Allows properties including private properties to have a label instead
of their actual name and hence not expose internals (also allows migration
of implementation whilst retaining external interface).
7. Allows calls to the generated `init` that don’t specify all members,
i.e. for `struct Ex init(i: Int = 0, s: String = “") {}` the following are
allowed `Ex()`, `Ex(i: 1)`, `Ex(s: “A”)`, and `Ex(i: 2, s: “B”)`.
8. Allows visibility of automatically generated `init` to be controlled.
9. Supports property behaviours.
10. Does not require a new keyword.

Several of these points are also true of my proposal and several others
would be true if what I believe are the most important future enhancements
are added.

The downsides of the proposal relative to `memberwise init(..)` and/or
existing automatic `inits` are:

1. That people would need to be careful when laying out their code
otherwise the first line could become long (a bit of pretty printing solves
this).
2. Existing structs/classes that have automatically generated inits would
need to be refactored, e.g. `CGRect` would become `struct CGRect init(var
origin: CGPoint, var size: CGSize) {}` (a migration tool would help here).

Other than the downsides listed above the proposal does everything the
current proposal and current implementation does and more (more also listed
above) and is simpler to both explain and implement.

This is not true. There are additional differences.

The above more than addresses the reasons given in the current proposal
for not using the Scala syntax and demonstrates superiority in many areas.

I disagree. The current proposal clearly states reasons not addressed
here.

I don’t wish to

However if it were the current proposal or nothing I would go with the
current proposal since something is better than nothing.

On Wednesday, 6 January 2016, Matthew Johnson <matthew@anandabits.com > <javascript:_e(%7B%7D,'cvml','matthew@anandabits.com');>> wrote:

On Jan 5, 2016, at 12:12 PM, Tino Heth <2th@gmx.de> wrote:

The “type parameter list” syntax is sugar that could be implemented as a
layer on top of the current proposal or could be implemented orthogonally.

Hi Howard,
I've heard this argument before, so I'll repeat my answer as well:
Both offers don't make sense, it's either one way or the other (or
something completely different).

I don’t think it’s clear whether both make sense or not. They are not
mutually exclusive and are aimed at solving related, but different
problems. If we adopt the current proposal, the Kotlin / Scala syntax
*might* make sense for some use cases. It would depend upon whether the
current proposal is considered too verbose in enough cases or not. This
may or may not turn out to be the case.

The reason my proposal looks the way it does is because there are
specific goals it intends to achieve and problems it is intended to solve.
These goals and problems are not adequately addressed by the Kotlin / Scala
syntax.

If diversity starts here, why not have "const" and "val" beside "let", or
allow "fn" and "lambda"?

@Matthew: Please, if you support something, then say so - using clear
words, not phrases like "could be implemented”.

This phrasing is plenty clear IMO. I am stating a possibility. I do not
have a position on whether or not it is a good idea at the moment.

I have made some modifications to the proposal over the last few days.
These changes have been partly motivated by considering the conversation we
have had. You may wish to give it another look. If so, please look here:
https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/0018-flexible-memberwise-initialization.md\.
There is currently an open PR for the latest change so it is not in the
main Swift evolution repo yet.

The most recent change includes a discussion of why it does not use the
Scala / Kotlin syntax. You may not like the choice but I hope you can at
least understand the rationale (even if you disagree with it).

--
  -- Howard.

--
  -- Howard.

@Tino,

I agree that this isn’t very productive, so I have in effect taken your suggestion.

I have changed tack and replied to the main review process instead of this thread with a revised suggestion giving examples like you suggest.

Cheers,

— Howard.

···

On 8 Jan 2016, at 4:31 AM, Tino Heth <2th@gmx.de> wrote:

Hi there!

I've been thinking about a "contest" for an alternative to memberwise initialization, but I guess the game is over before it began:
Afaics, Chris basically wrote the proposal himself, so I have little doubt on its acceptance.
I additionally really dislike the style of the discussion — but that wouldn't stop me continuing it.

How about you?

My idea was to write two small examples (one where "our" syntax really shines, and one where the problems of memberwise init are as obvious as possible), ask Matthew to do the same and then let each team express the foreign examples in their own syntax...

Best regards,
Tino

"Programming today is a race between software engineers
striving to build bigger and better idiot-proof programs,
and the Universe trying to produce bigger and better idiots.
So far, the Universe is winning."

Not with the updates I suggested as you can still initialize non-visible members. Whether you find that valuable or not is a different question.

-David

···

On Dec 21, 2015, at 11:05 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

The proposal already states that a memberwise initializer only includes parameters for properties that are at least as visible as the initializer itself. So if you can see the `s` and `i` parameters, you can also see the `s` and `i` properties. It's not going to expose anything that isn't already visible.

This isn’t about access modifiers, it’s about the name chosen for internal variables vs. names chosen for API contracts.

But if you have a bad name in your memberwise initializer, that bad name is *by definition* already part of your API contract as a property name.

Stated another way: Either the name is already externally visible as a property, and thus is not by any sensible definition an "internal" name, or the memberwise initializer will not publish it.

I agree. With this design, memberwise initializers are an opt-in sugar feature. If you don’t want to publish the names of your properties, then write your initializer manually however you want it.

-Chris

···

On Dec 21, 2015, at 11:05 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

The proposal already states that a memberwise initializer only includes parameters for properties that are at least as visible as the initializer itself. So if you can see the `s` and `i` parameters, you can also see the `s` and `i` properties. It's not going to expose anything that isn't already visible.

This isn’t about access modifiers, it’s about the name chosen for internal variables vs. names chosen for API contracts.

But if you have a bad name in your memberwise initializer, that bad name is *by definition* already part of your API contract as a property name.

Stated another way: Either the name is already externally visible as a property, and thus is not by any sensible definition an "internal" name, or the memberwise initializer will not publish it.

Swift has no support for such a type, when it does, it should be opt in.

···

On Dec 22, 2015, at 11:38 AM, Félix Cloutier <felixcca@yahoo.ca> wrote:

Sorry to sidetrack the discussion, but does that mean that using `let` properties in a type where memory layout is important is possibly not forward-compatible?

This is not an attempt to subvert `let` properties. The `= 1` in the declaration can very reasonably be viewed as a default value that should be used if the member is not otherwise initialized.

Why would you have an immutable instance member that is always going to have a constant value of 1? That just wastes space by duplicating the constant value in many instances. However it is quite reasonable to have an immutable instance member that defaults to 1, but may have a different value depending on the initializer that is used for the instance.

Matthew

···

On Dec 22, 2015, at 12:51 PM, Guillaume Lessard via swift-evolution <swift-evolution@swift.org> wrote:

Hi,

Regardless of anything else in the proposal, I strongly dislike its attempted subversion of `let` properties.

struct A {
let member = 1 // It’s done, over. If that’s not what you want, don’t initialize it here.
}

My proposal is specifically suggesting that we treat “initial value” as a default rather than an initialization that always happens. IMO the current behavior is limiting and problematic in a number of ways.

If we make the change I am suggesting double initialization / assignment will not happen.

Ah, ok!

I'm a bit uneasy about overloading the initial-value-syntax with a new meaning, though.

-Thorsten

This was pulled from the latest draft of the proposal. Please take a look at the current draft and let me know if you like the new solution better.

Matthew

···

Sent from my iPad

On Dec 24, 2015, at 5:46 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 22.12.2015 um 18:30 schrieb Matthew Johnson <matthew@anandabits.com>:

I wonder whether we could avoid the problems of mixing up inline initialization and default initializer parameterization by taking a different approach. Sorry if this has been discussed and I missed it, but Scala and Kotlin both support a compact function-like class declaration syntax for simple "case classes". We could adopt something similar for our structs and classes, so that:

public struct Vec4(x: Double, y: Double, z: Double, w: Double = 1.0) { }

expanded to:

public struct Vec4 {
  public let x: Double
  public let y: Double
  public let z: Double
  public let w: Double // NB: No inline initializer

  // Default argument in struct decl becomes default argument in initializer
  public init(x: Double, y: Double, z: Double, w: Double = 1.0) {
    self.x = x
    self.y = y
    /* you get the idea */
  }
}

(and you could presumably stick `var` on parameters to make the corresponding properties `var`s instead of `let`s, if you wanted). That collects all the information you want to know about the members and their initialization together in one place, and the syntax naturally suggests function-like semantics for `=` expressions rather than inline-initializer-like semantics.

-Joe

···

On Dec 24, 2015, at 5:41 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

Sent from my iPad

On Dec 24, 2015, at 5:46 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 22.12.2015 um 18:30 schrieb Matthew Johnson <matthew@anandabits.com>:

My proposal is specifically suggesting that we treat “initial value” as a default rather than an initialization that always happens. IMO the current behavior is limiting and problematic in a number of ways.

If we make the change I am suggesting double initialization / assignment will not happen.

Ah, ok!

I'm a bit uneasy about overloading the initial-value-syntax with a new meaning, though.

-Thorsten

This was pulled from the latest draft of the proposal. Please take a look at the current draft and let me know if you like the new solution better.

Sent from my iPad

My proposal is specifically suggesting that we treat “initial value” as a default rather than an initialization that always happens. IMO the current behavior is limiting and problematic in a number of ways.

If we make the change I am suggesting double initialization / assignment will not happen.

Ah, ok!

I'm a bit uneasy about overloading the initial-value-syntax with a new meaning, though.

-Thorsten

This was pulled from the latest draft of the proposal. Please take a look at the current draft and let me know if you like the new solution better.

I wonder whether we could avoid the problems of mixing up inline initialization and default initializer parameterization by taking a different approach. Sorry if this has been discussed and I missed it, but Scala and Kotlin both support a compact function-like class declaration syntax for simple "case classes". We could adopt something similar for our structs and classes, so that:

public struct Vec4(x: Double, y: Double, z: Double, w: Double = 1.0) { }

expanded to:

public struct Vec4 {
  public let x: Double
  public let y: Double
  public let z: Double
  public let w: Double // NB: No inline initializer

  // Default argument in struct decl becomes default argument in initializer
  public init(x: Double, y: Double, z: Double, w: Double = 1.0) {
    self.x = x
    self.y = y
    /* you get the idea */
  }
}

(and you could presumably stick `var` on parameters to make the corresponding properties `var`s instead of `let`s, if you wanted). That collects all the information you want to know about the members and their initialization together in one place, and the syntax naturally suggests function-like semantics for `=` expressions rather than inline-initializer-like semantics.

Hi Joe, thanks for jumping in to this thread with an idea.

One thing that isn't obvious to me is how your idea would work when some properties need a default and some need an initial value? What about access control for properties in the struct parameter list? What about behaviors (assuming your proposal is accepted).

I’ll have to give this some thought, but my initial reaction is that I would prefer a different path that sticks to current syntax and has clear interaction with other language features.

Did you have a chance to see how I handled this in the latest draft by introducing the `@default` attribute?

struct S {
    @default("hello") let s: String
    @default(42) let i: Int

    // user declares:
    memberwise init(...) {}
    // compiler synthesizes:
    init(s: String = "hello", i: Int = 42) {
        /* synthesized */ self.s = s
        /* synthesized */ self.i = i
    }
}

Something like this attribute might also be useful in other cases allowing the default to be used by manual initializers:

struct S {
  let value: Int?
}

public class X {
  @default(42) let a // same potential utility if this is a var

   // default is a keyword that is only valid in an expression
   // on the rhs of a member initialization statement
   // or possibly an initializer parameter with a name corresponding to a stored property

   init(s: S) {
      self.s = s.value ?? default
   }
   init(int a = default) { … }
}

As an alternative to specifying a value directly in the attribute, it could require a `let` member name, although my initial reaction is that this is more verbose and less good:

public class X {
   let defaultForA = 42
  @default(defaultForA) let a
}

···

On Dec 24, 2015, at 11:11 AM, Joe Groff <jgroff@apple.com> wrote:

On Dec 24, 2015, at 5:41 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Dec 24, 2015, at 5:46 AM, Thorsten Seitz <tseitz42@icloud.com <mailto:tseitz42@icloud.com>> wrote:

Am 22.12.2015 um 18:30 schrieb Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>>:

That said, I think the interaction of explicit initializers and memberwise initializers begs discussion. It would be a much simpler model to only get memberwise parameters for properties without an explicit init. Have you considered this model, what are the tradeoffs with allowing vars to overwrite them? Allowing an explicit init to be overwritten by the memberwise initializer seems potentially really confusing, and since you allow explicit arguments on inits, this could always be handled manually if someone really really wanted it. For example, they could write:

memberwise init(s : String) {
  self.s = s
}

If they wanted to get the sugar of memberwise inits (presumably for other properties without an explicit init) but still allow one to be overwritten.

Personally, I think there is a lot of value in allowing memberwise initialization for properties that do contain an initial value. Imagine a hypothetical Swift version of Cocoa Touch. UILabel might have initial values for text, font, textColor, etc but still want to allow clients to provide memberwise initialization arguments to override the default value. I think there are many cases like this both in UI code and elsewhere.

I think I confused the issue. If we have to support properties that have a default value, then the model I’m advocating for is that this:

class C {
  let x : Int
  var y : Int = foo()

  memberwise init(...) {}
}

compile into:

init(x : Int, y : Int = foo()) {
  self.x = x
  self.y = y
}

Pertinent points of this are that lets without a default value would still turn into arguments, and that any side effects of the var initializer would be squished. Another potential model is to compile it to:

init(x : Int, y : Int) {
  self.x = x
  self.y = foo()
}

which is guaranteed to run the side effect, but requires y to be specified. I do not think it is a good model to compile it to:

init(x : Int, y : Int? = nil) {
  self.x = x
  self.y = y ?? foo()
}

because that would allow passing in an Int? as the argument. The final model (which I know you don’t like) is for memberwise initializers to *only* apply to properties without a default value.

This doesn’t seem like the right behavior to me. The compiler shouldn’t be in the business of scanning the body of the init to decide what members are explicitly initialized. I’d suggest that the model simply be that the contents of the {} on a memberwise init get injected right after the memberwise initializations that are done. This mirrors how properties with default values work.

The model does inject the synthesized memberwise initialization just prior to the body of the initializer.

As written the proposal does scan the body of the init in order to determine which properties receive memberwise initialization. The idea here is that it provides additional flexibility as it allows specific initializers to “opt-out” of memberwise initialization synthesis for some properties while receiving it for others.

This is a very problematic model for me, because it can lead to serious surprises in behavior, and in the case of lets, shares the problems above with not allowing one to define the long-hand form explicitly.

Hi Matthew,

I continue to really like the approach and direction. Here’s an attempt to respond to both of your responses, I hope this comes across somewhat coherent:

Excellent, thanks! I have completed a third draft of the proposal. It may (probably does) still require further refinement but I believe it is another solid step forward.

Here’s the complete draft: https://github.com/anandabits/swift-evolution/blob/flexible-memberwise-initialization/proposals/NNNN-flexible-memberwise-initialization.md

Here’s the commit diff in case that is more helpful: Third draft · anandabits/swift-evolution@8287b67 · GitHub <Third draft · anandabits/swift-evolution@8287b67 · GitHub;

Discussion on a couple of topics continues inline below as well.

Great, comments below:

"memberwise initializer for us in some” -> typo “use”
"elimintate the existing “ -> typo “eliminate"

"Sometimes properties that should be immutable are made mutable” -> should be updated to reflect the new approach?

There are two major problems:

Problem 1: supporting this would *prevent* us from allowing memberwise initializers to be public. A really important feature of the memberwise design is that it is “just sugar” and that people can write the initializer out long hand if needed (e.g. if they want to add or reorder members without breaking API). With your proposed design, I could write:

public class X {
  let a = 42
  public memberwise init(...) {}
}

and use it with: X(a: 17). However, there is no way in swift to write that initializer out longhand. This is a critical problem to me.

Maybe it wasn’t clear, but I was suggesting that this particular rule be changed for all initializers. That would have made it possible to write the initializer out longhand. But I have pulled that part of the proposal so it doesn’t matter now.

Sounds good thanks. Changing the initialization semantics for lets in general is something far more nuanced and should be discussed separately. The design we have now is carefully considered, and has already been refined from what shipped in Swift 1.0.

Problem 2: This can cause very surprising performance issues, because it forces the let property to be stored. With the previous example, it is a goal for us to be able to compile:

public class X {
  let a = 42
}

into the equivalent of:

public class X {
  var a : Int { return 42 }
}

because people like to use local lets as manifest constants (avoiding “magic numbers”). With your proposal, we’d lose this capability, and we’d have to store them any time there is a memberwise initializer.

I would have never considered writing a type with an instance member with constant value that cannot be assigned a different value in the initializer as I would have considered it wasted space and possibly worthy of a compiler error. I would have written:

public class X {
  static let a = 42
}

Clearly I was underestimating the optimizer! I can see value in the ability to refer to the member without a prefix. Given the (guaranteed?) optimization it definitely makes sense.

I understand, and I would have written the same. However, it has been pointed out to me numerous times that I only know to write that because I “know too much” about the implementation. It is also annoying that writing it as a static property would force you to write something like “X.a" instead of just “a".

Neither of these problems apply to vars, which is why I think we can support vars in this model, but not lets.

As you said you agree with the desire to support this behavior, maybe you would be comfortable with a different approach. Rather than using the initial value we could use an attribute:

public class X {
  @default(42) let a
}

This would allow us to still support the desired memberwise initialization behavior without conflicting with the current “initial value” behavior. I’m have updated the proposal to specify the behavior this way. If you don’t like it let’s continue the discussion.

It could be called @default, @memberdefault, @memberwisedefault, etc. The name doesn’t really matter to me.

I’m not inclined to like an approach that requires magic attributes to be sprinkled around the code. The more attributes and annotations you have to add to your properties, the less the syntactic sugar of memberwise initializers are paying for themselves.

I see that your new version introduces the @default attribute. I’d strongly suggest the same advice as before (I know I must sound monotonous :-) - please start your proposal minimal and straight-forward, by moving @default to the “possible future extensions” section. We can always add it later to build out the model if there is a strong need, but I’d rather see us roll out the next incremental step and learn from it before we do. That allows us to carefully consider how much each piece is adding and costing as independent entities.

I understand, but it is a pure extension to the basic proposal. The proposal is complex enough as it is, so inessential parts should be split out for discussion and implementation. I’m not saying that we shouldn’t do @nomemberwise in (e.g.) the swift 3 timeframe, I’m saying that it should be a separate discussion informed by the design and implementation process of the base proposal.

That makes sense. I have updated the proposal to reflect this. Incremental change, right? :)

Incremental is good!

I absolutely agree that the compiler should not be in the business of making a guess about what superclass initializer needs to be called!

Pwew, great!

The example was intended to include a call to provide all of the arguments necessary to disambiguate prior to memberwise argument forwarding. In this case of the present example none are necessary. I am including the corrected example here and have also updated the proposal. It should have read like this:

class Base {
    let baseProperty: String

    // user declares:
    memberwise init(...) {}
    // compiler synthesizes:
    init(baseProperty: String) {
        self.baseProperty = baseProperty
    }
}

Looks good so far.

class Derived: Base {
    let derivedProperty: Int

    // user declares:
    memberwise init(...) { super.init(...) }
    // compiler synthesizes (adding forwarded memberwise parameters):
    init(baseProperty: String, derivedProperty: Int) {
        self.derivedProperry = derivedProperty
        super.init(baseProperty: baseProperty)
    }
}

This I still have a concern with, in two ways:

1) This still has tight coupling between the base and derived class. Properties in a based class are not knowable by a derived class in general (e.g. across module boundaries) and this directly runs afoul of our resilience plans. Specifically, across module boundaries, properties can change from stored to computed (or back) without breaking clients.

2) You’re introducing another unnecessary feature "super.init(…)” which will have to be independently justified.

I consider #1 a showstopper but, in any case, this is an orthogonal add-on to your base proposal, which can be considered independently as the base proposal rolls out. I’d strongly suggest dropping it from the first revision of the proposal. It can be added at any time if there is a compelling need. If/when we consider this, I’d suggest thinking about it in terms of a more general parameter forwarding feature, instead of something tied to memberwise initializers.

A related concern is the part of your proposal that supports memberwise initializers for convenience initializers. I think this should be dropped for a number of reasons:

1) Again, it is a major problem for resilience, because convenience initializers (unlike designated ones) can be added to a type in an extension. That extension can be defined in another module, and it isn’t knowable it that module what stored properties a type has.

2) Convenience initializers, by intent if not "by definition”, should not publish every little detail of a type. They are intended to be used as an additional non-canonical way to initialize a type. While I’m sure there are some exceptions, the ability to have a memberwise initializer doesn’t seem very core to them.

I understand there also potentially resilience concerns around supporting inheritance. If you’re willing to dive into specifics I may (or may not) be able to find solutions. If I am not able to identify solutions, at least I have tried and understand the specific issues involved. :)

I’m definitely willing to explain, please let me know if the above makes sense or not.

I personally don’t like inheritance very much and consider it a tool of last resort, but I do think it is best for a feature like flexible memberwise initialization in a language that offers inheritance should support it if at all possible.

Ok, if you don’t find derived classes to be super important, then it probably isn’t worth hyper-optimizing :-)

Instead, the user should have to write:

memberwise init(baseProperty : Int, ...) {
  super.init(baseProperty: baseProperty)
}

This doesn’t reduce the utility of this feature, and it preserves separability of class from superclass.

Ideally we could avoid writing this manually. We still have an M x N problem (M inherited properties, N subclass initializers).

Writing that manually would’t reduce the utility of the feature in cases where it is applicable, but it does severely limit its applicability to inheritance hierarchies.

I don’t agree. The ability to manually add parameters to the memberwise initializer, along with the extant requirement to explicitly call super.init seems to directly solve this. This feature is about automating initialization of a single classes properties independent of the superclass. Also, it is very uncommon for a class to expose an initializer for *all* of its properties if it is intended to be subclassed anyway.

You’re welcome! I know this proposal is “just” syntactic sugar, but I believe it will have an impact on initialization designs in practice.

Yes, I agree, I’m very excited about this for two reasons: 1) I think it is will be highly impactful for all kinds of code written in swift, and 2) it cleans up a half-baked area of the language by replacing it with some thing much better thought out.

Other comments:

In "Impact on existing code”, given your proposal, I think that we should keep the implicit memberwise initializer on classes, start generating it for root classes, and generate it for derived classes whose parent has a DI with no arguments (e.g. subclasses of NSObject). We should keep the current behavior where it is generated with internal behavior, and it is surpressed if *any* initializers are defined inside of the type.

Thanks again for pushing this forward, you can also put me down as the review manager if you’d like.

-Chris

···

On Dec 23, 2015, at 9:25 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Sorry if this has been discussed and I missed it, but Scala and Kotlin both support a compact function-like class declaration syntax for simple "case classes".

Just took some time to read more deeply and would have added a reference to Kotlin on my own — I think their approach of solving the problem from the other direction (turning parameters into members instead of inferring parameters for properties) feels quite natural and avoids many problems; but unless the decision to remove var parameters is revised, I think it's unfortunate to introduce them in another context.
We shouldn't forget that initializers in Swift already are a quite huge and complicated topic (required, convenience, when to call super…) with big potential to confuse newcomers; so I'd recommend to be careful introducing new keywords and concepts that aren't useful in other places and rather stick with a less complete solution that is lightweight and elegant:
It could be considered to simply don't address parts of the problem in the language at all, but encourage better tools to manage the boilerplate.

Merry christmas!
Tino