[Review] SE-0018 Flexible Memberwise Initialization

Before diving into the details, I’d like to reiterate the point of my review:

I don’t know exactly what the better mechanism is, but I strongly suspect that it’s out there. My wish is to defer the current very narrow proposal in favor of a more searching for a general mechanism.

I don't think we should permanently reject a thoughtful proposal only because a better proposal might be out there. Neither do I think that having to come up with a better mechanism within the review period should be a requirement of not immediately accepting the proposal.

That’s the upshot of my review. The rest of this message is just noodling….

I don't think the `members` tuple is a good alternative here. It looks good on paper, but think about what it actually would need to involve to match this proposal and its likely enhancements:

- It would need to have different elements at different levels of accessibility, and we would somehow have to select the right level for the matching initializer.
- It would need to include only properties *writable* at that level.
- It would need to include only stored properties.
- It would need to include only properties that are either `var`s or uninitialized `let`s.

You’re imagining with all these that the initialization code must be _completely_ synthesized. But as I said, I’d happily shoulder the burden of just a little more explicitness if I got a mechanism that wasn’t so inflexible as the proposal at hand.

I’m quite sure that the automated synthesizing won’t always get it right. It tries, but I’ll want inevitably want manual control. And thus…

- But we would need mechanisms to add items that have been excluded, and remove items that have been included.

…I’d want such mechanisms with the current proposal too — but there’s no obvious future expansion direction for them.

Plus there's all the weirdness around using it to reassign constants.

It would follow the same rules as normal assignment. No weirdness there.

Yeah. Still brainstorming here :-)

So you know, I didn't really mean to put you specifically on the spot here; you're obviously trying to find concrete solutions to these problems. But it seems to me that what we're going to end up with is that, instead of having this:

  public init(members...: Members) {
    self.members = members
  }

We'll have something more like:

  public init(members...: Members.Public.Initializable) {
    self.members.public.initializable = members
  }

Well, in my brainstorms, I was loosely imagining that when members is used as an lvalue, it would infer the subset being assigned to using the labels of the rhs tuple. So you could do this:

  public init(members...: Members.Public.Initializable) {
    self.members = members
  }

…or maybe something more arbitrarily composable:

  public init(members...: Members.only(public, var, except: meaningOfLife)) {
    self.members = members
                meaningOfLife = 42
  }

…or even:

  public init(members...: Members.only(public, var).except(meaningOfLife)) {
    self.members = members
                meaningOfLife = 42
  }

Breaking that down:

  Members.only(public, var) → (a: Int, b: String, meaningOfLife: Int)
        .except(meaningOfLife) = generic operation that works on any tuple → (a: Int, b: String)
  members arg then gets a value like (a: 4, b: “foo”)
  self.members = (a: 4, b: “foo”) infers that a and b are being assigned to, and expands to:
    a = 4
    b = 4
  …with no additional language features necessary for access control, let/var, etc.

Something like that. Who knows? It’s obviously a complicated discussion. The point is just that it would do a lot more for the language to have orthogonal features for:

turning tuples into variadic parameters,
turning member or member types into a tuple, the former of which can be an lvalue, and
subsetting and reordering tuples.

…with none of those being limited to initializers.

Some mechanism like this would generalize much better than the current proposal, which breaks down quickly if you step outside its imagined use cases.

I don’t see the current proposal leading in this more useful direction, though it sounds like Matthew has something up his sleeve.

Cheers,

Paul

···

Brent Royal-Gordon <brent@architechies.com> wrote:
On Jan 10, 2016, at 3:37 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Jan 10, 2016, at 1:17 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

The latter I'm afraid.

I was just discussing this design space with Chris Willmore, who's been working on revamping how our function type model works. If we move to a multiple-argument model for functions rather than the current every-function-takes-a-tuple-argument model, then we will likely need at least limited support for packing and unpacking tuples from and to arguments in order to avoid regressing at argument forwarding use cases. However, even that limited packing/unpacking functionality might be enough to seriously consider a more general magic "members" property as an alternative.

I don’t mind discussing an alternative using this approach. If we’re going to do that I think it must be clear how it would cover various intended use cases in detail. Specifically, how would we address:

1. Default parameter values (at least for `var` properties)
2. `let` properties: it seems pretty magical indeed if the computed `var` property exposing the tuple could be used to initialize a `let` property.

Yeah, this member would need special initialization abilities, I agree.

3. Partial memberwise initialization exposing a subset of members following some kind of “automatic” or “opt-in” model for determining the subset.

Seems to me that could be done by factoring the interesting subsets into structs, e.g.:

class Foo {
  internal struct MemberwiseProperties {
    var x,y,z: Int
  }
  internal var state: MemberwiseProperties

  init(members...: MemberwiseProperties.Members) {
    state.members = members
  }
}

-Joe

···

On Jan 7, 2016, at 10:03 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 7, 2016, at 11:39 AM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 8:28 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

Is it correct to assume that part of the magic is an understanding of the magic properties that allows them to be used to initialize the properties corresponding to the tuple members?

One thing I think is worth considering is that there is magic required no matter what approach we adopt. The primary advantage of this approach seems to be that the magic / implicit properties might sometimes be useful outside of an initialization context. There could be other advantages depending on the details of what this approach looks like but its hard to tell without more specifics.

