我尝试使用Composition而不是继承,我想在符合给定协议的对象数组上使用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
但我很快就发现了:
错误:协议&#39;字段&#39;只能用作通用约束,因为它具有Self或关联类型要求
我理解为什么因为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
错误:类型的值&#39; [字段]&#39;没有会员&#39;差异&#39;
从现在开始,说实话,我有点失落。我读了一些关于类型擦除的好帖子,但即使是提供的示例也遇到了同样的问题(我假设缺乏与Equatable
的一致性)。
我是对的吗?如果是这样,怎么办呢?
更新:
我不得不停止这个实验一段时间,完全忘记了依赖,对不起! Diff
是SwiftLCS提供的方法,是最长公共子序列(LCS)算法的实现。
TL; DR:
Field
协议需要遵守Equatable
,但到目前为止,我还无法做到这一点。我需要能够创建符合此协议的对象数组(请参阅第一个代码块中的错误)。
再次感谢
答案 0 :(得分:2)
问题来自于Equatable
协议的含义和Swift对类型重载函数的支持。
我们来看看Equatable
协议:
protocol Equatable
{
static func ==(Self, Self) -> Bool
}
这是什么意思?因此,了解“等于”在Swift环境中实际意味着什么是很重要的。 “Equatable”是结构或类的特征,使得该结构或类的任何实例可以与该结构的任何其他实例进行相等性比较或类。它没有说明将它与不同类或结构的实例进行比较。
想一想。 Int
和String
都是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]
因为Int
,String
和Bool
是类型。 Equatable
不是类型,它是一种类型。
let array:[Equatable] = [Int.self, String.self, Bool.self]
尽管这确实扩展了类型安全编程的范围,因此Swift不允许这样做。你需要一个像Python这样的完全灵活的元模型系统来表达这样的想法。
那么我们如何解决您的问题呢?好吧,首先要意识到在你的数组上应用SwiftLCS是有意义的唯一原因是因为,在某种程度上,你的所有数组元素都可以简化为一组完全相同的键Equatable
类型。在这种情况下,它是String
,因为您可以通过执行keys:[String]
来获取数组[Field](...).map{ $0.content }
。也许如果我们重新设计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
。{/ p>
顺便说一句,具有讽刺意味的是,这个问题的“面向协议”解决方案实际上仍然涉及继承。 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
...
}
协议和继承不是彼此的对立或替代。它们相互补充。
答案 1 :(得分:1)
我知道这可能是你想要的,但我知道如何使其工作的唯一方法是引入额外的包装类:
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
放入Collection
或Array
扩展程序中并仍然支持异构[Field]
数组没有编译错误:
不支持使用'Field'作为符合协议'Field'的具体类型
如果有人知道更好的解决方案,我也很感兴趣。
P.S。
在你提到的问题中
print(First(content: "abc") == Second(content: "abc")) // false
但根据您定义true
运算符
==
为ActiveRecord