Elvis Operator?

I am wondering what people think of the idea of an actual elvis operator?

Right now we have a null coalescing operator which is nice for optionals. This however does not work with non-optional Bools. The reason I think this would be handy is that you could set a value of a Bool using functions that return Bool but allowing a backup if the first fails while not having to worry about side-effects.

Example:

let value = Func1() ? Func1() : Func2() // Potential side-effects and Func1 is called twice. (What if Func1 incremented a value somewhere).

This can be fixed with (of course)

let func1Result = Func1()
let value = func1Result ? func1Result : Func2()

But this kinda defeats the purpose of a ternary operator IMO and the code below is a lot nicer.

let value = Func1() ?: Func2()

Now I did see this thread. Replace ternary _ ? _ : _ operator with a binary _ ? _ operator

It seems they want to remove the ternary operator completely because it uses a "?" it seems and that there is a replacement using an operator overload. The conversation there also got to adding an operator that seems to return an optional if the value is false. I think that is a slightly different issue and I would love to keep the current null coalescing and the ternary operators as is.

1 Like

Does this come up a lot? It sounds equivalent to short-circuiting logical OR to me, so I'm having trouble understanding why it is worth creating a new operator.

3 Likes

Indeed, that's the || operator. Making ?: work the same way will take you 3 lines of code.

In Swift ?: is ??.

1 Like

The Elvis operator has to be one of the following cases:

// In a typesafe language, ?: must construct the same type left and right T ?: T and not T ?: U
// The only way to have a test is Boolean:
Bool ? Bool : Bool

// which then is shortened to
Bool ?: Bool

If you use it like many people do in C-like languages, you can test against "0" instead of testing for a bool:

// How to construct it in Swift
intValue != 0 ? intValue : fallbackValue

// What an Elvis would look like
intValue ?: fallbackValue

This is, again, of very limited use.

Finally, you can test it against a false-like condition, for example, an optional:

value != nil ? value! : fallbackValue

// which is 
value ?: fallbackValue

// exactly the same as
value ?? fallbackValue

The only other situation I can see is extending ?? and if let to other constructs with bias case loads

SomeEnum.caseName(value) ?? fallbackValue

But I wouldn't go Elvis on these. I'd rather use @stephencelis 's .isCaseName?.value (however that ends up being spelled.)

4 Likes

Yes. More generally, many other languages have a concept of "truthiness" such that 0 or NULL or an empty string can all qualify as false values for Boolean purposes. But since Swift allows only real Booleans in Boolean contexts, the utility of an Elvis operator seems much reduced.

1 Like

It is easy enough to add Collection and hence String Elvis, this might be worth adding to the standard library since it is a common pattern in other languages:

extension Collection {
    static func ??(lhs: Self, rhs: @autoclosure () throws -> Self) rethrows -> Self {
        guard lhs.count != 0 else {
            return try rhs()
        }
        return lhs
    }
}

let s = ""
print(s ?? "Default") // Default

You could write something similar for Integers, but I would be against added the int version because it is rarely used in GCC and because it is very un-Swift like since testing against zero instead of a Bool is weird in Swift.

You could write something similar for Integers, but I would be against added the int version because it is rarely used in GCC and because it is very un-Swift like since testing against zero instead of a Bool is weird in Swift.

I feel the same way about your extension to Collection here : if it's 'un-Swift like' to test an integer against zero instead of using a Bool, why would testing a Collection's count against zero would be better ?

4 Likes

Reasons:

  1. If you view an Optional as a collection of up to 1 element it makes sense.

  2. It is a common idium in other languages that use reference semantics to return nil for empty collections/strings and therefore code like this is common practice.

  3. It is useful to signal an error with an empty collection and have an easy way to provide a default.

Optional formally isn't a collection and I don't see a way in which it resembles one.

Yes, but Swift, being a high-level language, doesn't use such semantics. An empty array in C is a NULL pointer. In Swift, an empty array is a full-fledged instance.

The nil-coalescing operator is unique and used only for optionals, and in my opinion it should be left as is.

I don't see the perspectives of introducing a new operator for that purpose - the core team isn't tolerant towards new operators of that sort, and neither am I. But you are free to try, of course.
You can always use the 'true-boolean' _?_:_ which is even more useful and isn't that bad of an alternative. It's just a bit longer.

Better use isEmpty, count can lead to writing inefficient code when using the operator.
Could be written in just a single line: return lhs.isEmpty ? try rhs() : lhs

What I sometimes miss is being able to write:

let a : A = maybeA() ?? throw SomeError()

But, unfortunately, throw X is not an expression (of type Never).

An extra pair of braces and parentheses (and a try) makes it work:

let a = try maybeA() ?? { throw SomeError() }()
2 Likes

Please start a new thread discussing the specific issue you raise here: I see no obvious reason we couldn't change 'throw' to be an expression returning Never.

That said, making that change wouldn't fix your code above, because ?? requires the left and right side to be the same base types, and Never is not an A.

2 Likes

…but “??” uses an autoclosure for its right-hand side, and the code works if you manually wrap the expression in a closure, so I’m not sure why it doesn’t work as-is:

let x = Optional(0)

let y = x ?? fatalError()
// error: cannot convert value of type 'Never' to expected argument type 'Int'

let z = x ?? { fatalError() }()
// no error
2 Likes

Done.