Why are memberwise initializers all-or-nothing?

Let's consider this struct:

struct Foo {
    var x = 1
    var y = 2
    var z = 3
}

The automatically created memberwise initializers we got for this one are:

  • Foo()
  • Foo(x:y:z:)

Now let's consider that one:

struct Bar {
    var x = 1
    var y: Int
    var z = 3
}

Same initializer requiring to pass all values, missing clear initializer for obvious reasons:

  • Bar(x:y:z:)

Why is it all-or-nothing, despite having default values? I think it would feel more natural if automatic memberwise initializers worked just like functions with default values, so in these cases:

  • Foo(x: Int = 1, y: Int, z: Int = 3)
  • Bar(x: Int = 1, y: Int = 2, z: Int = 3)

For structs that have multiple properties and many of them have a default value, that would be very convenient.

Why was it decided to make it the way it works now?

1 Like

There was a proposal for it but got deferred: swift-evolution/0018-flexible-memberwise-initialization.md at master · apple/swift-evolution · GitHub

Here’s the rationale for deferring this: [swift-evolution] [Review] SE-0018 Flexible Memberwise Initialization

There's also: Synthesize default values for the memberwise initializer by Azoy · Pull Request #936 · apple/swift-evolution · GitHub from Synthesizing default values for the memberwise initializer

I don't quite understand calling this feature sugar and trying to find a manual way to define this initializer. I'm pretty sure I'm missing something. So called sugar made Swift a Swift and sugar is one of the crucial ingredient of modern programming languages.

Swift supports default parameter values just fine, so I'm not sure why automatically generated initializer couldn't use those?

