-1

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!

ev0
  • 179
  • 2
  • 7
  • To be able to use the `==` or `!=` operator the type must conform to `Equatable`, there is no workaround. – vadian Dec 14 '22 at 19:08
  • You don't have the "Equatable conformance over the whole class", if you did, it would look like this: `class Enhanced: Codable, Equatable` But you have the Equatable conformance of `T`, which is absolutely good way of doing it. And it's better to leave it that way. If any class does not conform to `Equatable`, it's easy to make it conform. In worst case your equatable can return `false` unless they are the same object: `static func == (lhs: ..., rhs: ...) -> Bool { return lhs === rhs }` which would be the equivalent of your `default: changed = true` – timbre timbre Dec 14 '22 at 19:09
  • Thanks akjndklskver, I corrected the question to clarify what actually has the `Equatable` conformance. I believe the `default:` case will be fine, but it's the `changed = newEquatableValue != oldEquatableValue` line that is returning the error. – ev0 Dec 15 '22 at 15:15

1 Answers1

0

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!

ev0
  • 179
  • 2
  • 7