3

I need container for Any Equatable items in NOT Generic class (for example UI classes initial from storyboard). I need like this

var items: [Equatable]?

but it don't work, Equatable need Generic. the problem that there is no exist common Equatable class.


Ok - Go to generic! But if I do this

class Item<Value: Equatable>: Equatable {
   var value: Value
   init(_ value: Value) {
       self.value = value
   }
   //Equatable
   public static func ==(lhs: Item, rhs: Item) -> Bool {
       return (lhs.value == rhs.value)
   }
}

then I will be forced to specify the type in my nonGeneric-UI class. Like this

var items: [Item<WhatShouldBeHere?>]?

but again we come to the problem that there is no exist common Equatable class


Any solutions for container for All Equatable?

EvGeniy Ilyin
  • 1,817
  • 1
  • 21
  • 38
  • Related: [Operation on an array of structs implementing Equatable](https://stackoverflow.com/questions/41298464/operation-on-an-array-of-structs-implementing-equatable) – Hamish Sep 18 '17 at 23:02

1 Answers1

6

In lieu of existential types, you need to use a type eraser:

public struct AnyEquatable: Equatable {
    public let value: Any
    private let equals: (Any) -> Bool

    public init<E: Equatable>(_ value: E) {
        self.value = value
        self.equals = { ($0 as? E) == value }
    }

    public static func == (lhs: AnyEquatable, rhs: AnyEquatable) -> Bool {
        return lhs.equals(rhs.value) || rhs.equals(lhs.value)
    }
}

example usage:

let items = [
    AnyEquatable(1),
    AnyEquatable(1.23),
    AnyEquatable(true),
    AnyEquatable("some string")
]

let desired = "some string"
let desiredAsAnyEquatable = AnyEquatable(desired)
let desiredDescription = String(reflecting: desired)

for item in items {
    let itemDescription = String(reflecting: item.value)
    let isEqualToDesired = item == desiredAsAnyEquatable
    print("\(itemDescription) is \(isEqualToDesired ? "": "not ")equal to \(desiredDescription)")
}

Example output:

1 is not equal to "some string"

1.23 is not equal to "some string"

true is not equal to "some string"

"some string" is equal to "some string"

Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Note you should call `equals(_:)` for both `lhs` and `rhs` in order to preserve symmetry for when comparing a subclass with a superclass instance. – Hamish Sep 18 '17 at 23:04
  • @Hamish Good one, I hadn't considered that. so `lhs.equals(rhs.value) || rhs.equals(lhs.value)`? – Alexander Sep 19 '17 at 00:40
  • @Alexander Yup :) – Hamish Sep 19 '17 at 14:56
  • 1
    @Hamish Looking back on this, is that really necessary? A proper conformance to `Equatable` establishes an equivalence relationship, which implies symmetry. `a == b ≡ b == a` – Alexander Oct 07 '18 at 18:38
  • @Alexander It does, but casting isn't symmetrical – i.e for `class C {}; class D : C {}`, casting `C() as? D` fails, but `D() as? C` succeeds. See https://gist.github.com/hamishknight/80f2ef2dd328892439eaa20eb3478deb for an example of how that breaks `Equatable`'s requirement of symmetry. – Hamish Oct 07 '18 at 19:13
  • @Hamish Ahhh, I see what you mean – Alexander Oct 07 '18 at 19:49