Matthew

-Joe

Sent from my moss-covered three-handled family gradunza

On Jan 6, 2016, at 7:12 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Sent from my iPad

On Jan 6, 2016, at 8:46 PM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

Sent from my moss-covered three-handled family gradunza

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

On Jan 6, 2016, at 5:23 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 6, 2016, at 6:04 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 6, 2016, at 2:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello Swift community,

The review of "Flexible Memberwise Initialization" begins now and runs through January 10th. The proposal is available here:

  https://github.com/apple/swift-evolution/blob/master/proposals/0018-flexible-memberwise-initialization.md

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  * What is your evaluation of the proposal?

It’s okay.

  * Is the problem being addressed significant enough to warrant a change to Swift?

I’m lukewarm about that. I have never found writing out the initializers I want to be a significant burden, and I find my code is better when they’re explicit. Every new feature increases the language's complexity and surface area, and I fear this one is not going to pay its way.

  * Does this proposal fit well with the feel and direction of Swift?

Yes, but I worry that it may be too early to add it. Other features in this space, like truly generic variadics, may well obsolete anything we do today. I’m not sure we should be designing convenience features that are likely to overlap with more general features coming down the road unless the inconvenience is very painful… which I personally don’t find it to be.

It isn’t clear to me how generic variadics might obsolete the functionality of this proposal. Can you elaborate on that?

Not sure if this is exactly what Dave has in mind, but an idea that comes to mind: we could say that structs and classes have a magic "members" tuple and typealias:

struct Foo {
  var x, y: Int

  // Implicit members
  typealias Members = (x: Int, y: Int)
  var members: Members {
    get { return (x: x, y: y) }
    set { (x, y) = newValue }
  }
}

With plausible future features for forwarding tuples as arguments, then the memberwise initializer could be implemented like this:

      // Say that a parameter declared 'x...' receives a tuple of arguments labeled according to its type,
      // like '**x' in Python
init(members...: Members) {
  self.members = members
}

And I think all your other use cases could be covered as well.

That's exactly what I had in mind. Thanks, Joe!

Is there any chance of generic variadics along these lines being part of Swift 3? Or is this down the road further?

And this is more clear than this?

class Foo {
  var x,y,z: Int
  init(x: Int, y: Int, z: Int) {
    self.x = x
    self.y = y
    self.z = z
  }
}

It seems odd to me to change the design of your type for the sole purpose of using a language feature.

It’s the complexity of the feature and its applicability only to initialization that lead me to vote no for the proposal. I've wanted public, generated initializers (ask to the internal generated struct inits). However, as you dig into to see what you really need to support, the complexities become quite nuanced.

I keep coming back to a much simpler and more explicit desire of being able to use “self” (this is not a label, but an annotation like "inout") in the parameter list. This is applicable to all members of a type.

class Foo {
  var x,y,z: Int
  init(self x: Int, self y: Int, self z: Int) {
    // the compiler auto-inserts these lines
    self.x = x
    self.y = y
    self.z = z
  }

  func fooey(self x: Int) {
    // the compiler generates
    self.x = x
  }
}

I’m not convinced that even that is worth the magic it brings to the table, but it does get rid of nearly all of the complexities, becomes more applicable than just initialization, and has only a single rule to learn that is fairly self-explanatory. The trade-off is you must be explicit about the members of the initializer. I’m OK with that because if I have a type that really has so many members that this is a burden, then I should probably refactor it. A sufficient macro system also trivially solves this issue as well.

-David

···

On Jan 7, 2016, at 10:14 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 7, 2016, at 10:03 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 7, 2016, at 11:39 AM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 8:28 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

The latter I'm afraid.

I was just discussing this design space with Chris Willmore, who's been working on revamping how our function type model works. If we move to a multiple-argument model for functions rather than the current every-function-takes-a-tuple-argument model, then we will likely need at least limited support for packing and unpacking tuples from and to arguments in order to avoid regressing at argument forwarding use cases. However, even that limited packing/unpacking functionality might be enough to seriously consider a more general magic "members" property as an alternative.

I don’t mind discussing an alternative using this approach. If we’re going to do that I think it must be clear how it would cover various intended use cases in detail. Specifically, how would we address:

1. Default parameter values (at least for `var` properties)
2. `let` properties: it seems pretty magical indeed if the computed `var` property exposing the tuple could be used to initialize a `let` property.

Yeah, this member would need special initialization abilities, I agree.

3. Partial memberwise initialization exposing a subset of members following some kind of “automatic” or “opt-in” model for determining the subset.

Seems to me that could be done by factoring the interesting subsets into structs, e.g.:

class Foo {
  internal struct MemberwiseProperties {
    var x,y,z: Int
  }
  internal var state: MemberwiseProperties

  init(members...: MemberwiseProperties.Members) {
    state.members = members
  }
}

No, it isn't, but Matthew asked… I'm personally not too motivated to support anything more than all-or-nothing memberwise initialization, and tend to agree that anything more specialized deserves an explicit implementation.

-Joe

···

On Jan 7, 2016, at 10:32 AM, David Owens II <david@owensd.io> wrote:

And this is more clear than this?

