Merits of the "unless" keyword

Time to dust off the old April Fools pitch and implementation, @harlanhaskins !

11 Likes

The funny thing about unless is that you can't do conditional bindings with it... or if you allow it, it'd apply to the else part instead of the block of code that immediately follows:

let a: Int? = getAnOptional()
unless let b = a {
   // b is not in scope
} else {
   // b is in scope
   print(b)
}

This gets interesting if you chain multiple unless one after another:

let a: Int? = getAnOptional()
unless let b = a {
   // b is not in scope
} else unless let c = a {
   // b is in scope
   // c is not in scope
   print(b)
} else {
   // b and c are in scope
   print(b, c)
}

Is that behavior strange? I'm actually pondering whether this would be useful or not. It really feels like I should use guard here.

I suppose we could say conditional bindings are not available with unless if that's too strange, and unless case (similar to if case) would have to get the same treatment.

8 Likes

I would love to use this in Swift, having enjoyed Ruby’s options for control flow in the past where I took quite some pleasure in choosing the right way of expressing a bunch of conditions beginning a method, and where I think the resulting readability was good.

I’ve always had trouble parsing anything more than the simplest conditional logic. In Swift, that’s lead me towards liberal use of computed properties that wrap up different conditions, so that aside from any bindings, my ifs are largely just a few comma-separated values.


While at first I thought this would work great with unless, I found after sketching a few examples that the comma syntax is less intuitive to me than it is for with if. I read it as if it’s a logical or, but of course it isn’t it’s “not both”, as stated by @Quedlinbug. I think I would end up not using the comma syntax and using more ||.

unless isSystemActive || isAutoRestartDisabled {
    logger.info("Restarting...")
    restart()
}
1 Like

Swift already has so many keywords. Not a big fan of adding more keywords.

4 Likes

I often hear this argument but find it difficult to understand. Could you clarify your reasoning? From my perspective, Swift achieves clarity and expressiveness through its keywords. The language intentionally avoids reusing some of them (e.g., consume/consuming) to enhance (human!) readability. Since removing keywords is unlikely, why limit their addition now? Adding more keywords could enhance the language's expressiveness for specific, justified use-cases.

1 Like

And funcn't for abstract methods please.

1 Like

But everyone has the same thoughts. after adding this keyword, someone would say, let's add another new keyword to solve another issue. It is a vicious circle. There is no prefect language. Why do we need to make it more complicated? in an interview, Chris Lattner mentioned, swift has too much sugar. and sugar makes people high. Because everyone has tried to add things. I think it is good to say stop and enough. I don't want to see swift becomes a kitchen sink language. as complex as C++.

2 Likes

Given that we already have both the && operator and the comma in condition clauses, I'd say there's already some precedent for a "redundant" version of a Boolean operator that specifically applies to condition clauses. I think a not keyword in a similar vein may be a good idea.

-1 from me.

'unless' has more characters than 'if' and '!' combined ; the lexical analyser has to spend more time to recognise that it is a valid token. This will have consequences: slower compilation , increase in energy consumption. :slight_smile:

Plus, not to mention the fact that 'unless' is a negative word, which may have some cognitive implications, especially for nonnative speakers.

1 Like

If it were up to me, I would only consider adding unless if it made the control flow of some conditional binding easier to reason about. Both if let and guard let demonstrate reasonably scoped bindings. I don't think this is a happy coincidence, but essentially why if and guard are great control flow concepts. Swift understood this when it introduced guard as the control flow complement to if. I would be disappointed if Swift reserved a keyword for nothing more than *checks notes* bitwise negation of a Bool.

4 Likes

I don't feel it necessary to add unless while in semantic it is like if not which is a common and well-known concept (personally I see no problem to use if !foo). By introducing another option to do the same thing which is unpopular in general, it leads to confusion in my opinion.

The argument that more keywords makes the language harder flies in the face of all evidence. The fact that words like "unless" exist [in English] and are widely used demonstrates that they are not too complicated. More broadly, [spoken] languages naturally evolve a huge number of words in general, including such conditional conjunctions, to allow for the most elegant, understandable, and precise expression of intent. Spoken & written languages are often more confusing if they lack the vocabulary (or grammar) to express things well.

