Pitch: Control Flow Negation Statements

Hi swift-evolution,

I've been working on a pitch I think you're all going to love, as well as the corresponding implementation


Control Flow Negation Statements

Introduction

This proposal adds negated forms of the various control-flow operations in the Swift language. Specifically, it introduces negated forms of if, guard, while, do, and defer.

Motivation

Swift is a uniquely expressive language, but there are still places where boilerplate is necessary. One egregious example of missing syntactic sugar revolves around condition negation. Frequently, developers want to conditionalize their code on a condition they intend to be false. Swift has a prefix operator, !, that allows for negating a boolean, but this can be difficult to see when in front of a large expression, and sometimes requires parentheses around nested logical expressions.

Instead, let’s provide negated forms of common control flow. While we’re at it, we can also add negated forms of do and defer, to complete the picture.

Proposed solution

I propose adding 5 new statements to Swift: ifn’t, guardn’t, whilen’t, don’t, and defern’t. These will be analogues to the if, guard, while, do, and defer statement, but with the opposite semantics.

Detailed design

ifn’t

ifnt-statement → `ifn’t` condition-list code-block else-clause?
else-clause → `else` code-block | `else` if-statement | `else` ifn’t-statement

An ifn’t statement executes the provided code block if the provided condition evaluates to false. There is no support for ifn’t let or ifn’t case statements.

guardn’t

guardnt-statement → `guardn’t` condition-list `else` code-block

A guardn’t statement executes the provided code block if the provided condition evaluates to true. There is no support for guardn’t let or ifn’t case statements. All the traditional rules of guard still apply.

whilen’t

whilent-statement → `whilen’t` condition-list code-block

A whilen’t statement executes the provided code block repeatedly until the provided condition evaluates to true. There is no support for whilen’t let or whilen’t case statements.

don’t

dont-statement → `don’t` code-block

A don’t statement does not execute the code in the provided code block.

defern’t

defernt-statement → `defern’t` code-block

A defern’t statement does not schedule the code in the provided code block to be run at the end of the current execution scope. These blocks, because they are not scheduled at all, do not execute in reverse order.

Source compatibility

These changes are purely additive, no source changes will be necessary to adopt them.

Effect on ABI stability

These statements do not meaningfully affect ABI surface.

Effect on API resilience

These statements do not affect the API of the declarations in which they are used.

Alternatives considered

There are many more statements whose semantics can be negated. They are left to future proposals, as I did not want to balloon this proposal.

ifn’t let, guardn’t let, whilen’t let

We could support parameter binding in negated statements. One possible implementation strategy is to only execute the block if the value could not be bound, and do not introduce the binding into the new scope.

ifn’t case, guardn’t case, whilen’t case

Similar to the above, we could support pattern matching. A possible strategy could be to match all patterns except the written one, and explicitly not bind any pattern bindings.

catchn’t

We could extend the existing catch clause to support catchn’t, which will only execute if the caught error does not match the provided pattern.

forn’t

It is unclear how this would work. Maybe this would execute if the sequence returned nil immediately, without any intermediate results.

return’t

This could be a no-op, or this could only work for boolean return values and negate their value.

47 Likes

If the goal is expressivity then I’d prefer the extra ‘o’ e.g. guardnot.

1 Like

The contraction is vitally important to this feature.

24 Likes

Seems to me you forgot wheren’t.

10 Likes

Could it make sense to redo the don't statement to read more like English?

don't do {
  print(128)
}
15 Likes

Could we add a bottom type Always to complement Never? I will tell the compiler to always execute this code-path.

We can use it inside a don't block to clearly emphasize our intentions.

11 Likes

I would want to name it Nevern’t

8 Likes

Well, I think we'll have to bikeshed this for at least a month


7 Likes

I disagree with the semantics of defern't: The negation of defer is to perform the given block immediately, rather than not to perform it at all.

Other than this minor quibble, huge +1. Adds very important expressivity to the language, and frankly I can't understand how we've somehow made it all the way to 5.0 without these necessary keywords.

16 Likes

The apostrophe looks very alien to Swift though. If the main idea is to "bury" negation (!) into a keyword, maybe a separate contextual not keyword that must strictly follow a statement keyword would be more natural and have less impact on the AST model and tooling.

Edit

OK, I admit I literally took this seriously :sweat_smile:

1 Like

I love the symmetry this proposal gives to the language though. I think we'll see a lot of this bleed into Foundation and hopefully even AppKit.

I always wanted an @IBInlet and and boy have there been times when I needed a viewDidn'tLoad() callback!

15 Likes

I've always appreciated the unless keyword in Ruby, and I like that this proposal is moving Swift in that direction but with even more clearly understandable keywords. Swift is an expressive language and English is an expressive language, so the product of those is quite beautiful.

I think we should take this opportunity to clean up some of the cruft in Swift, though. The keyword if is too short and quite easy to overlook in your code. The obvious answer here is to deprecate if and introduce unlessn't, which is quite clearly visible to the reader.

15 Likes

I see the shortcoming of isn’t In the proposal.

2 Likes

Compelling proposal. However, I think it's incomplete without funcn't, which would have a nil function pointer.

2 Likes

I tend to agree with @rex-remind. Swift’s philosophy isn’t terseness but clarity and expressivity. Except for don't which is arguably a term of art, I’d add the “o” to avoid confusing a novice user. ifn’t et al is too academic and we’re targeting the general developer public. But I’m bikeshedding at this point, I’m all +1 for this feature.

1 Like

Do these statements support whomst clauses?

18 Likes

Heh. U got me

I look forward to when NotificationCenter supports the addIgnorer method, which fires in a continuous loop except when the relevant notification is posted.

7 Likes

Great proposal, but the else syntax seems unnecessarily complex. It seems reasonable to extend this to the else clause such that instead of else ifn't it would be possible to use a much more succinct (and therefore significantly more clear) elsen't

3 Likes

We absolutely need the ability to cast with asn't which returns a type of Typen't.

let kermit = Muppet()
if let k = kermit asn't? Fraggle {
   // Now `k` is of type `Fragglen't`
   // Ă€hh
 đŸ€Ż
}

Not sure about the exact ergonomics yet though :thinking:

5 Likes