class Foo {
  var x,y,z: Int
  init(x: Int, y: Int, z: Int) {
    self.x = x
    self.y = y
    self.z = z
  }
}

The latter I'm afraid.

I was just discussing this design space with Chris Willmore, who's been working on revamping how our function type model works. If we move to a multiple-argument model for functions rather than the current every-function-takes-a-tuple-argument model, then we will likely need at least limited support for packing and unpacking tuples from and to arguments in order to avoid regressing at argument forwarding use cases. However, even that limited packing/unpacking functionality might be enough to seriously consider a more general magic "members" property as an alternative.

I don’t mind discussing an alternative using this approach. If we’re going to do that I think it must be clear how it would cover various intended use cases in detail. Specifically, how would we address:

1. Default parameter values (at least for `var` properties)
2. `let` properties: it seems pretty magical indeed if the computed `var` property exposing the tuple could be used to initialize a `let` property.

Yeah, this member would need special initialization abilities, I agree.

It seems odd that this would be deemed acceptable. Would the `members` member be usable outside of an initializer? It seems like that wouldn’t be possible if there were a `let` property. But it also seems weird if its availability was determined by whether you have a `let` property or not.

3. Partial memberwise initialization exposing a subset of members following some kind of “automatic” or “opt-in” model for determining the subset.

Seems to me that could be done by factoring the interesting subsets into structs, e.g.:

class Foo {
  internal struct MemberwiseProperties {
    var x,y,z: Int
  }
  internal var state: MemberwiseProperties

  init(members...: MemberwiseProperties.Members) {
    state.members = members
  }
}

Sure you could do that, but it doesn’t seem like an acceptable alternative to me. Now the members must be prefixed with an identifier everywhere they are used. I don’t see why this is acceptable when requiring `self` is not.

Are there compelling reasons to go down this road rather than the current proposal? What use cases does it address that the current proposal does not?

···

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

On Jan 7, 2016, at 10:03 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 7, 2016, at 11:39 AM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 8:28 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

-Joe

Is it correct to assume that part of the magic is an understanding of the magic properties that allows them to be used to initialize the properties corresponding to the tuple members?

One thing I think is worth considering is that there is magic required no matter what approach we adopt. The primary advantage of this approach seems to be that the magic / implicit properties might sometimes be useful outside of an initialization context. There could be other advantages depending on the details of what this approach looks like but its hard to tell without more specifics.

Matthew

-Joe

Sent from my moss-covered three-handled family gradunza

On Jan 6, 2016, at 7:12 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Sent from my iPad

On Jan 6, 2016, at 8:46 PM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

Sent from my moss-covered three-handled family gradunza

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

On Jan 6, 2016, at 5:23 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 6, 2016, at 6:04 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 6, 2016, at 2:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello Swift community,

The review of "Flexible Memberwise Initialization" begins now and runs through January 10th. The proposal is available here:

  https://github.com/apple/swift-evolution/blob/master/proposals/0018-flexible-memberwise-initialization.md

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  * What is your evaluation of the proposal?

It’s okay.

  * Is the problem being addressed significant enough to warrant a change to Swift?

I’m lukewarm about that. I have never found writing out the initializers I want to be a significant burden, and I find my code is better when they’re explicit. Every new feature increases the language's complexity and surface area, and I fear this one is not going to pay its way.

  * Does this proposal fit well with the feel and direction of Swift?

Yes, but I worry that it may be too early to add it. Other features in this space, like truly generic variadics, may well obsolete anything we do today. I’m not sure we should be designing convenience features that are likely to overlap with more general features coming down the road unless the inconvenience is very painful… which I personally don’t find it to be.

It isn’t clear to me how generic variadics might obsolete the functionality of this proposal. Can you elaborate on that?

Not sure if this is exactly what Dave has in mind, but an idea that comes to mind: we could say that structs and classes have a magic "members" tuple and typealias:

struct Foo {
  var x, y: Int

  // Implicit members
  typealias Members = (x: Int, y: Int)
  var members: Members {
    get { return (x: x, y: y) }
    set { (x, y) = newValue }
  }
}

With plausible future features for forwarding tuples as arguments, then the memberwise initializer could be implemented like this:

      // Say that a parameter declared 'x...' receives a tuple of arguments labeled according to its type,
      // like '**x' in Python
init(members...: Members) {
  self.members = members
}

And I think all your other use cases could be covered as well.

That's exactly what I had in mind. Thanks, Joe!

Is there any chance of generic variadics along these lines being part of Swift 3? Or is this down the road further?

Maybe you would feel differently if you were an app developer. Different kinds of code have different needs. The most important use cases I have in mind are related to UI code, which is often the majority of the code in an app.

Matthew

···

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

On Jan 7, 2016, at 10:32 AM, David Owens II <david@owensd.io <mailto:david@owensd.io>> wrote:

And this is more clear than this?

class Foo {
  var x,y,z: Int
  init(x: Int, y: Int, z: Int) {
    self.x = x
    self.y = y
    self.z = z
  }
}

No, it isn't, but Matthew asked… I'm personally not too motivated to support anything more than all-or-nothing memberwise initialization, and tend to agree that anything more specialized deserves an explicit implementation.

