在符合协议的对象数组中使用 diff

Using diff in an array of objects that conform to a protocol

我正在尝试使用组合而不是继承,我想对符合给定协议的对象数组使用 diff

为此,我实施了一个协议并使其符合 Equatable

// Playground - noun: a place where people can play
import XCPlayground
import Foundation

protocol Field:Equatable {
    var content: String { get }
}

func ==<T: Field>(lhs: T, rhs: T) -> Bool {
    return lhs.content == rhs.content
}

func ==<T: Field, U: Field>(lhs: T, rhs: U) -> Bool {
    return lhs.content == rhs.content
}

struct First:Field {
    let content:String
}

struct Second:Field {
    let content:String
}

let items:[Field] = [First(content: "abc"), Second(content: "cxz")] //  boom

但我很快发现:

error: protocol 'Field' can only be used as a generic constraint because it has Self or associated type requirements

我明白为什么 Swift 是一种类型安全的语言,需要能够随时知道这些对象的具体类型。

修修补补后,我最终从协议中删除了 Equatable 并重载了 == 运算符:

// Playground - noun: a place where people can play
import XCPlayground
import Foundation

protocol Field {
    var content: String { get }
}

func ==(lhs: Field, rhs: Field) -> Bool {
    return lhs.content == rhs.content
}

func ==(lhs: [Field], rhs: [Field]) -> Bool {
    return (lhs.count == rhs.count) && (zip(lhs, rhs).map(==).reduce(true, { [=11=] &&  })) // naive, but let's go with it for the sake of the argument
}

struct First:Field {
    let content:String
}

struct Second:Field {
    let content:String
}


// Requirement #1: direct object comparison
print(First(content: "abc") == First(content: "abc")) // true
print(First(content: "abc") == Second(content: "abc")) // false

// Requirement #2: being able to diff an array of objects complying with the Field protocol
let array1:[Field] = [First(content: "abc"), Second(content: "abc")]
let array2:[Field] = [Second(content: "abc")]

print(array1 == array2) // false
let outcome = array1.diff(array2) //  boom

error: value of type '[Field]' has no member 'diff'

从这里开始,老实说我有点迷茫了。我读了一些关于类型擦除的很棒的帖子,但即使是提供的示例也遇到了同样的问题(我认为是不符合 Equatable)。

我说得对吗?如果是这样,如何做到这一点?

更新: 我不得不暂时停止这个实验,完全忘记了依赖,抱歉! DiffSwiftLCS提供的一种方法,是最长公共子序列(LCS)算法的一种实现。

TL;DR: Field 协议需要遵守 Equatable 但到目前为止我还不能做到这一点。我需要能够创建一个符合此协议的对象数组(请参阅第一个代码块中的错误)。

再次感谢

我知道这可能是你现在想要的,但我知道如何让它工作的唯一方法是引入额外的包装器 class:

struct FieldEquatableWrapper: Equatable {
    let wrapped: Field

    public static func ==(lhs: FieldEquatableWrapper, rhs: FieldEquatableWrapper) -> Bool {
        return lhs.wrapped.content == rhs.wrapped.content
    }

    public static func diff(_ coll: [Field], _ otherCollection: [Field]) -> Diff<Int> {
        let w1 = coll.map({ FieldEquatableWrapper(wrapped: [=10=]) })
        let w2 = otherCollection.map({ FieldEquatableWrapper(wrapped: [=10=]) })
        return w1.diff(w2)
    }
}

然后你可以做

    let outcome = FieldEquatableWrapper.diff(array1, array2)

我认为你根本无法使 Field 符合 Equatable,因为它被设计为 "type-safe" 使用 Self 伪 class.这是包装器 class 的原因之一。不幸的是,似乎还有一个我不知道如何解决的问题:我无法将此 "wrapped" diff 放入 CollectionArray 扩展中,但仍然可以它支持异构 [Field] 数组,没有编译错误:

using 'Field' as a concrete type conforming to protocol 'Field' is not supported

如果有人知道更好的解决方案,我也很感兴趣。

P.S.

在你提到的问题中

print(First(content: "abc") == Second(content: "abc")) // false

但鉴于您定义 == 运算符

的方式,我希望它是 true

问题来自 Equatable 协议的含义和 Swift 对类型重载函数的支持。

我们来看看Equatable协议:

protocol Equatable
{
    static func ==(Self, Self) -> Bool
}