IIRC, SE-0018 was trying to add pure sugar in Swift 2, which was quite a chaotic time (around this time, we added function’s compound name, #selector and a few things that changed Swift in a big way). So something like SE-0018 has somewhat lower priority, esp. when the we aren’t even sure it’s the right approach and doing nothing doesn’t affect developers too much.

IMO, @Alejandro’s proposal is closer to what @Bear is asking though.
It seems that the thread just stagnate, perhaps one can revive it? Or add an implementation if it doesn’t have one?

I still don't understand why would it require a memberwise keyword or any additional syntax. Wouldn't it be possible to just create this initializer behind the scene? Like I said, I'm sure I am missing something, because if that was so simple, it would have been done like that. I mean to just declare default memberwise initializer as it would have used default values, just like a regular function with default values:

func x(a: Int = 1, b: Int = 2, c: Int = 3) {/*...*/}

Which we can call either way:

x()
x(a: 7)
x(b: 7)
x(c: 7)
x(a: 7, b: 7)
x(a: 7, c: 7)
x(b: 7, c: 7)
x(a: 7, b: 7, c: 7)

The goals of flexible memberwise intialization were much broader than that and could be added later. Some examples are being able to control the visibility of the synthesized initializer, including synthesizing a public initializer, being able to prevent some members from participating in the synthesized initializer, being able to define an initializer body that runs after the synthesized memberwise initialization happens, being able to opt-in to the synthesized initializer while also writing custom initializers in the type declaration.

If the only goal is supporting default values as default parameter arguments wouldn’t require anything more than an implementation and proposal to move ahead. However it would inevitably re-open the larger can of worms in discussion and review and it’s not certain what the general sentiment of the community would be or what the core team would decide. On the one hand, it seems like a small no-brainer addition. On the other hand, I think many of us still hope to see something more robust added eventually and the core team might want to wait until then to make a change.

Coming from the Drupal community who just went through a major refactor from straight PHP to symphony...

Everyone always wants more robustness and a better solution that caters to everything. Though this isn’t a bad thing, it delays actual progress.

What happens more often in professional, non collaborative community based projects, is iterative approaches. We build, we refactor, we add on, we remove.

I’d imagine if we went for this one simple approach in the interim, it would satisfy a small pain point.

if we ever implemented a more robust situation, we could then refactor this.

That being said, I understand with the evolution of things, making decisions now would be ones were stuck with for some time because the community takes so much effort to make decisions.

I hope simplicity will be the direction with Swift. Programming needs to evolve to more abstract, artistic world. At least that’s where I would love to see it, that’s why I became a programmer long time ago myself. I am not interested in telling the machine how to handle my idea. I want to express my ideas as naturally as I can.

I find programming to have quite a few negative effects on people who do it. That’s because it’s still very machine-oriented in many languages. But it gets better and Swift is certainly one of the best examples. Just hoping that it will not lose it while it grows, because some decisions are questionable.

I hope hardware will get fast enough to make most of today optimizations become micro-optimizations that don’t matter and don’t make me express my idea way around to make it fast enough.

I think most people will want that, but it takes an incredible amount of experience, planning, structuring, and restraint when trying to evolve a programming language to meet such goals. So far the Swift core team has shown all of those and progressing wisely and in increments is the way to get there.

Please point out examples when making summary statements like this. I highly doubt that you will find a decision that is not clearly reasoned about or has plenty of discussion around it. Of course you are free to question any decision that has been made but then be more specific please and also reason about your disagreement.

Many mathematical problems will not become easier or significantly faster if we have faster machines so I wouldn't count on that. Also an optimization doesn't become a "micro-optimization" because it is faster.

I hope to see you participate in many of the discussions on this forum so you can help push Swift in the direction you and many others would like it to progress!

1 Like

Absolutely! I’m not trying to discourage anyone from working on a first step. I’m just describing the uncertainty inherent in the process. I don’t think it’s clear what the outcome would be and anyone considering devoting time on this should be aware of that.

1 Like

This statement doesn't make any sense. If the hardware gets, let's say, 1000x faster, which is certainly possible with the change of the material, then any appropriate operation using this hardware will get 1000x faster. If we consider, let's say (my favorite example) correct Unicode handling slow to the point that we need to make a low level API to handle them, what does that mean? This means that the time needed for this operation matters. It's not because it's X times slower than addition or multiplication operation. And if we cut that time 1000 times then it will not matter. It doesn't make any difference if our operation takes 1/1000000000 microseconds or is 10 times slower and takes 1/100000000 microseconds. That's the trap that our minds tend to fall into - we are conditioned to compare everything relatively, while sometimes it doesn't really make any sense. Some people will spend 15 minutes going to a store to buy a chocolate for $1,50 instead of $2,00, but they will not go to another town to buy the same car for $29,499 instead of $29,999. This is how we are designed as humans. Same goes with discussions about optimizations. People will defend saving fractions of nanoseconds, because our minds can't imagine how in the (hopefully quite near) future the change in technology will make it really irrelevant. It will change the programming entirely. Yes, we will be solving more complex problems, but also, most of the operations considered slow today, will become fast enough to just don't care about optimizing them.

You can take a look at the topics I created. I'm not saying I completely don't agree to these decisions or I don't know why they were taken, but some of them are a little off balance of Swift's beauty and performance.

I think the idea is this thread is essentially orthogonal to the rest of SE–18. We can make the memberwise initialize synthesis aware of default values, as a discrete goal.

Then, sometime down the line, we can once again consider making it possible to specify the access level of the memberwise initializer, and to make it available even when other initializers are defined, and to mark properties as not participating.

The only notable difference between the current thread’s proposal and SE–18, is that SE–18 says:

Which accords with the official language guide:

However, we are now in the era of source compatibility, so I think we must leave the parameters in the same order that the existing memberwise initializer. This should be fine since every parameter has an argument label, meaning there isn’t any ambiguity.

This topic should probably go in the Swift Evolution / Pitches category though.

1 Like

Yeah, I have seen that statement about parameters order in The Swift Programming Language. I was wondering why is that recommended, I actually like to have a default value in between, sometimes it makes sense if, for example, two parameters out of 3 are linked together logically, but the second one of them makes sense to have a default value. It works perfectly fine with default values in between, so I’m not sure why it’s recommended to avoid it.

It gets weird when there are multiple parameters with the same type and label. Specifically, the compiler greedily assigns arguments to parameters, so the following does not compile:

func foo(_ a: Int = 0, _ b: Int) {
  print(a, b)
}

foo(1)
// error: missing argument for parameter #2 in call

But for the case at hand, where every parameter has a unique argument label, I think it will be fine.

Yes, I meant the situation when all of them are labeled. In my opinion sometimes it makes more sense to have a default value in the middle.

It’s interesting how there are so many edge cases with Swift, because it tries hard to be developer friendly for the most part. Most languages just don’t have cool features and there are less problems. I really appreciate that Swift syntax is way smarter.

That is indeed true. While the guide encourage us to put default value at the very end, it’s still a guide and Swift know enough to let us break it (it could easily be language-level enforcement O.o).

I think the it’s mostly about the callsite, since function call is most readable when it reads like a sentence. Default value in the middle could break the sentence easily in many cases, that’s why they want us to be a little more wary of it.