My biggest problem is what does member wise even mean the averts person. And a naked ... Is a spread operator in most languages and doesn't mean anything inherently.

If you want to bikeshed on the placeholder we use be my guest! I think a placeholder is necessary and `…` feels best in Swift but wouldn’t be opposed to changing it if you are able to convince most people that you have a better idea.

···

On Jan 8, 2016, at 10:08 AM, Tal Atlas <me@tal.by> wrote:

I worry about arguing about the specifics because this is way better than the current state but I very much think the details of this proposal are confusing to the average developer and is more restrictive to any future use of the spread operator.

On Thu, Jan 7, 2016 at 8:25 PM Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> We will probably have warning flags eventually.

I don't know what makes you think so. The language has so far carefully avoided this, and we've heard some pretty strong statements from the core team against the idea of compiler flags and incompatible dialects.

--
Brent Royal-Gordon
Architechies

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

It’s a common use case to have a private stored object that’s set
externally via the initializer, I think having a solution that doesn’t
support that would be a big mistake.

···

On Fri, Jan 8, 2016 at 11:08 AM Tal Atlas <me@tal.by> wrote:

My biggest problem is what does member wise even mean the averts person.
And a naked ... Is a spread operator in most languages and doesn't mean
anything inherently.

I worry about arguing about the specifics because this is way better than
the current state but I very much think the details of this proposal are
confusing to the average developer and is more restrictive to any future
use of the spread operator.

On Thu, Jan 7, 2016 at 8:25 PM Brent Royal-Gordon via swift-evolution < > swift-evolution@swift.org> wrote:

> We will probably have warning flags eventually.

I don't know what makes you think so. The language has so far carefully
avoided this, and we've heard some pretty strong statements from the core
team against the idea of compiler flags and incompatible dialects.

--
Brent Royal-Gordon
Architechies

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

Do you have an example of where you would want a caller to initialize a property, but then overwrite the value they provide during initialization?

Sure, how about something like a Rect type that always guarantees it's in "standard" form (e.g. no negative sizes):

struct StandardRect {
    var origin: CGPoint
    var size: CGSize {
        didSet {
            // ensure standardized form here
        }
    }

    memberwise init(...) {
        if size.width < 0 {
            origin.x += size.width
            size.width = -size.width
        }
        if size.height < 0 {
            origin.y += size.height
            size.height = -size.height
        }
    }
}

This is a good example. Thanks!

Actually I do not like this example for several reasons: (1) I would make the rectangle an immutable type with let properties, (2) the didSet already seems to do what is encoded in the memberwise init, so this seems to be redundant, (3) the memberwise init is so complex that having the automatic initialization feature is not really worth it for this example, especially as it seems to require using var properties instead of let properties to do the overwriting.

1) Why would you make it immutable? That helps nothing and only serves to make the type harder to use. Structs should _rarely_ be immutable, you should always default to mutable and only make things immutable if there's a good reason for it. If the struct itself is in an immutable position then it inherits the immutability, which handles all of the reasons why you might otherwise default to immutable.

Hmm, food for thought… guess I still haven’t completely understood Swift’s handling of immutability… thanks for pointing that out!

2) didSet isn't triggered in init. There's no redundancy.

You are right, of course. I forgot that when I wrote the mail.

3) You really really want var properties anyway, it's pointless to use let properties.

I think cases like this will be rare so I still think a warning is a good idea. Something like -Wno-overwrite-memberwise-init would allow it to be suppressed in cases where you actually do intend to do this. Would that satisfy you?

No. It's not appropriate to have the only way to suppress a warning on perfectly legal code to be passing a flag to the swiftc invocation. Especially because we have no precedent yet for even having flags like that.

What's wrong with the suggestion to make the warning behave the same way as dead store warnings (e.g. warn if the property is overwritten without any prior reads)? We already have logic for doing this kind of analysis.

I think this would not be sufficient, because this would not allow overwriting a property based on the value of another property which might be necessary as well.

That seems much less likely to be necessary, because if you're doing that, then you're completely ignoring one of your parameters.

Actually isn’t this what happens in your example? The property origin is overwritten without being read, so this would generate the warning, or did I understand something wrong?

Origin is being modified. Modification reads it first. `x += 2` reads `x` before writing to it.

I stand corrected.

-Thorsten

···

Am 08.01.2016 um 19:58 schrieb Kevin Ballard via swift-evolution <swift-evolution@swift.org>:
On Fri, Jan 8, 2016, at 12:56 AM, Thorsten Seitz wrote:

Am 08.01.2016 um 00:41 schrieb Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:
On Thu, Jan 7, 2016, at 03:11 PM, Matthew Johnson wrote:

On Jan 7, 2016, at 3:31 PM, Kevin Ballard <kevin@sb.org <mailto:kevin@sb.org>> wrote:
On Thu, Jan 7, 2016, at 07:12 AM, Matthew Johnson wrote:

-Kevin Ballard

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

Are you suggesting that this attribute would be applied to a specific initializer? Or would this be shared by all memberwise initializers of the type in some way?

I meant it as a type-level declaration.

The mental model here is that in many cases, a type with multiple public initializers winds up (or at least should wind up) like this:

- there’s a common “package” comprised of:
  - the input parameters (e.g. init args)
  - the boilerplate assignments (in the init)
