The Challenge
Courtesy of Mike Ash, here’s the challenge. You have an enumeration:
enum Enum { case foo(Int) case bar(String) case qux(Int) }
And you have an array of them:
let items: [Enum] = [.foo(1), .bar("hi"), .foo(2)]
Return a filtered array containing only one case, for example foo
. The difficulty lies in that Swift does not seem to offer a ==
or ~=
operator that works on cases, ignoring associated values:
// does not work let filtered = items.filter({ $0 == .foo })
So what do you do?
Attempt 1
Here’s my first attempt. Super ugly but it gets the job done:
let filtered = items.filter({ switch $0 { case .foo: return true; default: return false } })
Evan Dekhayser prefers if-case:
let filtered = items.filter({ if case .foo = $0 { return true }; return false })
And you can of course use guard
as well:
let filteredy = items.filter({ guard case .foo = $0 else { return false }; return true })
Attempt 2
Just as ugly but slightly shorter in terms of number of characters. But it does more work than #1:
let filtered = items.filter({ for case .foo in [$0] { return true }; return false })
Again, yuck.
Attempt 3
I really hate this approach because you have to implement a separate property for each case. Double yuck:
extension Enum { var isFoo: Bool { switch self { case .foo: return true; default: return false } } } let filtered = items.filter({ $0.isFoo })
Attempt 4
This is gross because it requires a placeholder value for the rhs, even though that value is never used. And no, you can’t pass an underscore here:
extension Enum { static func ~= (lhs: Enum, rhs: Enum) -> Bool { let lhsCase = Array(Mirror(reflecting: lhs).children) let rhsCase = Array(Mirror(reflecting: rhs).children) return lhsCase[0].0 == rhsCase[0].0 } } let filtered = items.filter({ $0 ~= .foo(0) })
Attempt 5
Then I got the idea into my head that you could use reflection. If you don’t supply a value to an enumeration case with an associated value, it returns a function along the lines of (T) -> Enum
. Here is as far as I got before I realized the enumeration *name* was not preserved in its reflection:
import Foundation extension Enum { var caseName: String { return "\(Array(Mirror(reflecting: self).children)[0].0!)" } static func ~= <T>(lhs: Enum, rhs: (T) -> Enum) -> Bool { let lhsCase = lhs.caseName let prefixString = "Mirror for (\(T.self)) -> " let typeOffset = prefixString.characters.count let typeString = "\(Mirror(reflecting: rhs).description)" let rhsCase = typeString.substring(from: typeString.index(typeString.startIndex, offsetBy: typeOffset)) return true } }
Yeah. Really bad, plus it doesn’t work.
Call for solutions
Since I didn’t really get very far with this, I’m throwing this out there as an open challenge. Can you come up with a parsimonious, readable, and less horrible (I was going to say “more elegant”, but c’mon) way to approach this? I suspect my first attempt may be the best one, which would make me sad.