Pitch: if/else expressions

(Dave Abrahams) #21

For me, the new information is that even advanced programmers have a hard time reading ternary chains as though they aren't nested. Even though a strict BNF grammar for the following would require nesting, no human thinks of if/else chains like this

if a {b} 
else if c {d} 
else {e}

as "nested if statements" unless they are writing a parser, but there are some people who can't map that same way of reading onto:

x = a ? b
    : c ? d 
    : e

Maybe this is old news to you, but for me it's an insight.

'Scuse me, but I haven't "banned," or even proposed to ban, anything. The pitch is entirely additive.

And no, I don't think people will end up writing that, because it's not better or more readable than any of the alternatives. Ternary expressions like this should almost never have side-effects (your doXXX() functions, presumably). The best answer for code like that is probably to break it out into a function with a meaningful name that describes the side-effects. Failing that, I'd still rather read this.

let r: SomeType
if someCondition  { 
    doSomething()
    r = firstThing()
}
else if otherCondition {
    doSomethingElse()
    r = secondThing() 
}
else { 
   doYetAnotherThing()
   r = thirdThing()
}
some.long.lvalue[expression] = r

Finally, if you absolutely must be maximally terse, this is still a better alternative:

some.long.lvalue[expression] 
    = if someCondition  { (doSomething(), firstThing()).1 }
        else if otherCondition { (doSomethingElse(), secondThing()).1 }
        else { (doYetAnotherThing(), thirdThing()).1 }
(Xiaodi Wu) #22

The ternary operator isn't being misused; it's not being used to its fullest potential. But this is an operator that will not go away, neither in Swift nor in any of the numerous languages that users are sure to encounter. Mastery of ?: is portable to many other languages, just as mastery of many other concepts in Swift is portable.

As pointed out here already, users already have many options to accomplish the same tasks in other ways, and if switch expressions are made possible there will be yet another. What I'm seeing is an argument for better education, not one for yet more syntax. In fact, the latter option will not only do nothing to help users master ?: (which they will still encounter when reading code), but by adding more syntax to the language it will only add to the difficulty of mastering the language for all users and increase the number of choices users have to make to accomplish the same thing, which sets us back instead of forward in terms of making the language accessible to learners.

What we lack as part of the Swift project is a companion cookbook to TSPL that shows best practices, which would be applicable for beginners, intermediate, and advanced users alike--examples of effective use of ternary chaining would definitely be one topic to include (and use of optional chaining and ?? would be others that fall in the same bin).

3 Likes
(Dave Abrahams) #23

I was in the "better education" camp until just a few days ago. My conclusion, after personally and patiently explaining to colleagues how to read ternary chains, and getting repeated blank stares and a failure to convince them in response, is that the problem really is with the syntax. The fact that it's easy for you and me doesn't mean it can work for everybody.

7 Likes
(Jeremy Pereira) #24

I'm not disagreeing with you that it is easy to write difficult to understand ternary conditions even if you format them in a nice way. And I absolutely agree that your alternative is easier to read. I just do not agree your information is new.

You said that the contents of the braces would be limited to single expressions that all return the same type. Everywhere else in Swift we are allowed to put multiple statements in braces. So you are overloading the semantics of braces (and the if ... else keywords). I'd rather you proposed something else e.g. use parentheses instead.

(Xiaodi Wu) #25

If we take this experience as representative, then we have an unfixable problem. We can't eliminate the need to read ternary chains from the language. If, as you claim, it is unteachable, then we are stuck.

But one cannot conclude that there is no pedagogical method to teach users how to use ternary chains because one pedagogical attempt did not succeed. We needn't invent these from scratch, either: articles that teach this in varying ways abound, thanks to its being shared among the majority of the most commonly used languages.

1 Like
(Dave Abrahams) #26

But one cannot conclude that there is no pedagogical method to teach users how to use ternary chains because one pedagogical attempt did not succeed.

That’s not my conclusion. My conclusion is that it’s hard to teach and learn, and that is enough.