- each init has some additional parameters/logic not shared with the other

For another example, this is lightly-adapted from actual code:

class PlayerItemWrapper : NSObject {
  let previewInfo: PreviewInfo
  let itemDescriptor: ItemDescriptor
  let metricCollector: MetricCollector
  let playerItem: AVPlayerItem

  // assume trivial assignments followed by observation-setup logic:
  required init(previewInfo: PreviewInfo, itemDescriptor: ItemDescriptor, metricCollector: MetricCollector, playerItem: AVPlayerItem)

  // these exist for convenience only; we have them b/c we don’t want to directly use
  // the corresponding AVPlayerItem convenience methods, since we also want to
  // control some of automatically loaded keys (etc.)
  convenience init(previewInfo: PreviewInfo, itemDescriptor: ItemDescriptor, metricCollector: MetricCollector, asset: AVAsset)
  convenience init(previewInfo: PreviewInfo, itemDescriptor: ItemDescriptor, metricCollector: MetricCollector, assetURL: NSURL)
  convenience init(previewInfo: PreviewInfo, itemDescriptor: ItemDescriptor, metricCollector: MetricCollector, assetDescriptor: AssetDescriptor)
}

…where you can easily see these inits have the structure of a “common package” + per-init details.

Under the organization above, it then makes sense to me to have a type-level declaration that is able to declare that type’s “official” common package, which would let the above be condensed into something like:

class PlayerItemWrapper : NSObject {
  @memberwise(previewInfo, itemDescriptor, metricCollector)
  // ^ need to be explicit here to *omit* playerItem, unless we
  // also plan to immediately have an @nomemberwise declaration
  // we can apply to individual properties
  let previewInfo: PreviewInfo
  let itemDescriptor: ItemDescriptor
  let metricCollector: MetricCollector
  let playerItem: AVPlayerItem

  // assume trivial assignments followed by observation-setup logic:
  memberwise required init(..., playerItem: AVPlayerItem)

  // these exist for convenience only (and would need a way to forward the …
  // to the required init, e.g. `self.init(…, playerItem: _)`, which is seems to be
  // noted as a future direction in the current proposal
  memberwise convenience init(…, asset: AVAsset)
  memberwise convenience init(..., assetURL: NSURL)
  memberwise convenience init(..., assetDescriptor: AssetDescriptor)
}

…and because writing out such parameter lists explicitly is a bit of a pain, the hope is that it’d also be possible to simply write e.g. @memberwise w/out an explicit list and get some automatically-inferred list synthesized on your behalf wherever possible.

Hope this clears up the kind of thing I was trying to sketch earlier. It is at a different point in the flexibility/feature spectrum for sure, and not thought-through too thoroughly either.

I also made a significant earlier error: it shouldn’t be an error if some @memberwise’s $parameterList was *incomplete*, the only error is if it contains invalid/unrecognized names.

I don’t think applying this to a specific initializer gains enough over just writing the initializer manually to be worthwhile. If we were going to do something specific to a single initializer, something along the lines of David’s suggestion makes the most sense to me.

On the other hand, if you are suggesting something type-wide that would be shared by all memberwise initializers, this is really an alternate way to accomplish an opt-in model. Rather than applying an attribute to properties you would have a declaration specify which properties are included in memberwise initializers, with the ability to specify order, labels, and defaults if necessary.

This is what I meant, essentially. An explicit @memberwise as per the above saves minimal effort if you had to do it on a per-init basis, but in scenarios with a bunch of basic parameters + various convenience inits for the remaining parameters it could still be a net win.

That might be a reasonable syntax for an opt-in model. As noted in the proposal, an opt-in model could be added by a future enhancement and would be used in place of the automatic model if the opt-in syntax was present.

If this goes anywhere I am hoping someone will improve the syntax, actually, what I sketched seems clunky and non-idiomatic.

In any case, having seen how this discussion has gone I think it’s safe that at least in hindsight starting from the purely-automatic/purely-inferred synthesis standpoint may ultimately have made this proposal than it needs to have been.

It’s a *really* good goal — I’d like to be able to get the memberwise init synthesized automatically where possible! — and I think within the design constraints you’ve been working under it may not be possible to do it that much better than as per your proposal…but I think the conclusion here is that “it seemed plausible to extend the memberwise-init synthesis like this, but if you work through it in enough detail to make it concrete, it has a surprising amount of intrinsic complexity/inference rules/edge-cases to worry about”.

That’s my overall 2c here (along with what’s in the earlier emails).

···

On Jan 8, 2016, at 12:51 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Yeah. Still brainstorming here :-)

So you know, I didn't really mean to put you specifically on the spot here; you're obviously trying to find concrete solutions to these problems.

No offense taken, I’m really interested in the opinion of everyone, that’s why I’m posting here :-)

But it seems to me that what we're going to end up with is that, instead of having this:

   public init(members...: Members) {
       self.members = members
   }

We'll have something more like:

   public init(members...: Members.Public.Initializable) {
       self.members.public.initializable = members
   }

Yes, that’s just what I’m trying to find out: I do like the Members-based idea but wanted to get a clearer understanding how it might *really* unfold before casting my vote for the current proposal.

