繁体   English   中英

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

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

我正在尝试使用Composition而不是Inheritance,并且想在符合给定协议的对象数组上使用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

但是我很快发现:

错误:协议“字段”仅具有通用或相关类型要求,因此只能用作通用约束

我理解为什么原因是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, { $0 && $1 })) // 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

错误:类型[[Field]'的值没有成员'diff'

从现在开始,老实说我有点迷茫。 我阅读了一些有关类型擦除的精彩文章,但即使提供的示例也遇到了相同的问题(我认为是与Equatable )。

我对吗? 如果是这样,该怎么办?

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

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

再次感谢

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

让我们看一下Equatable协议:

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

这是什么意思? 好了,重要的是要了解“相等”在Swift上下文中实际上意味着什么。 “相等的”是使其成为结构或类的特征,以便可以将该结构或类的任何实例与该结构或类的任何其他实例进行比较。 它没有说过将其与其他类或结构的实例进行相等性比较。

想一想。 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{ $0.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")]

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

顺便说一句,具有讽刺意味的是,针对该问题的“基于协议”的解决方案实际上仍然涉及继承。 SwiftLCS API将提供类似的协议

protocol LCSElement 
{
    associatedtype Key:Equatable
    var key:Key { get }
}

我们将以超类来专门化它

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
    ...
}

协议和继承不是对立的或相互替代的。 他们互相补充。

我知道现在可能正是您想要的,但是我知道如何使其工作的唯一方法是引入其他包装器类:

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: $0) })
        let w2 = otherCollection.map({ FieldEquatableWrapper(wrapped: $0) })
        return w1.diff(w2)
    }
}

然后你可以做

    let outcome = FieldEquatableWrapper.diff(array1, array2)

我认为您不能使Field完全符合Equatable ,因为它使用Self伪类被设计为“类型安全”。 这是包装器类的原因之一。 不幸的是,似乎还有一个我不知道如何解决的问题:我无法将这个“包装”的diff放入CollectionArray扩展中,并且仍然使其支持异构[Field]数组而没有编译错误:

不支持将“字段”用作符合协议“字段”的具体类型

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

PS

在问题中您提到

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

但鉴于您定义==运算符的方式,我希望这是true

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM