I haven’t weighed in on this pitch but have watched avidly from the sidelines as various arguments have been put forth and pondered excessively. I know this isn’t a vote, but I’ll offer my opinion.
I’m very excited to see this pitch. I very much hope it arrives in some form.
If it were a perfect world, I’d love to see the result come from the unadorned last line.
If issues with that cannot be surmounted, I’ll be very happy with then or use. return: no, please, never.
I don’t much like the idea of parens: that’s ugly, and I feel the prefix of a leading keyword more clearly marks the result. And converting from a single to multi-line expression block using parens would require tedious changes to both ends of the expression.
Since this proposal seems likely to go forward in some way whether the community agrees on a way to do it or not, I would like to contribute what I consider to be the most preferable option for addressing the problem it aims to solve.
This exact problem has been solved before, e.g. in Rust, by implicitly "returning" the last expression in a block. With that in mind, I personally believe the preferable way to solve the problem is to finish the job started by SE-0255. That proposal mentions explicitly the main motivation for not always returning the last expression in a block: at the time, we didn't allow if/switch/do clauses as expressions (see Allow implicit return of the last expression even from bodies which consist of more than a single expression.).
That option is not open to us in Swift because conditionals are statements, not expressions in Swift. Changing Swift into an expression-oriented language would be a radical transformation to the language and is beyond the scope of this change.
Now we have the opposite problem: we do have if (etc) clauses as expressions, and it's weird.
So, instead of trying to bend Swift's syntax by adding more edge cases, I would much rather go back to Rust's existing precedent and enable implicit returns from the last line of any block. That would solve this problem at hand, and make Swift more internally consistent. For example, it has been an annoyance of mine since SE-0255 was introduced that adding e.g. a preceding print into a single-line implicit return causes it to no longer to return the value. Adding the implicit behaviour more generally would solve both issues.
But that perfectly highlights why use isn't great - the do expression isn't using the value 1, it's producing it (or "evaluating" to it, or "resulting" in it, or similar phrasing).
use, to me, throughout this discussion, has always read wrong because I find it mentally more related to inputs than outputs.
I'd still prefer the "((x, y))" form, just to not have an exception to the rule.
Single-statement if expressions are relatively new thing... We could even deprecate the "bare" form of single statement expression as well and always require "dressed up" form (nudging users towards the dressed up form via deprecation warnings initially):
You have to reason it as "use this value in the outer context" and not as in "the inner construct (do, if, etc.) will use the value". This is indeed different from return "returning" a value in a function definition where the caller could be anyone, wheras when "using" a value you can readilly see where it goes. I have no problem seeing "use this piece to fill that hole" as something different from "return this piece to an unknown caller".
That said, my comparison with return was more about how "returning" mirrors the return you see in the code, while "producing" doesn't mirror then at all. I think use/"using" is more coherent than then/"producing" in this regard. But this coherence could also be acheived with produce/"producing" and give/"giving".
Four examples that compare implicit vs use, save, or then:
if condition { value }
if condition { use value }
if condition { save value }
if condition { then value }
could be read as:
if true, (then) value
if true, (then) use value
if true, (then) save value
if true, (then) then value
or:
if true, consequence: value
if true, consequence: use value
if true, consequence: save value
if true, consequence: then value
These examples show do:
do { value }
do { use value }
do { save value }
do { then value }
and could be read as:
do value
do use value
do save value
do then value
Addendum regarding save:
We are, of course, saying that the value should be
saved for the assignment – or:
"the variable is assigned the value being saved"
Also: when we save something in an app, we hardly
think of "save" as meaning "with the exception of",
so I don't expect this to be a problem in real life.
Just throwing a +1 behind a keyword that’s in the imperative mood (use, produce, save, etc.), as opposed to then. All the other control flow keywords that result in a non-linear jump in what’s being executed (return, break, continue, throw) are also imperative, and having that “commanding” nature really helps highlight that execution is moving to another location, especially if you’re a beginner or coming back to Swift after a few years off and wondering what the heck that new keyword does.
I would probably vote for use as (at least to me) it implies that one value must be used as the value of the variable in question, whereas produce and save don’t necessarily carry the same connotation of being mandatory.
I’m perfectly fine with implicit last-expression returns being added for those that want it, but I’m a hard -1 on it being the only option - while I don’t think it’s all that confusing, I would personally never use it in my coding style as I like to make control flow jumps very explicit. I would probably even go as far as continuing to use immediately-executed closures if implicit last-expression returns were the only option.
But, there's not much more imperative than then. It's explicitly time-ordering. "This, then that".
use / produce doesn't imply any ordering (beyond whatever might be implied in the general sense that it appears subsequent to other statements). e.g.:
let x = if someBool {
then 42
print("True.")
} else {
7
}
let x = if someBool {
use 42
print("True.")
} else {
7
}
Neither is completely clear that the print call is in an invalid position, but then at least somewhat suggests so. use just says, well, "use 42". For what? Could be for the following statement(s) as much as anything else.
None of this particular matters in the long term, as folks will get used to any of these keyword candidates well enough.
And it does seem like there's a broad opposition to then specifically. I'm just a bit baffled as why, as none of the proffered justifications have been particularly compelling (most are just "I intuit the meaning to be this, which would be mistaken", which is valid as a data point suggesting a challenge for learning, but not super weighty when there's equally if not more logical counter-interpretations).
This will and should result in a warning, similarly to how post return keyword is handled. Maybe something like "Code after 'use' will never be executed".
With save, the {} saves its own value – and then the
value can be delivered to its recipient. The actual delivery
is commanded by the = sign. So, the word save, in itself,
would fully describe the intended action.
According to the pitch, this is an error, not a warning—then has to be the last statement in the block as there's no reason to have it not be.
Which raises a semi-related question: Does defer work as expected when paired with then? I can't think of any reason that they wouldn't work correctly together, but in this example,
func someComplexFunction() -> Int {
print("inside someComplexFunction()")
return 10
}
func anotherFunction(_ cond: Bool) {
print("entering anotherFunction")
defer { print("exiting anotherFunction")
let x = if cond {
defer { print("after someComplexFunction") }
print("cond is true")
then someComplexFunction()
} else { ... }
}
anotherFunction(true)
I would expect this to print
entering anotherFunction
cond is true
inside someComplexFunction
after someComplexFunction
exiting anotherFunction
@Ben_Cohen I want to raise this question again. What paradigm are you aiming at? From the proposal it seems like you're modelling "one exit point per branch". But what about the imperative interpretation of "then" suggested by @ellie20. It would allow as many exit points as needed, and IMO doesn't shift the paradigm as much as the proposed variant. Is it ruled out already?
I had missed that bit; that seems inconsistent. There's no reason either for a return statement to not be the last statement in a block, but it is legal. It also seems counter to the goal of making it easy to quickly modify a multi-line expression. In a debugging context I might occasionally want to change
func foo(bar: Bar, baz: Baz) -> Quux {
let x = doSomething(bar)
let y = doSomethingElse(baz)
return frobnicate(x, y)
}
to
func foo(bar: Bar, baz: Baz) -> Quux {
return someSpecificQuux
let x = doSomething(bar)
let y = doSomethingElse(baz)
return frobnicate(x, y)
}
I'd expect the same behavior from then: a warning, but it compiles and produces the earlier value, skipping the later statements. Of course I can comment out the other lines in either situation, but since this is how it already works for return, it's awkward to have a different rule here.