A way to compare generics in Swift only if they conform to Equatable

437 views Asked by At

Wondering if there's a good way to do this or not:

I have a @propertyWrapper named "Enhanced" that I use. I use the wrappedValue.set to do some actions, and I would also like to do some further actions if the property is Equatable.

Currently, the code looks like this:

@propertyWrapper
class Enhanced<T: Equatable>: Codable
{
    private var value: T
    var projectedValue: Enhanced<T> { self }

    var wrappedValue: T
    {
        get { value }
        set { set(newValue, notify: nil) }
    }
    
    func set(_ proposedValue: T, notify: Bool?)
    {
        let oldValue = value
        let newValue = proposedValue
        let changed = newValue != oldValue

        if changed { /* more magic here */ }

        value = newValue
    }
}

Now I would like to remove the Equatable conformance over the generic T, but still be able to compare the old and new values IF the generic T conforms to Equatable.

I've tried a handful of techniques, all of which dead end somewhere. My latest was this:

        let changed: Bool
        switch T.self
        {
        case let equatableType as any Equatable.Type:
            if
                let oldEquatableValue = oldValue as? any Equatable,
                let newEquatableValue = newValue as? any Equatable
            {
                changed = newEquatableValue != oldEquatableValue
            }
        default:
            changed = true
        }

...but the error is an understandable Binary operator '!=' cannot be applied to two 'any Equatable' operands.

I tried different patterns to cast the generic type T into an Equatable and silently fail if the generic does not conform, but even if they do, the resulting "cast" types I get back aren't equatable themselves.

Any revelations to the proper pattern would be great!

1

There are 1 answers

0
ev0 On BEST ANSWER

After some deep web-sleuthing, I came across a snippet of code that does the magic I need:

private extension Equatable
{
    func isEqualTo(_ rhs: Any) -> Bool
    {
        if let castRHS = rhs as? Self
        {
            return self == castRHS
        }
        else
        {
            return false
        }
    }
}

(HT to neonichu/equalizer.swift on GitHub)

With this bit of pseudo type-erasure, I can make this work:

let changed: Bool
if let oldEquatableValue = oldValue as? any Equatable,
   let newEquatableValue = newValue as? any Equatable
{
    changed = oldEquatableValue.isEqualTo(newEquatableValue) == false
}
else
{
    changed = true
}

By using an extension on Equatable that does further casting of the values, this allows for these two values to be compared (and fail if they are not the same).

Hope this helps someone else!