I will be sharing my vision for a long-term direction later today or tomorrow. It allows us to accept this proposal as a step forward while still having a long-term path that relies on more general features, with a clean migration path when the more general features are ready.

I hope you'll be willing to support this proposal as a good first step.

Matthew

···

Sent from my iPad

On Jan 10, 2016, at 4:08 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

Am 10.01.2016 um 10:37 schrieb Brent Royal-Gordon <brent@architechies.com>:
On Jan 10, 2016, at 1:17 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Which looks a whole lot worse than the original when you compare it to this proposal's:

   public memberwise init(...) {}

You are certainly right, although one might argue that it has the advantage of spelling out a little bit more explicitly which members are hiding behind the "…“, i.e. it should be immediately clear that let properties with an initial value would not be part of Members.Public.Initializable, shouldn’t it?
Same for internal or private properties.
Whether that would be worth the longer form I’m not sure myself. At least it’s still just one simple assignment.

Exempting properties will certainly look even worse:

public init(members…: Members.Public.Initializable.except(i), x: Int) {
   self.members.public.initializable.except(i) = members
   i = 2 * x
}

In addition the syntax would need some changes to differentiate between regular property access, accessing the special property "members" and narrowing operations.

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

Hi Matthew,

But it seems to me that what we're going to end up with is that, instead of having this:

  public init(members...: Members) {
      self.members = members
  }

We'll have something more like:

  public init(members...: Members.Public.Initializable) {
      self.members.public.initializable = members
  }

Yes, that’s just what I’m trying to find out: I do like the Members-based idea but wanted to get a clearer understanding how it might *really* unfold before casting my vote for the current proposal.

I will be sharing my vision for a long-term direction later today or tomorrow. It allows us to accept this proposal as a step forward while still having a long-term path that relies on more general features, with a clean migration path when the more general features are ready.

That sounds great! Would you mind sharing a sketch of your idea today just to get a rough feeling before the review ends?

I would like to. I need a little bit more time to finish the drafts before sharing. I have dad duty on Sundays so I won't be able to do that until until my son goes to bed. I am hoping to be ready to share them by about 8 or 9 CST tonight. That still leaves a few hours while the review is open.

Matthew

···

Sent from my iPhone

On Jan 10, 2016, at 9:26 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 10.01.2016 um 15:03 schrieb Matthew Johnson <matthew@anandabits.com>:
On Jan 10, 2016, at 4:08 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

Am 10.01.2016 um 10:37 schrieb Brent Royal-Gordon <brent@architechies.com>:

I hope you'll be willing to support this proposal as a good first step.

I reckon so as I do value your proposal highly, but I’d prefer to get at least a short glimpse up front :-)
It doesn’t have to be fleshed out in detail, just to get a rough idea about the migration path. That would be great.

-Thorsten

Unfortunately I’m in the wrong time zone: 8 pm CST means 3 am of the next day here in Europe and I’d rather not get up in the middle of the night :-)
Bad luck that today you have dads duty but that’s life and there are more important things than Swift evolution. I mean that sincerely, so don’t worry about it.

I hadn’t expected a full draft of your vision, I just had hoped for one or two lines to get an idea of your vision, no need to finish the draft for that.

Hi Thorsten. I have time for a quick teaser. It may or may not mean much without the details so watch for those tonight / in the morning.

The general approach I plan to outline uses partial initializers, concise syntax for declaring memberwise partial initializers, a general parameter forwarding mechanism, and sugar for directly forwarding to a partial initializer. Obviously this is more than just one proposal.

There is a very clear and straightforward migration path from the current proposal to the general solution if the necessary features are accepted.

More on this later tonight.

Matthew

···

Sent from my iPhone

On Jan 10, 2016, at 11:20 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

But I understand that now is a bad moment to send me something, so no worries. Have a nice day with your son!

-Thorsten

Am 10.01.2016 um 16:35 schrieb Matthew Johnson <matthew@anandabits.com>:

On Jan 10, 2016, at 9:26 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Hi Matthew,

Am 10.01.2016 um 15:03 schrieb Matthew Johnson <matthew@anandabits.com>:

On Jan 10, 2016, at 4:08 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

Am 10.01.2016 um 10:37 schrieb Brent Royal-Gordon <brent@architechies.com>:

But it seems to me that what we're going to end up with is that, instead of having this:

  public init(members...: Members) {
      self.members = members
  }

We'll have something more like:

  public init(members...: Members.Public.Initializable) {
      self.members.public.initializable = members
  }

Yes, that’s just what I’m trying to find out: I do like the Members-based idea but wanted to get a clearer understanding how it might *really* unfold before casting my vote for the current proposal.

I will be sharing my vision for a long-term direction later today or tomorrow. It allows us to accept this proposal as a step forward while still having a long-term path that relies on more general features, with a clean migration path when the more general features are ready.

That sounds great! Would you mind sharing a sketch of your idea today just to get a rough feeling before the review ends?

I would like to. I need a little bit more time to finish the drafts before sharing. I have dad duty on Sundays so I won't be able to do that until until my son goes to bed. I am hoping to be ready to share them by about 8 or 9 CST tonight. That still leaves a few hours while the review is open.

Matthew

The latter I'm afraid.

