[swift-evolution] [Proposal Draft] Flexible memberwise initialization

Matthew Johnson matthew at anandabits.com
Thu Dec 24 14:17:53 CST 2015


> On Dec 24, 2015, at 11:11 AM, Joe Groff <jgroff at apple.com> wrote:
> 
>> 
>> On Dec 24, 2015, at 5:41 AM, Matthew Johnson via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>> 
>> 
>> Sent from my iPad
>> 
>>> On Dec 24, 2015, at 5:46 AM, Thorsten Seitz <tseitz42 at icloud.com <mailto:tseitz42 at icloud.com>> wrote:
>>> 
>>> 
>>>> Am 22.12.2015 um 18:30 schrieb Matthew Johnson <matthew at anandabits.com <mailto:matthew at 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.
> 
> 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
}


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


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


More information about the swift-evolution mailing list