IMO, this feels like code golf. A switch is a perfectly idiomatic way to check multiple cases and is considerably more readable than the patterns that try to rig things up with operators to make it read more like English.
Sounds like there’s a missed optimization somewhere, might be worth filing a bug. I tried this test case with Swift 6.2:
public enum E {
case w
case x
case y
case z
}
public func testFunction(e: E) -> Bool {
return [.x, .y].contains(e)
}
If I compile it with -S -O, I get the following:
_$s1e12testFunctionAASbAA1EO_tF:
and w8, w0, #0xff
sub w8, w8, #1
cmp w8, #2
cset w0, lo
ret
If I change the contents of the array to include other subsets of the cases it gets optimized down in other clever ways.
Personally, I view getting it down to operator syntax a non goal but switching over instances that have associated value before you have an instance is an actual sticking point.
Writing a state machine is the most straightforward example I have.
Swift 6.2 on godbolt shows a more elaborate asm for me for that fragment. I could see the optimised version you are talking about on the nightly version on godbolt.
How is this optimisation done, is it specific to array and won't work with a dictionary or a set?
I would tweak isAny as follows:
enum E
{
case active
case pending
case inactive
}
extension Equatable
{
@inline( __always )
func isOne(of candidates: Self...) -> Bool
{
candidates.contains(self)
}
}
@main
class MainProgram
{
static func main()
{
let varTypeE :E = .active
if varTypeE.isOne(of: .active, .pending )
{
print( "It matched" )
}
}
}
Sorry I'm just replying now. Was on vacation and then getting back into the flow of work again. Some really great insights have been shared and I'm appreciative for all the responses.
I agree, this is probably the best out of box way to go. Meant to mention this in my post.
This is really nice and reads so clean and simple!
That's super clean. Also, I don't think I've personally used @inline before.
That's pretty dang close to my original want. That's awesome. I totally forgot about indirect enum. Thank you!
That makes sense. I think I meant that it feels heavy syntax-wise.
Your answer is super insightful. Thanks a ton! That does make a lot of sense. I'm not too imaginative right now, but I wonder what breaks (I imagine a lot) if || got a higher precedence to make that work.
Looking at Rust as another language with very similar enums and pattern matching, they resolve this kind of problem with a macro:
enum Foo {
Bar(i32),
Baz(String),
Quux,
}
fn f(arg: Foo) {
if matches!(arg, Foo::Bar(_) | Foo::Baz(_)) {
println!("hello");
}
}
The matches macro just gets converted into a match expression (similar to Swift’s switch statement).
Swift could probably provide a very similar macro for this use case.
EDIT:
Thinking more about this, the obvious blocker here is that while Rust macros take in raw syntax, meaning they can just take patterns like this, Swift macros cannot do that AFAIK. It would be nice if Swift had a way to define syntactical only macros (a system similar to Rust's macro_rules would be superb).
Is the benefit here that there isn’t another type or something else?
We don't need to open up arbitrary token macros—a macro that could accept a PatternSyntax would be sufficient to solve this.
It's possible today to get something pretty close to what Rust has, because a subset of interesting patterns are also valid expressions. For example, I whipped together a quick-and-dirty macro that expands this:
if #matches(subject, MyEnum.foo, MyEnum.bar(10)) { ... }
to this:
if ({
switch subject {
case MyEnum.foo, MyEnum.bar(10): true
default: false
}()) { ... }
But Rust's matchers also allow a guard equivalent to our where clauses, and you can't write something like this:
if #matches(subject, MyEnum.foo(let x), where: x % 2 == 0) { ... }
because MyEnum.foo(let x) isn't a valid expression.