[Pitch] Last expression as return value

UPDATE: Many/most of my concerns raised in this thread have since been addressed.

Ultimately I am -1 on this.

This proposal addresses a genuine pain point for me, but it does so by introducing several more bigger pain points.

Swift already has a small problem of ambiguity between expressions and statements but this proposal seems to turn that small problem into a very big problem.

let ternary = bool ? "green" : "red"
let ifExpression = if bool { 
  "green"
} else {
  "red" 
}

The ternary is obviously an expression because it can't be anything else. The if could be either an expression or a statement. We are trained from decades of other languages to think of if as statements, so most of us are going to reflexively assume that it's a statement. When I read this as a statement, I get confused because, this statement isn't really stating (i.e. doing) anything. Both branches have a string expression and then don't do anything with that string expression. Then I realize "Oh this is an expression." and reevaluate it as such. If the type of the expression is not explicitly declared, then I have to do some extra work to determine what the type will be (and so does the compiler).

The current design is already problematic because of that ambiguity, but in practice this ambiguity problem isn't that large because it forces you to keep your expressions small. It's an okay tradeoff because (in some use cases) it is more readable than a ternary, and it makes switch expressions possible.

But this proposal leaves the door wide open to multiply the ambiguity as much as we want. If I'm not mistaken Swift currently has this limitation:

  1. A statement can contain an expression but...
  2. An expression cannot contain a statement.

Many on this thread view #2 as a pain point, and indeed it is, but they do not realize how it is also a benefit because it limits the ambiguity of expressions and statements. But after this proposal the following will be true:

An expression can contain a statement which can contain an expression which can contain a statement... Good luck trying to figure that out.

let ambiguousExpression = if bool {
  doSomeWork()
} else {
  print("This is a debug message.")
  doSomeOtherWork()
  doSomeAsyncWork {
    "String expression"
    switch myEnum {
      case one: "red"
      case two: 
        print("This switch is an expression. But it sure doesn't look like it.")
        "blue"
    }
  }
}

Is this an expression or a statement? I can clearly see that it's an expression because there is an = if but that is a very easy detail to miss, and it bears repeating that we have decades of training telling us that if is not an expression. But even after you know it's an expression, you still don't know the type, so you (and the compiler) have to do a ton more work to infer the type.

It is not obvious at the call site what type doSomeWork() returns, or if it returns anything at all.

It is not obvious that all branches return the same type. The compiler will be able to determine quickly that the types on the branches don't match and will then throw an error. But now the human has to spend more effort finding which branch doesn't match.

In short, I'm not convinced that an expression should be allowed to contain a statement. It makes a few use cases more readable, but opens the door for far more ambiguity.

13 Likes