这是什么意思?好吧,重要的是要了解“equatable”在 Swift 的上下文中的实际含义。 “Equatable”是结构或 class 的一个特征,它使得该结构或 class 的任何 实例 可以与任何其他 class 比较是否相等 该结构的实例或class。它没有说明将它与不同 class 或结构的实例进行相等性比较。

想一想。 IntString 都是 Equatable 的类型。 13 == 13"meredith" == "meredith"。但是13 == "meredith"吗?

Equatable 协议只关心何时要比较的两个事物属于同一类型。它没有说明当两个事物属于不同类型时会发生什么。这就是为什么 ==(::) 定义中的两个参数都是 Self.

类型的原因

让我们看看您的示例中发生了什么。

protocol Field:Equatable 
{
    var content:String { get }
}

func ==<T:Field>(lhs:T, rhs:T) -> Bool 
{
    return lhs.content == rhs.content
}

func ==<T:Field, U:Field>(lhs:T, rhs:U) -> Bool 
{
    return lhs.content == rhs.content
}

您为 == 运算符提供了两个重载。但只有第一个与 Equatable 一致性有关。第二个重载是在您执行

时应用的重载
First(content: "abc") == Second(content: "abc")

这与 Equatable 协议无关。

这是一个混淆点。 相同 类型的实例之间的公平性比 不同 类型的实例之间的公平性 更低 要求,当我们' 谈论您想要测试是否相等的类型的单独绑定实例。 (因为我们可以假设两个被测试的东西属于同一类型。)

但是,当我们制作一个符合Equatable的东西的数组时,这比制作一个可以测试相等性的东西的数组更高的要求,因为您所说的是 可以比较数组中的每个项目 就好像它们都是同一类型 一样。但是由于你的结构是不同类型的,你不能保证这一点,所以代码无法编译。

这是另一种思考方式。

没有关联类型要求的协议和有关联类型要求的协议实际上是两种不同的动物。没有 Self 的协议基本上看起来和行为都像类型。具有 Self 的协议是 类型本身符合 的特征。从本质上讲,它们“更上一层楼”,就像一种类型。 (在概念上与 元类型 相关。)

这就是为什么写这样的东西没有意义:

let array:[Equatable] = [5, "a", false]

你可以这么写:

let array:[Int] = [5, 6, 7]

或者这个:

let array:[String] = ["a", "b", "c"]

或者这个:

let array:[Bool] = [false, true, false]

因为IntStringBool是类型。 Equatable 不是类型,它是类型中的类型。

写这样的东西“有意义”……

let array:[Equatable] = [Int.self, String.self, Bool.self]

尽管这确实在扩展类型安全编程的范围,因此 Swift 不允许这样做。你需要一个像 Python 这样完全灵活的元类型系统来表达这样的想法。

那么我们如何解决您的问题呢?好吧,首先要意识到在你的数组上应用 SwiftLCS 有意义的唯一原因是,在某种程度上,你的所有数组元素都可以简化为一个键数组,这些键都是相同的Equatable 类型。在本例中,它是 String,因为您可以通过 [Field](...).map{ [=46=].content } 获得数组 keys:[String]。也许如果我们重新设计 SwiftLCS,这会为它创建一个更好的界面。

然而,由于我们只能直接比较我们的 Field 数组,我们需要确保它们都可以向上转换为相同的类型,而做到这一点的方法就是继承。

class Field:Equatable
{
    let content:String

    static func == (lhs:Field, rhs:Field) -> Bool
    {
        return lhs.content == rhs.content
    }

    init(_ content:String)
    {
        self.content = content
    }
}

class First:Field
{
    init(content:String)
    {
        super.init(content)
    }
}

class Second:Field
{
    init(content:String)
    {
        super.init(content)
    }
}


let items:[Field] = [First(content: "abc"), Second(content: "cxz")]

数组然后将它们全部向上转换为 Field 类型,即 Equatable.

顺便说一句,讽刺的是,这个问题的“面向协议”的解决方案居然还涉及到继承。 SwiftLCS API 会提供类似

的协议
protocol LCSElement 
{
    associatedtype Key:Equatable
    var key:Key { get }
}

我们会用超级class

专门化它
class Field:LCSElement
{
    let key:String // <- this is what specializes Key to a concrete type

    static func == (lhs:Field, rhs:Field) -> Bool
    {
        return lhs.key == rhs.key
    }

    init(_ key:String)
    {
        self.key = key
    }
}

图书馆会将其用作

func LCS<T: LCSElement>(array:[T])
{
    array[0].key == array[1].key
    ...
}

协议和继承不是彼此对立或替代的。他们相辅相成。