5 Likes
(Michel Fortin) #27

I agree that nested ternary ?: looks like a soup of punctuation to many.

let x = a ? b : c ? d : e

It'll look messy regardless of how you format it. While this reads particularly well:

let x = if a { b } else if c { d } else { e }

So I'm in favor. I'm not even sure if it enters in the "another thing to teach" category or the "another thing that works as expected the first time you try" category.

4 Likes
#28

I find that Xcode's indenting rules for Swift (but not ObjC :frowning:), allow for the following:

let x = a
    ? b
    : c
        ? d
        : e 

This example doesn't really look good because the conditions and values are both really short. However, understanding that a and c are conditions only requires a 1-line look-ahead. Separating the true and false branches makes them easy to spot, especially with the leading punctuation of the ternary operator. The indentation clarifies the nesting of conditions.

I am all for if statements becoming expressions, but I think the ternary operator should get another chance.

3 Likes
(Xiaodi Wu) #29

I can buy that. This calls for us to step up to the challenge, looking for ways to teach it better. Since the ternary operator can't be removed, it doesn't address the problem to create more syntax; it only adds to the amount of syntax we must teach.


(Food for thought only, not seriously pitching--)

It also opens up an avenue to explore for the syntax of switch expressions: many languages use -> or => and , as punctuation, but one strategy to make ternary chaining more teachable would be to make its syntax more commonplace by using for punctuation in switch expressions the same ? and : as used in ternary chaining:

// Rust-like syntax
let x = switch y {
  1 => foo(),
  2 => bar(),
  _ => baz()
}

// ternary chaining-like syntax
let x = switch y {
  1 ? foo() : 
  2 ? bar() :
  baz()
}

When this spelling is taught in a separate context as part of a new feature (switch expressions), ternary chaining in its original form would then become comprehensible to users on the basis of pattern recognition, totally bypassing the need for users to reason through the syntax as a form of chaining.

2 Likes
(Dave Abrahams) #30

That’s only true to the extent that you measure success in terms of every swift user being comfortable reading every possible swift program. The short form could fall into disuse, at least in chained expressions, and could be discouraged there by linters and style guides. To the extent I have intuition about these things (questionable because I thought people would learn to read ?:) the proposal adds almost zero cognitive load for a person learning the language syntax.

2 Likes
(Xiaodi Wu) #31

If I have learned anything from these forums, it is that any intuition about what's easy or hard to learn can be surprisingly overturned by real-world usage.

I don't think every Swift users needs to be comfortable reading any Swift code, but no Swift user should be uncomfortable with reading code that merely uses basic control flow syntax and because of that syntax, particularly when the usage (i.e., chaining) is one anticipated by the designers of the language. Accomplishing this "success" in learning and teaching is in my mind a bare minimum bar to clear, and by your experiences recounted here, we have flunked.

2 Likes
(Frederick Kellison-Linn) #32

I feel like the readability benefits of an if expression over the ternary are pretty apparent, but I’m still not convinced of their usefulness over other alternatives, e.g. wrapping an if statement in an immediately called closure. At that point we’re no longer dealing with the issue “even experienced programmers have significant trouble understanding this construct” and instead are discussing “this requires more cumbersome syntax than I would like”. Some of the problems with alternatives that people have complained about have potential solutions as well! Improved type inference could prevent having to specify a return type for closures that consist of just a single switch or if statement where all paths return the same type.

I’m not necessarily against if expressions, but I’d like to know if there are more significant downsides to these other alternatives that haven’t already been raised.

I also wonder if we could adopt a syntax that makes the if expressions more visually distinct from their statement counterpart while still being readable. We could, for example, drop the braces to avoid some of the confusion that @jeremyp was concerned with:

some.long.lvalue[expression] 
    = if someCondition then firstThing()
      else if otherCondition then secondThing()
      else thirdThing()

Or, then could also be some sort of punctuation like =>. I think making these look quite different from statements would be valuable, especially since we’d be overloading some of the most (if not the most) heavily used keywords in the language.

