Is there a way to write an `if case` statement as an expression?

Viewed 4099

Consider this code:

enum Type {
    case Foo(Int)
    case Bar(Int)

    var isBar: Bool {
        if case .Bar = self {
            return true
        } else {
            return false
        }
    }
}

That's gross. I would like to write something like this instead:

enum Type {
    case Foo(Int)
    case Bar(Int)

    var isBar: Bool {
        return case .Bar = self
    }
}

But such a construct does not seem to exist in Swift, or I cannot find it.

Since there's data associated with each case, I don't think it's possible to implement the ~= operator (or any other helper) in a way that's equivalent to the above expression. And in any case, if case statements exist for free for all enums, and don't need to be manually implemented.

Thus my question: is there any more concise/declarative/clean/idiomatic way to implement isBar than what I have above? Or, more directly, is there any way to express if case statements as Swift expressions?

4 Answers

So, there is a neater way, but requires a 3rd-party package: CasePaths

The idea is they work similarly to KeyPaths, and they come with a / operator to trigger it. There is also a ~= operator to check if a CasePath matches an instance.

So, you can achieve something like your original example like so:

import CasePaths

enum Type {
    case Foo(Int)
    case Bar(Int)

    var isBar: Bool {
        /Self.Bar ~= self
    }
}

You can also get the value:

extension Type {
    /// Returns the `Int` if this is a `Bar`, otherwise `nil`.
    var barValue: Int? {
        (/Self.Bar).extract(from: self)
    }
}

You can do several other useful things with CasePaths as well, such as extracting the Foo values in an array of Type values:

let values: [Type] = [.Foo(1), .Bar(2), .Foo(3), .Foo(4), .Bar(5)]
let foos = values.compactMap(/Type.Foo) // [1, 3, 4]
let bars = values.compactMap(/Type.Bar) // [2, 5]

I'm sure there is somewhat of a performance cost, but it may not be an issue in your context.

I have a similar wondering, and I kept searching for some work arounds about this, and landed on this page. I came up with code like this to compromise.

fileprivate enum TypePrimitive {
    case foo
    case bar
}

enum Type {
    case foo(Int)
    case bar(Int)
    fileprivate var primitiveType: TypePrimitive {
        switch self {
        case .foo(_): return .foo
        case .bar(_): return .bar
        }
    }
    var isFoo: Bool { self.primitiveType == .foo }
    var isBar: Bool { self.primitiveType == .bar }
}

I hope Apple will provide better solution by adding some features in Swift language.

Related