有没有办法为Swift中的`struct`自动定义比较(`==`)函数?
Is there way to define compare (`==`) function automatically for `struct` in Swift?
假设我们在 Swift 中有一个相当大的 struct
:
struct SuperStruct {
var field1: Int = 0
var field2: String = ""
// lots of lines...
var field512: Float = 0.0
}
.. 然后我们需要实现 Equatable
协议:
extension SuperStruct: Equatable {
}
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
return
lhs.field1 == rhs.field1 &&
lhs.field2 == rhs.field2 &&
// lots of lines...
lhs.field512 == rhs.field512
}
...而且我们需要编写很多行愚蠢的代码。
有没有办法"to ask"编译器"to do"适合我们?
以下答案显示了一种可能的解决方案;可能不是推荐的(但是这个问题的未来读者可能会感兴趣)。
如果您有大量属性都属于数量有限的不同 类型 ,您可以使用结构实例的 Mirror
并迭代超过结构的属性;对于每次尝试转换为您知道的不同类型您的属性。
在观看了以下 WWDC 2015 session(感谢 Leo Dabus!)后,我编辑了之前的答案(我认为它更简洁):
我也会在这个答案的底部留下最初的答案,因为它显示了一种替代方法,更少 protocol-oriented 方法,可以利用这个 Mirror
解决方案。
Mirror
&protocol-oriented解法:
/* Let a heterogeneous protocol act as "pseudo-generic" type
for the different (property) types in 'SuperStruct' */
protocol MyGenericType {
func isEqualTo(other: MyGenericType) -> Bool
}
extension MyGenericType where Self : Equatable {
func isEqualTo(other: MyGenericType) -> Bool {
if let o = other as? Self { return self == o }
return false
}
}
/* Extend types that appear in 'SuperStruct' to MyGenericType */
extension Int : MyGenericType {}
extension String : MyGenericType {}
extension Float : MyGenericType {}
// ...
/* Finally, 'SuperStruct' conformance to Equatable */
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
let mLhs = Mirror(reflecting: lhs).children.filter { [=10=].label != nil }
let mRhs = Mirror(reflecting: rhs).children.filter { [=10=].label != nil }
for i in 0..<mLhs.count {
guard let valLhs = mLhs[i].value as? MyGenericType, valRhs = mRhs[i].value as? MyGenericType else {
print("Invalid: Properties 'lhs.\(mLhs[i].label!)' and/or 'rhs.\(mRhs[i].label!)' are not of 'MyGenericType' types.")
return false
}
if !valLhs.isEqualTo(valRhs) {
return false
}
}
return true
}
用法示例:
/* Example */
var a = SuperStruct()
var b = SuperStruct()
a == b // true
a.field1 = 2
a == b // false
b.field1 = 2
b.field2 = "Foo"
a.field2 = "Foo"
a == b // true
前一个Mirror
解法:
/* 'SuperStruct' conformance to Equatable */
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
let mLhs = Mirror(reflecting: lhs).children.filter { [=12=].label != nil }
let mRhs = Mirror(reflecting: rhs).children.filter { [=12=].label != nil }
for i in 0..<mLhs.count {
switch mLhs[i].value {
case let valLhs as Int:
guard let valRhs = mRhs[i].value as? Int where valRhs == valLhs else {
return false
}
case let valLhs as String:
guard let valRhs = mRhs[i].value as? String where valRhs == valLhs else {
return false
}
case let valLhs as Float:
guard let valRhs = mRhs[i].value as? Float where valRhs == valLhs else {
return false
}
/* ... extend with one case for each type
that appear in 'SuperStruct' */
case _ : return false
}
}
return true
}
用法示例:
/* Example */
var a = SuperStruct()
var b = SuperStruct()
a == b // true
a.field1 = 2
a == b // false
b.field1 = 2
b.field2 = "Foo"
a.field2 = "Foo"
a == b // true
不,不是。至少在任何情况下都不会过于复杂并且基于运行时自省的使用(滥用?)。请参阅 以了解技术上可行的内容,但这比编写直接比较所有字段的 ==
实现要复杂 方式 。
至于您对 "should" 在 Swift 中可用的内容的看法,如果您分享它们,您更有可能看到一些效果 with Apple or with the Swift open source community。
您可以使结构 Codable 并比较 JSON 编码数据。效率不高,但对某些应用程序可能有用(例如单元测试)。
struct SuperStruct: Encodable {
var field1: Int = 0
// ....
var field512: Float = 0.0
}
let s1 = SuperStruct()
let s2 = SuperStruct()
let encoder = JSONEncoder()
let data1 = try! encoder.encode(s1)
let data2 = try! encoder.encode(s2)
let result = (data1 == data2)
如果你喜欢这个,你可以把它整理成 Encodable
的协议扩展。
在 Swift 4.1 中,如果所有类型的成员都是 Equatable/Hashable
,Equatable/Hashable 类型现在综合符合 Equatable/Hashable
SE-0185
综合 Equatable 和 Hashable 一致性
Developers have to write large amounts of boilerplate code to support equatability and hashability of complex types. This proposal offers a way for the compiler to automatically synthesize conformance to Equatable and Hashable to reduce this boilerplate, in a subset of scenarios where generating the correct implementation is known to be possible.
https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md
假设我们在 Swift 中有一个相当大的 struct
:
struct SuperStruct {
var field1: Int = 0
var field2: String = ""
// lots of lines...
var field512: Float = 0.0
}
.. 然后我们需要实现 Equatable
协议:
extension SuperStruct: Equatable {
}
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
return
lhs.field1 == rhs.field1 &&
lhs.field2 == rhs.field2 &&
// lots of lines...
lhs.field512 == rhs.field512
}
...而且我们需要编写很多行愚蠢的代码。 有没有办法"to ask"编译器"to do"适合我们?
以下答案显示了一种可能的解决方案;可能不是推荐的(但是这个问题的未来读者可能会感兴趣)。
如果您有大量属性都属于数量有限的不同 类型 ,您可以使用结构实例的 Mirror
并迭代超过结构的属性;对于每次尝试转换为您知道的不同类型您的属性。
在观看了以下 WWDC 2015 session(感谢 Leo Dabus!)后,我编辑了之前的答案(我认为它更简洁):
我也会在这个答案的底部留下最初的答案,因为它显示了一种替代方法,更少 protocol-oriented 方法,可以利用这个 Mirror
解决方案。
Mirror
&protocol-oriented解法:
/* Let a heterogeneous protocol act as "pseudo-generic" type
for the different (property) types in 'SuperStruct' */
protocol MyGenericType {
func isEqualTo(other: MyGenericType) -> Bool
}
extension MyGenericType where Self : Equatable {
func isEqualTo(other: MyGenericType) -> Bool {
if let o = other as? Self { return self == o }
return false
}
}
/* Extend types that appear in 'SuperStruct' to MyGenericType */
extension Int : MyGenericType {}
extension String : MyGenericType {}
extension Float : MyGenericType {}
// ...
/* Finally, 'SuperStruct' conformance to Equatable */
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
let mLhs = Mirror(reflecting: lhs).children.filter { [=10=].label != nil }
let mRhs = Mirror(reflecting: rhs).children.filter { [=10=].label != nil }
for i in 0..<mLhs.count {
guard let valLhs = mLhs[i].value as? MyGenericType, valRhs = mRhs[i].value as? MyGenericType else {
print("Invalid: Properties 'lhs.\(mLhs[i].label!)' and/or 'rhs.\(mRhs[i].label!)' are not of 'MyGenericType' types.")
return false
}
if !valLhs.isEqualTo(valRhs) {
return false
}
}
return true
}
用法示例:
/* Example */
var a = SuperStruct()
var b = SuperStruct()
a == b // true
a.field1 = 2
a == b // false
b.field1 = 2
b.field2 = "Foo"
a.field2 = "Foo"
a == b // true
前一个Mirror
解法:
/* 'SuperStruct' conformance to Equatable */
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
let mLhs = Mirror(reflecting: lhs).children.filter { [=12=].label != nil }
let mRhs = Mirror(reflecting: rhs).children.filter { [=12=].label != nil }
for i in 0..<mLhs.count {
switch mLhs[i].value {
case let valLhs as Int:
guard let valRhs = mRhs[i].value as? Int where valRhs == valLhs else {
return false
}
case let valLhs as String:
guard let valRhs = mRhs[i].value as? String where valRhs == valLhs else {
return false
}
case let valLhs as Float:
guard let valRhs = mRhs[i].value as? Float where valRhs == valLhs else {
return false
}
/* ... extend with one case for each type
that appear in 'SuperStruct' */
case _ : return false
}
}
return true
}
用法示例:
/* Example */
var a = SuperStruct()
var b = SuperStruct()
a == b // true
a.field1 = 2
a == b // false
b.field1 = 2
b.field2 = "Foo"
a.field2 = "Foo"
a == b // true
不,不是。至少在任何情况下都不会过于复杂并且基于运行时自省的使用(滥用?)。请参阅 ==
实现要复杂 方式 。
至于您对 "should" 在 Swift 中可用的内容的看法,如果您分享它们,您更有可能看到一些效果 with Apple or with the Swift open source community。
您可以使结构 Codable 并比较 JSON 编码数据。效率不高,但对某些应用程序可能有用(例如单元测试)。
struct SuperStruct: Encodable {
var field1: Int = 0
// ....
var field512: Float = 0.0
}
let s1 = SuperStruct()
let s2 = SuperStruct()
let encoder = JSONEncoder()
let data1 = try! encoder.encode(s1)
let data2 = try! encoder.encode(s2)
let result = (data1 == data2)
如果你喜欢这个,你可以把它整理成 Encodable
的协议扩展。
在 Swift 4.1 中,如果所有类型的成员都是 Equatable/Hashable
,Equatable/Hashable 类型现在综合符合 Equatable/HashableSE-0185
综合 Equatable 和 Hashable 一致性
Developers have to write large amounts of boilerplate code to support equatability and hashability of complex types. This proposal offers a way for the compiler to automatically synthesize conformance to Equatable and Hashable to reduce this boilerplate, in a subset of scenarios where generating the correct implementation is known to be possible.
https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md