I was just discussing this design space with Chris Willmore, who's been working on revamping how our function type model works. If we move to a multiple-argument model for functions rather than the current every-function-takes-a-tuple-argument model, then we will likely need at least limited support for packing and unpacking tuples from and to arguments in order to avoid regressing at argument forwarding use cases. However, even that limited packing/unpacking functionality might be enough to seriously consider a more general magic "members" property as an alternative.

I don’t mind discussing an alternative using this approach. If we’re going to do that I think it must be clear how it would cover various intended use cases in detail. Specifically, how would we address:

1. Default parameter values (at least for `var` properties)
2. `let` properties: it seems pretty magical indeed if the computed `var` property exposing the tuple could be used to initialize a `let` property.

Yeah, this member would need special initialization abilities, I agree.

It seems odd that this would be deemed acceptable. Would the `members` member be usable outside of an initializer? It seems like that wouldn’t be possible if there were a `let` property. But it also seems weird if its availability was determined by whether you have a `let` property or not.

3. Partial memberwise initialization exposing a subset of members following some kind of “automatic” or “opt-in” model for determining the subset.

Seems to me that could be done by factoring the interesting subsets into structs, e.g.:

class Foo {
  internal struct MemberwiseProperties {
    var x,y,z: Int
  }
  internal var state: MemberwiseProperties

  init(members...: MemberwiseProperties.Members) {
    state.members = members
  }
}

Sure you could do that, but it doesn’t seem like an acceptable alternative to me. Now the members must be prefixed with an identifier everywhere they are used. I don’t see why this is acceptable when requiring `self` is not.

That's a tradeoff, yes.

Are there compelling reasons to go down this road rather than the current proposal? What use cases does it address that the current proposal does not?

I find this approach interesting because it enables the easy implementation of at least most memberwise initializers, using more generally useful language features I think we are going to eventually want for argument forwarding and compile-time metaprogramming anyway. If we had already had these features, would you still have been compelled to write your proposal?

-Joe

···

On Jan 7, 2016, at 10:22 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 7, 2016, at 12:14 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 10:03 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 7, 2016, at 11:39 AM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 8:28 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

Do you have any concrete examples in mind?

-Joe

···

On Jan 7, 2016, at 10:49 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 7, 2016, at 12:37 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 10:32 AM, David Owens II <david@owensd.io <mailto:david@owensd.io>> wrote:

And this is more clear than this?

class Foo {
  var x,y,z: Int
  init(x: Int, y: Int, z: Int) {
    self.x = x
    self.y = y
    self.z = z
  }
}

No, it isn't, but Matthew asked… I'm personally not too motivated to support anything more than all-or-nothing memberwise initialization, and tend to agree that anything more specialized deserves an explicit implementation.

Maybe you would feel differently if you were an app developer. Different kinds of code have different needs. The most important use cases I have in mind are related to UI code, which is often the majority of the code in an app.

And FYI, many of us are or have been app developers in the past, we aren't (all) ivory tower academic purists here.

-Joe

···

On Jan 7, 2016, at 10:49 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 7, 2016, at 12:37 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 10:32 AM, David Owens II <david@owensd.io <mailto:david@owensd.io>> wrote:

And this is more clear than this?

class Foo {
  var x,y,z: Int
  init(x: Int, y: Int, z: Int) {
    self.x = x
    self.y = y
    self.z = z
  }
}

No, it isn't, but Matthew asked… I'm personally not too motivated to support anything more than all-or-nothing memberwise initialization, and tend to agree that anything more specialized deserves an explicit implementation.

Maybe you would feel differently if you were an app developer.

It’s not a universal truth that the majority of code in an app is UI code, unless you’re specifically talking about really small apps or essentially apps that are views for a server. The UI code to non-UI code ratio in a product like Word (for Mac) is no where near “majority”.

-David

···

On Jan 7, 2016, at 10:49 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jan 7, 2016, at 12:37 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 10:32 AM, David Owens II <david@owensd.io <mailto:david@owensd.io>> wrote:

And this is more clear than this?

class Foo {
  var x,y,z: Int
  init(x: Int, y: Int, z: Int) {
    self.x = x
    self.y = y
    self.z = z
  }
}

No, it isn't, but Matthew asked… I'm personally not too motivated to support anything more than all-or-nothing memberwise initialization, and tend to agree that anything more specialized deserves an explicit implementation.

Maybe you would feel differently if you were an app developer. Different kinds of code have different needs. The most important use cases I have in mind are related to UI code, which is often the majority of the code in an app.

Matthew

If the general features were truly able to handle this on their own maybe not. But they don’t.

This approach requires the magic property or properties with unusual behavior in order to work. I don’t think the approach you suggested for partial memberwise initialization is sufficient and even more magic would be required if we wanted to address that.

I am still not sure how default parameter values would be handled so I can’t evaluate that. Do you have an idea of how that might be handled?

I understand the desire to rely on more general features, but I think at least as much magic would be required and I am not convinced that the solution would be a better one. The primary difference seems to be the fact that the magic isn’t visible in the surface syntax. But that is somewhat illusory, the magic would still exist.

Matthew

···

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

On Jan 7, 2016, at 10:22 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 7, 2016, at 12:14 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 10:03 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 7, 2016, at 11:39 AM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 8:28 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

