(Sorry; just saw this thread today.)
As for the original example, this already works:
let bool = .aCase ~= value
It only stops working when you add in associated values.
I've been using the following ~=
for a while, for those. It's pretty great. And definitely the right solution because of how switch
works, and the existing precedent for no associated values.
Although I think that we shouldn't have to rely on my solution (i.e. a reflection-less implementation of ~=
would be nice), I think all of the constituent pieces below should be in the standard library anyway (with better-enforced correctness, though this stuff is fine in practice).
func test_enum_NotEquatable() {
enum π§ {
case tuple(cat: String, hat: String)
case anotherTuple(cat: String, hat: String)
case labeled(cake: String)
case noAssociatedValue
}
let tupleCase = π§.tuple(cat: "π―", hat: "π§’")
XCTAssertTrue(π§.tuple ~= tupleCase)
XCTAssertFalse(π§.anotherTuple ~= tupleCase)
XCTAssertTrue(π§.noAssociatedValue ~= .noAssociatedValue)
XCTAssertTrue(π§.labeled ~= π§.labeled(cake: "π°"))
let makeTupleCase = π§.tuple
XCTAssertFalse(makeTupleCase ~= π§.noAssociatedValue)
switch tupleCase {
case π§.labeled: XCTFail()
case makeTupleCase: break
default: XCTFail()
}
}
func test_enum_Equatable() {
enum π§: Equatable {
case tuple(cat: String, hat: String)
case anotherTuple(cat: String, hat: String)
case labeled(cake: String)
case noAssociatedValue
}
let tupleCase = π§.tuple(cat: "π―", hat: "π§’")
XCTAssertTrue(π§.tuple ~= tupleCase)
XCTAssertFalse(π§.anotherTuple ~= tupleCase)
XCTAssertTrue(π§.labeled ~= π§.labeled(cake: "π°"))
let makeTupleCase = π§.tuple
XCTAssertFalse(makeTupleCase ~= π§.noAssociatedValue)
switch tupleCase {
case π§.labeled: XCTFail()
case makeTupleCase: break
default: XCTFail()
}
}
/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter case: Looks like `Enum.case`.
public func ~= <Enum: Equatable, AssociatedValue>(
case: (AssociatedValue) -> Enum,
instance: Enum
) -> Bool {
Mirror.associatedValue(of: instance, ifCase: `case`) != nil
}
/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter case: Looks like `Enum.case`.
public func ~= <Enum, AssociatedValue>(
case: (AssociatedValue) -> Enum,
instance: Enum
) -> Bool {
Mirror.associatedValue(of: instance, ifCase: `case`) != nil
}
/// Match non-`Equatable` `enum` cases without associated values.
public func ~= <Enum>(pattern: Enum, instance: Enum) -> Bool {
guard (
[pattern, instance].allSatisfy {
let mirror = Mirror(reflecting: $0)
return
mirror.displayStyle == .enum
&& mirror.children.isEmpty
}
) else { return false }
return .equate(pattern, to: instance) { "\($0)" }
}
public extension Mirror {
/// Get an `enum` case's `associatedValue`.
static func associatedValue<AssociatedValue>(
of subject: Any,
_: AssociatedValue.Type = AssociatedValue.self
) -> AssociatedValue? {
guard let childValue = Self(reflecting: subject).children.first?.value
else { return nil }
if let associatedValue = childValue as? AssociatedValue {
return associatedValue
}
let labeledAssociatedValue = Self(reflecting: childValue).children.first
return labeledAssociatedValue?.value as? AssociatedValue
}
/// Get an `enum` case's `associatedValue`.
/// - Parameter case: Looks like `Enum.case`.
static func associatedValue<Enum: Equatable, AssociatedValue>(
of instance: Enum,
ifCase case: (AssociatedValue) throws -> Enum
) rethrows -> AssociatedValue? {
try associatedValue(of: instance)
.filter { try `case`($0) == instance }
}
/// Get an `enum` case's `associatedValue`.
/// - Parameter case: Looks like `Enum.case`.
static func associatedValue<Enum, AssociatedValue>(
of instance: Enum,
ifCase case: (AssociatedValue) throws -> Enum
) rethrows -> AssociatedValue? {
try associatedValue(of: instance).filter {
.equate(try `case`($0), to: instance) {
Self(reflecting: $0).children.first?.label
}
}
}
}
public extension Optional {
/// Transform `.some` into `.none`, if a condition fails.
/// - Parameters:
/// - isSome: The condition that will result in `nil`, when evaluated to `false`.
func filter(_ isSome: (Wrapped) throws -> Bool) rethrows -> Self {
try flatMap { try isSome($0) ? $0 : nil }
}
}
public extension Equatable {
/// Equate two values using a closure.
static func equate<Wrapped, Equatable: Swift.Equatable>(
_ optional0: Wrapped?, to optional1: Wrapped?,
using transform: (Wrapped) throws -> Equatable
) rethrows -> Bool {
try optional0.map(transform) == optional1.map(transform)
}
}
public extension Sequence {
typealias Tuple2 = (Element, Element)
var tuple2: Tuple2? { makeTuple2()?.tuple }
private func makeTuple2() -> (
tuple: Tuple2,
getNext: () -> Element?
)? {
var iterator = makeIterator()
let getNext = { iterator.next() }
guard
let _0 = getNext(),
let _1 = getNext()
else { return nil }
return ( (_0, _1), getNext )
}