Proposal sanity check: assigning a case statement to a boolean

(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. :smiley_cat: 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 )
  }
2 Likes