I agree. In many natural languages, like English, relational operators distribute over boolean operators: the sentence "the user status is active or pending" expands to "(the user status is active) or (the user status is pending)". It would be nice if more programming languages worked like that.
In the meantime, you can get close with custom operators:[1]
infix operator =~: ComparisonPrecedence
extension Equatable {
static func | (lhs: Self, rhs: Self) -> [Self] {
return [lhs, rhs]
}
static func | (lhs: [Self], rhs: Self) -> [Self] {
return lhs + [rhs]
}
static func =~(lhs: Self, rhs: [Self]) -> Bool {
return rhs.contains(lhs)
}
}
if userStatus =~ .active | .pending {
// Do something
}
(Performance)
From some experimentation, it looks like we can get the compiler to generate more efficient code by using array literals instead of the | operator, using a manual reimplementation of Array.contains, and adding the @inline(always) attribute to the implementation of =~. The @inline(always) attribute was introduced by SE-0496, but isn't released yet, so for now we have to use the compiler-private spelling, @inline(__always).
infix operator =~: ComparisonPrecedence
extension Equatable {
@inline(__always)
static func =~(lhs: Self, rhs: [Self]) -> Bool {
for i in rhs {
if lhs == i {
return true
}
}
return false
}
}
enum UserStatus {
case active, pending, somethingElse
}
func f(userStatus: UserStatus) -> Bool {
return userStatus =~ [.active, .pending]
}
The compiler (aarch64 swiftc 6.2) with -O optimizes f to the following: (Compiler Explorer)
output.f(userStatus: output.UserStatus) -> Swift.Bool:
and w8, w0, #0xff
cmp w8, #2
cset w0, lo
ret
The optimization also happens with userStatus =~ .active | .pending, using the same | implementation (Compiler Explorer). But the optimization breaks with more than two operands to the | operator, like userStatus =~ .active | .pending | .somethingElse, even with various optimizations to the | implementation (Compiler Explorer, Compiler Explorer). The optimization still works with more than two operands to an array literal, like userStatus =~ [.active, .pending, .somethingElse] (Compiler Explorer).
It's also possible to use InlineArray instead of Array, which results in the same optimization even without @inline(always) (Compiler Explorer). But that makes it impossible to implement the | operator for more than two operands, because it's impossible to use arithmetic for the length of an InlineArray, like [n+1 of Int].
A problem with the || operator specifically is that it has lower precedence than ==. The compiler does parsing before type checking, so when it parses userStatus == .active || .pending, it can't distinguish .pending from a boolean value like list.isEmpty.
The most recent information I'm aware of is that a few years ago, the Language Steering Group decided they would hold on small pattern matching improvements until there's a more cohesive vision for the future of pattern matching in Swift. [Pitch] is case expressions - #69 by John_McCall
I chose
=~instead of the existing~=operator (which can customize switch statements) because with~=, the pattern is supposed to go on the left instead of the right, like.active | .pending ~= userStatus. ↩︎