AnyHashable как альтернатива AnyEquatable - PullRequest
1 голос
/ 23 апреля 2020

Мне нужно сравнить массивы структур, которые соответствуют протоколу P.

P не может соответствовать Equatable, потому что у него не должно быть «Самостоятельных требований».

Создание AnyEquatable как стирание типа является обычной практикой для этой цели. Однако AnyHashable уже является частью стандартной библиотеки и соответствует Equatable.

Интересно, не является ли AnyEquatable частью стандартной библиотеки по уважительной причине. Должен ли стандарт AnyHashable использоваться вместо AnyEquatable?

1 Ответ

1 голос
/ 23 апреля 2020

AnyHashable заключает в себе множество общих функций. Это AnyEquatable нет; то, что он делает, может быть представлено только замыканием.

let cupcake = "?"
let notCake = 0xca_e

let cupcakeEquals: (Any) -> Bool = try cupcake.getEquals()
XCTAssert( cupcakeEquals(cupcake) )
XCTAssertFalse( cupcakeEquals(notCake) )

let notCakeEquals = try notCake.getEquals(Any.self)
XCTAssert( notCakeEquals(notCake) )
XCTAssertFalse( notCakeEquals(cupcake) )

XCTAssertThrowsError( try cupcake.getEquals(Int.self) )
public extension Equatable {
  /// A closure that equates another instance to this intance.
  /// - Parameters:
  ///   - _: Use the metatype for `Castable` to avoid explicit typing.
  /// - Throws: `CastError.impossible` if a `Castable` can't be cast to `Self`.
  func getEquals<Castable>(_: Castable.Type = Castable.self) throws -> (Castable) -> Bool {
    if let error = CastError(self, desired: Castable.self)
    { throw error }

    return { self == $0 as? Self }
  }
}
/// An error that represents casting gone wrong. ?‍♀️?
public enum CastError: Error {
  /// An undesired cast is possible.
  case possible

  /// An desired cast is not possible.
  case impossible
}

public extension CastError {
  /// `nil` if  an `Instance` can be cast to `Desired`. Otherwise, `.impossible`.
  init?<Instance, Desired>(_: Instance, desired _: Desired.Type) {
    self.init(Instance.self, desired: Desired.self)
  }

  /// `nil` if  a `Source` can be cast to `Desired`. Otherwise, `.impossible`.
  init?<Source, Desired>(_: Source.Type, desired _: Desired.Type) {
    if Source.self is Desired.Type
    { return nil }

    self = .impossible
  }

  /// `nil` if  an `Instance` cannot be cast to `Undesired`. Otherwise, `.possible`.
  init?<Instance, Undesired>(_: Instance, undesired _: Undesired.Type) {
    self.init(Instance.self, undesired: Undesired.self)
  }

  /// `nil` if  a `Source` cannot be cast to `Undesired`. Otherwise, `.possible`.
  init?<Source, Undesired>(_: Source.Type, undesired _: Undesired.Type) {
    guard Source.self is Undesired.Type
    else { return nil }

    self = .possible
  }
}

Вы можете обернуть это, чтобы соответствовать Equatable, с Cast, равным Any. Есть ли вариант использования?

/// A type-erased equatable value.
///
/// An `Equatable` instance is stored as a "`Cast`".
/// Only instances that can be cast to that type can be `==`'d with the `AnyEquatable`.
public struct AnyEquatable<Cast> {
  public init<Equatable: Swift.Equatable>(_ equatable: Equatable) throws {
    equals = try equatable.getEquals()
    cast = equatable as! Cast
  }

  private let equals: (Cast) -> Bool
  private let cast: Cast
}

extension AnyEquatable: Equatable {
  public static func == (equatable0: Self, equatable1: Self) -> Bool {
    equatable0 == equatable1.cast
  }
}

public extension AnyEquatable {
  static func == (equatable: Self, castable: Cast) -> Bool {
    equatable.equals(castable)
  }

  static func == (castable: Cast, equatable: Self) -> Bool {
    equatable.equals(castable)
  }
}
let anyEquatable = try AnyEquatable<Any>(cupcake)
XCTAssertEqual( anyEquatable, try .init(cupcake) )
XCTAssert(anyEquatable == cupcake)
XCTAssertFalse(notCake == anyEquatable)
...