3 Likes
(Nate Cook) #33

I've been on the receiving end of those same stares, and couldn't be more in favor of if expressions! (If the prevailing mood of the community would allow it, I'd also welcome switch expressions, primarily for the improvement to computed properties in enums.)

11 Likes
(Jon Shier) #34

Can I get an example of this motivating example?

(Chéyo Jiménez) #35

Let’s discuss switch expressions and alternative syntax in another thread?

2 Likes
(Matthew Johnson) #36

Sure, here you go:

// part of the model layer
enum Foo {
    case first
    case second
}

func displayStrings(for foos: [Foo]) -> [String] {
    // imaging formatting that might become common for switch expression closures
    return foos.map { switch {
       case .first:  NSLocalizedString(“Foo.first”, nil)
       case .second: NSLocalizedString(“Foo.second”, nil)
    }}
}

// or alternatively as an extension on `Foo` using the recently pitched ability to omit `return`
// in single-expression getters
extensions Foo {
    var localizedName: String { switch {
       case .first:  NSLocalizedString(“Foo.first”, nil)
       case .second: NSLocalizedString(“Foo.second”, nil)
    }}
}

Some of us see these features as integrally related. I pretty strongly feel that if we’re going to have expression syntax for conditionals modeled after the statement syntax we should do that across the board and not preference if / else just because ternary exists.

It am pretty certain that if this works for if / else people will try it for switch and be frustrated when it doesn’t work. The explanation that this is because ternary existed and the syntax was lacking will be unsatisfactory and seem very arbitrary. Unless there is a really strong reason to omit switch I see no reason to force that to be delayed to a separate proposal.

switch expressions were an extremely heavily requested feature before they made it onto the frequently rejected list (far more so than alternative syntax for ternary). The primary reason they landed there is that acceptable syntax could not be identified. If we make the decision to support statement-like syntax for if / else the syntax that should be used for switch is obvious and there is no reason to keep it on that list or further delay its consideration.

8 Likes
(Adrian Zubarev) #37

IIRC for computed properties on enums there was a different design floating around where the whole body becomes the switch statements body for convenience?! I don‘t remember in which thread it was first mentioned. Something like:

var property: Value {
case .first:
  ...
case .second:
  ...
}
(Matthew Johnson) #38

That’s pretty special case syntactic sugar. We can get very close to it with the more general switch expressions and implicit return for single-expression bodies. If we can get those in I’m not sure there is enough benefit to include a feature targeted specifically at enum computed properties and methods.

3 Likes
(Chéyo Jiménez) #39

I get the impression that the author wanted feedback specifically about the if/let expression and not switch expressions and/or new syntax.

I’ve spend a lots of time thinking about switch expressions and syntax that could unify them. I’ve seen many threads derailed by this unification mentality also, including me. I think a progressive approach could have a chance. I hope this goes to review as proposed.

1 Like
(David Zarzycki) #40

Hi Dave,

Personally, I don't think the problem you raise is really about the ternary operator or if/else expressions at the end of the day.

I too would support rewriting the ternary code, but not because of the ternary operator. To me, the problem is that code example is that a multi-line expression and I try to avoid multi-line expressions for two main reasons:

Many multi-line expressions can be rewritten as a series of simpler expressions with no additional lines of code required. By avoiding code overflow onto the next line, one improves both clarity and comprehension for unfamiliar readers of the code. Also, breaking multi-line expressions into smaller expressions is more friendly to those that like to set breakpoints by line and inspect temporary variables. Finally (and least important), breaking big multi-line expressions into smaller expressions will probably compile faster if a ton of type inference or overloads are involved.

Depending on one's coding style, multi-line expressions aren't friendly to the last line in an edit window. In other words, if an expression overflows a line and operators are involved, do you put the operator at the end of the first line? Or the beginning of the second?

let x = longStuff + // Friendly to code editing/review.
  otherLongStuff

Versus:

let x = longStuff // Not friendly to code editing/review.
  + otherLongStuff

Dave

1 Like