The latter I'm afraid.

I was just discussing this design space with Chris Willmore, who's been working on revamping how our function type model works. If we move to a multiple-argument model for functions rather than the current every-function-takes-a-tuple-argument model, then we will likely need at least limited support for packing and unpacking tuples from and to arguments in order to avoid regressing at argument forwarding use cases. However, even that limited packing/unpacking functionality might be enough to seriously consider a more general magic "members" property as an alternative.

I don’t mind discussing an alternative using this approach. If we’re going to do that I think it must be clear how it would cover various intended use cases in detail. Specifically, how would we address:

1. Default parameter values (at least for `var` properties)
2. `let` properties: it seems pretty magical indeed if the computed `var` property exposing the tuple could be used to initialize a `let` property.

Yeah, this member would need special initialization abilities, I agree.

It seems odd that this would be deemed acceptable. Would the `members` member be usable outside of an initializer? It seems like that wouldn’t be possible if there were a `let` property. But it also seems weird if its availability was determined by whether you have a `let` property or not.

3. Partial memberwise initialization exposing a subset of members following some kind of “automatic” or “opt-in” model for determining the subset.

Seems to me that could be done by factoring the interesting subsets into structs, e.g.:

class Foo {
  internal struct MemberwiseProperties {
    var x,y,z: Int
  }
  internal var state: MemberwiseProperties

  init(members...: MemberwiseProperties.Members) {
    state.members = members
  }
}

Sure you could do that, but it doesn’t seem like an acceptable alternative to me. Now the members must be prefixed with an identifier everywhere they are used. I don’t see why this is acceptable when requiring `self` is not.

That's a tradeoff, yes.

Are there compelling reasons to go down this road rather than the current proposal? What use cases does it address that the current proposal does not?

I find this approach interesting because it enables the easy implementation of at least most memberwise initializers, using more generally useful language features I think we are going to eventually want for argument forwarding and compile-time metaprogramming anyway. If we had already had these features, would you still have been compelled to write your proposal?

Here is an example where partial memberwise initialization would apply. It is similar to something in a real project:

public class FontPicker: UIControl {
  public let fonts: [UIFont]

  public var fontSize: CGFloat = 22
  public var foregroundColor: UIColor = UIColor.darkGrayColor()
  public var backgroundColor: UIColor = UIColor.whiteColor()
  // A bunch of other appearance attributes here

  private let collectionView: UICollectionView
  private let layout: UICollectionViewLayout
  // other internal state required by the implementation

  public memberwise init(...) {
    // configure the collection view and add it as a subview
  }
}

A couple points are relevant here:

1. Memberwise initialization is very valuable for the appearance attributes, but is useless if it exposes our implementation details.

2. In many custom UI widgets the appearance attributes don’t really need to be mutable post-initialization. At the same time, it is necessary to allow, but not require a value to be specified. It would be ideal if they were `let` properties with a default value, but still able to participate in memberwise initialization. Without that capability we are forced to choose between the advantages of using a `let` property and the advantages of memberwise initialization.

UI widgets are a great example. View controllers often have a similar divide between state provided by the user and state related to internal implementation details.

Matthew

···

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

On Jan 7, 2016, at 10:49 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 7, 2016, at 12:37 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 10:32 AM, David Owens II <david@owensd.io <mailto:david@owensd.io>> wrote:

And this is more clear than this?

class Foo {
  var x,y,z: Int
  init(x: Int, y: Int, z: Int) {
    self.x = x
    self.y = y
    self.z = z
  }
}

No, it isn't, but Matthew asked… I'm personally not too motivated to support anything more than all-or-nothing memberwise initialization, and tend to agree that anything more specialized deserves an explicit implementation.

Maybe you would feel differently if you were an app developer. Different kinds of code have different needs. The most important use cases I have in mind are related to UI code, which is often the majority of the code in an app.

Do you have any concrete examples in mind?

-Joe

And this is more clear than this?

class Foo {
  var x,y,z: Int
  init(x: Int, y: Int, z: Int) {
    self.x = x
    self.y = y
    self.z = z
  }
}

No, it isn't, but Matthew asked… I'm personally not too motivated to support anything more than all-or-nothing memberwise initialization, and tend to agree that anything more specialized deserves an explicit implementation.

Maybe you would feel differently if you were an app developer. Different kinds of code have different needs. The most important use cases I have in mind are related to UI code, which is often the majority of the code in an app.

Matthew

It’s not a universal truth that the majority of code in an app is UI code, unless you’re specifically talking about really small apps or essentially apps that are views for a server. The UI code to non-UI code ratio in a product like Word (for Mac) is no where near “majority”.

I did say “often”, not always. I didn’t make a claim to any universal truth.

Also, by “app” I meant typical consumer mobile apps that make up most of the stuff on the app store aside from games. I did not mean to refer to software in general. I could have been more clear about that.

···

On Jan 7, 2016, at 1:23 PM, David Owens II <david@owensd.io> wrote:

On Jan 7, 2016, at 10:49 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 7, 2016, at 12:37 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 7, 2016, at 10:32 AM, David Owens II <david@owensd.io <mailto:david@owensd.io>> wrote:

-David