Jokers aside, no-one here is proposing words or grammar that are uncommon. Any five-year-old has a vocabulary far beyond what Swift currently has or conceivably ever will have, even if pitches like this were always wholeheartedly accepted.

Consider also that the bigger challenge in Swift is understanding the logic of conditions themselves, where one of the biggest pitfalls for beginners (and arguably old hands alike) is correctly parsing things like if !(!a || (b && !c)) { … }. Anything which makes it easier to understand conditional logic is a very good thing, even if it means some extra words.

3 Likes

What about the fact that learning new natural languages (especially vocabulary) takes years for almost all people? The fact that languages has a variety of words to express (sometimes the same thing) cannot justify adding them to programming language (or any language).

If any, this makes programming languages harder to learn, leading to artificial limitations of what you use and what tend avoid.

2 Likes

You never need to write an expression like this. You can always write it as nested conditions or with temporary variables.

let stepOne = !c
let stepTwo = b && stepOne
let stepThree = !a
let stepFour = stepThree || stepTwo
if (!stepFour) {
  // do the thing
}

IMO the only value of unless would be to simplify changes to conditionals. People tend to make mistakes applying De Morgan’s laws when inverting conditions. Changing a single keyword to unless might introduce fewer bugs.

1 Like

Only as personal experience I find unless to be one of those things that sounds nice in practice but then it makes your brain stop for a moment to process new code you are reading and that’s always a bad sign to me. And then you have folks doing further code changes on it sometimes getting into double negations which are fun to debug. I’ve seen enough programmers get stuck in it to not like it at all.
Instead I tend to recommend to just write == false. Way more clear, readable and works everywhere ^^
A similar argument can be made of guard, except that it brings benefits that make it worth while. Forcing the return is huge for removing cognitive load for the rest of the function.
But in the end in just question of taste :joy: i just don’t think I would like to see it in Swift code.

3 Likes

Elegantly put and I agree completely.

If you're going to add a helper function for this it may as well be a property on Bool. That way it chains naturally.

extension Bool {
  // or negated if we think postfix not doesn't match our naming conventions.
  var not: Bool { !self }
}

I respectfully disagree, maybe because English is the second language I learned. 'Unless' is a negative word, but 'if' is not.

Which one puts less burden on the cognitive engine (1) or (2) ?

(1) Unless you don't like red hot chillies, you should eat them everyday.
    You should eat them everyday unless you don't like red hot chillies.

(2) If you like red hot chillies, you should eat them everyday.
    You should eat them everyday if you like red hot chillies.

3 Likes

Don't forget the 'otherwise' to complement 'unless'.

var isOn = false

unless (isOn) {
    print ("off")
}

unless (!isOn) {
    print ("on")
}
otherwise: {
    print ("off")
}

Here is a temporary workaround.

func unless (_ u:Bool, _ f: () -> Void) {
    if (!u) {
        f ()
    }
}

func unless (_ u:Bool, _ f: () -> Void, otherwise g: () -> Void) {
    if (!u) {
        f ()
    }
    else  {
        g ()
    }
}

Keep in mind that unless behaves the same as guard when it comes to bindings. e.g.:

unless let message { print(message) }

…is invalid, just like this is:

guard let message else { print(message) }

The difference is simply that unless doesn't require you to exit the enclosing scope.


Note that the most common use of a binding is to unwrap an Optional (I assume), and I'm not sure unless helps with optionals. Optional chaining seems like a better mechanism, at least in all the cases I can think of off the top of my head.

Typically you either want to abort if a value is nil - in which case you just use guard - or you want to run additional code to get a [non-nil] value some other way. e.g.:

func getCachedToken() -> Token? { … }
func createToken() -> Token { … }

…

let token = getCachedToken() ?? createToken()

You could write it this way:

var maybeToken = getCachedToken()
let token: Token

if let tmp = maybeToken {
    token = tmp
} else {
    token = createToken()
}

(although I'm not sure why you would / should)

…but it's even worse with unless:

var maybeToken = getCachedToken()

unless nil != maybeToken {
    maybeToken = createToken()
}

let token = maybeToken!

It seems to me that it's more useful in non-binding situations, e.g.:

unless loggedIn {
    try await logIn()
}

…though I'm still not sure when that would be clearer than:

if not loggedIn {
    try await logIn()
}
2 Likes