【问题标题】:Compare Swift Enum types ignoring associated values - generic implementation比较忽略关联值的 Swift Enum 类型 - 通用实现
【发布时间】:2017-12-01 16:26:28
【问题描述】:

我的项目中有几个不同的枚举,它们符合相同的协议。协议中的compareEnumType 方法比较忽略关联值的枚举案例。这是我在操场上的代码:

protocol EquatableEnumType {
    static func compareEnumType(lhs: Self, rhs: Self) -> Bool
}

enum MyEnum: EquatableEnumType {
    case A(Int)
    case B

    static func compareEnumType(lhs: MyEnum, rhs: MyEnum) -> Bool {
        switch (lhs, rhs) {
        case (.A, .A): return true
        case (.B, .B): return true
        default: return false
        }
    }
}

enum MyEnum2: EquatableEnumType {
    case X(String)
    case Y

    static func compareEnumType(lhs: MyEnum2, rhs: MyEnum2) -> Bool {
        switch (lhs, rhs) {
        case (.X, .X): return true
        case (.Y, .Y): return true
        default: return false
        }
    }
}

let a = MyEnum.A(5)
let a1 = MyEnum.A(3)
if MyEnum.compareEnumType(lhs: a, rhs: a1) {
    print("equal") // -> true, prints "equal"
}

let x = MyEnum2.X("table")
let x1 = MyEnum2.X("chair")
if MyEnum2.compareEnumType(lhs: x, rhs: x1) {
    print("equal2") // -> true, prints "equal2"
}

在我的实际项目中,我有超过 2 个枚举,对于每个枚举,我都必须有类似的 compareEnumType 函数实现。

问题是:是否有可能有一个通用的compareEnumType 实现,它适用于所有符合EquatableEnumType 协议的枚举?

我尝试在协议扩展中编写一个默认实现,如下所示:

extension EquatableEnumType {
    static func compareEnumType(lhs: Self, rhs: Self) -> Bool {
        // how to implement???
    }
}

但我坚持执行。我看不到访问lhsrhs 中包含的值的方法。谁能帮帮我?

【问题讨论】:

  • 这违反了Equatable 的含义,它“暗示了可替换性——任何两个相等比较的实例都可以在取决于它们的值的任何代码中互换使用。为了保持可替换性,== 运算符应该考虑到 Equatable 类型的所有可见方面。不鼓励公开 Equatable 类型的非值方面而不是类标识,任何公开的方面都应在文档中明确指出。这是一个非常糟糕的主意,您应该重新设计您的类型以避免它。如果你决心这样做,你需要的工具就是 SwiftGen。
  • 如果您的枚举携带与相等性无关的私有信息(例如缓存),那么您几乎可以肯定在这里使用类或结构。枚举不太适合该问题。 (任何基于枚举的解决方案都可以转换为基于协议+结构的等价物,反之亦然。维护权衡会有所不同,但逻辑始终可以实现,因为它们是对偶的。)
  • @RobNapier 我不想改变 == 的含义或实现。我知道它考虑了关联的值,这是正确的。在我的协议中,我只想比较枚举案例,而忽略关联的值。我尝试使用 == 但它对我不起作用(这是正确的)。我正在寻找另一种解决方案来比较如何以通用方式忽略关联值的枚举案例。
  • 当然。资源。 github.com/krzysztofzablocki/Sourcery(抱歉之前说的是 SwiftGen;这是 Sourcery 所基于的低级库,但为此您需要 Sourcery)简短而长的答案是您需要 Swift 中不存在的元编程级别,这就是 Sourcery 目前填补空白的地方。有一天,Swift 中会出现更多元编程,因此不需要 Sourcery,但今天不是那一天。
  • (根据您的编辑,这没有任何问题,当然也有道理。只有将它与 Equatable 联系起来才是问题。)

标签: swift enums swift-protocols


【解决方案1】:

简单!我会使用实例方法,但如果你真的需要它是静态的,你可以将它重写为类函数。

extension EquatableEnumCase {
    func isSameCase(as other: Self) -> Bool {
        let mirrorSelf = Mirror(reflecting: self)
        let mirrorOther = Mirror(reflecting: other)
        if let caseSelf = mirrorSelf.children.first?.label, let caseOther = mirrorOther.children.first?.label {
            return (caseSelf == caseOther) //Avoid nil comparation, because (nil == nil) returns true
        } else { return false}
    }
} 


enum MyEnum1: EquatableEnumCase {
    case A(Int)
    case B
}

enum MyEnum2: EquatableEnumCase {
    case X(String)
    case Y
}

let a = MyEnum1.A(5)
let a1 = MyEnum1.A(3)
if a.isSameCase(as: a1) {
    print("equal") // -> true, prints "equal1"
}

let x = MyEnum2.X("table")
let x1 = MyEnum2.X("chair")

if x.isSameCase(as: x1) {
    print("equal2") // -> true, prints "equal2"
}

let y = MyEnum2.Y
print(x.isSameCase(as: y) ? "equal3": "not equal3") // -> false, "not equal3"

【讨论】:

    【解决方案2】:

    我不认为您可以自动生成它,所以这是一种使用扩展的方法。我建议创建一个新的enum CompareEnumMethod,它告诉您是要比较关联的values 还是只比较type。在您的协议中创建一个新函数compareEnum,并将此枚举作为参数。然后,您可以为==(lhs:,rhs:) 创建一个扩展,并说它使用.value,而对于compareEnumType,您使用.type。现在只需在每个枚举中实现 compareEnum 方法。

    enum CompareEnumMethod {
        case type, value
    }
    
    protocol EquatableEnumType: Equatable {
        static func compareEnumType(lhs: Self, rhs: Self) -> Bool
        static func compareEnum(lhs: Self, rhs: Self, method: CompareEnumMethod) -> Bool
    }
    
    extension EquatableEnumType {
        static func compareEnumType(lhs: Self, rhs: Self) -> Bool {
            return Self.compareEnum(lhs: lhs, rhs: rhs, method: .type)
        }
    
        static func ==(lhs: Self, rhs: Self) -> Bool {
            return Self.compareEnum(lhs: lhs, rhs: rhs, method: .value)
        }
    }
    
    enum MyEnum: EquatableEnumType {
        case A(Int)
        case B
    
        static func compareEnum(lhs: MyEnum, rhs: MyEnum, method: CompareEnumMethod) -> Bool {
            switch (lhs, rhs, method) {
            case let (.A(lhsA), .A(rhsA), .value):
                return lhsA == rhsA
            case (.A, .A, .type),
                 (.B, .B, _):
                return true
            default:
                return false
            }
        }
    }
    
    let a0 = MyEnum.A(5)
    let a1 = MyEnum.A(3)
    let b0 = MyEnum.B
    print(MyEnum.compareEnumType(lhs: a0, rhs: a1)) //true
    print(a0 == a1) //false
    print(MyEnum.compareEnumType(lhs: a0, rhs: b0)) //false
    

    【讨论】:

    • 感谢您的回答!但是这种方式 compareEnum() 应该对每个枚举有不同的实现(例如,对于我的代码 sn-p 中的 MyEnum2)。这就是我试图避免的。我想为所有符合 EquatableEnumType 的枚举提供一个单一的实现。
    猜你喜欢
    • 1970-01-01
    • 2015-10-11
    • 1970-01-01
    • 1970-01-01
    • 2021-04-30
    • 1970-01-01
    • 2021-02-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多