Swift 泛型、约束和 KeyPath
Swift Generics, Constraints, and KeyPaths
我知道 Swift 中泛型的局限性以及它们存在的原因,所以这不是关于编译器错误的问题。相反,我偶尔会 运行 遇到一些情况,这些情况似乎可以通过一些可用资源(即泛型、associatedTypes/protocols 等)的组合来实现,但似乎无法找到解决方案。
在这个例子中,我试图想出一个 Swift 来替代 NSSortDescriptor(只是为了好玩)。当你只有一个描述符时,它工作得很好,但是,就像 NS 版本经常做的那样,创建一个 SortDescriptors 数组来对多个键进行排序会很好。
这里的另一个试验是使用 Swift KeyPaths。因为那些需要 Value 类型,而比较需要 Comparable 值,所以我 运行 遇到了麻烦 where/how 定义类型来满足一切。
这可能吗?这是我想出的最接近的解决方案之一,但是,正如您在底部看到的那样,它在构建数组时达不到要求。
同样,我明白为什么这不能按原样工作,但我很好奇是否有办法实现所需的功能。
struct Person {
let name : String
let age : Int
}
struct SortDescriptor<T, V:Comparable> {
let keyPath: KeyPath<T,V>
let ascending : Bool
init(_ keyPath: KeyPath<T,V>, ascending:Bool = true) {
self.keyPath = keyPath
self.ascending = ascending
}
func compare(obj:T, other:T) -> Bool {
let v1 = obj[keyPath: keyPath]
let v2 = other[keyPath: keyPath]
return ascending ? v1 < v2 : v2 < v1
}
}
let jim = Person(name: "Jim", age: 30)
let bob = Person(name: "Bob", age: 35)
let older = SortDescriptor(\Person.age).compare(obj: jim, other: bob) // true
// Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
var descriptors = [SortDescriptor(\Person.age), SortDescriptor(\Person.name)]
这里的问题是 SortDescriptor
在 T
和 V
上都是通用的,但您只希望它在 T
上通用。也就是说,你想要一个 SortDescriptor<Person>
,因为你关心它比较 Person
。你不需要 SortDescriptor<Person, String>
,因为一旦它被创建,你不关心它是在 String
属性 of Person
.
上进行比较
“隐藏”V
的最简单方法可能是使用闭包来包装关键路径:
struct SortDescriptor<T> {
var ascending: Bool
var primitiveCompare: (T, T) -> Bool
init<V: Comparable>(keyPath: KeyPath<T, V>, ascending: Bool = true) {
primitiveCompare = { [=10=][keyPath: keyPath] < [keyPath: keyPath] }
self.ascending = ascending
}
func compare(_ a: T, _ b: T) -> Bool {
return ascending ? primitiveCompare(a, b) : primitiveCompare(b, a)
}
}
var descriptors = [SortDescriptor(keyPath: \Person.name), SortDescriptor(keyPath: \.age)]
// Inferred type: [SortDescriptor<Person>]
之后,您可能需要一种方便的方法来使用 SortDescriptor
序列与对象进行比较。为此,我们需要一个协议:
protocol Comparer {
associatedtype Compared
func compare(_ a: Compared, _ b: Compared) -> Bool
}
extension SortDescriptor: Comparer { }
然后我们可以使用 compare
方法扩展 Sequence
:
extension Sequence where Element: Comparer {
func compare(_ a: Element.Compared, _ b: Element.Compared) -> Bool {
for comparer in self {
if comparer.compare(a, b) { return true }
if comparer.compare(b, a) { return false }
}
return false
}
}
descriptors.compare(jim, bob)
// false
如果您使用具有条件一致性的较新版本 Swift,您应该能够通过将扩展名的第一行更改为有条件地使 Sequence
符合 Comparer
这个:
extension Sequence: Comparer where Element: Comparer {
扩展@Rob Mayoff 的回答,这是一个完整的排序解决方案
enum SortDescriptorComparison {
case equal
case greaterThan
case lessThan
}
struct SortDescriptor<T> {
private let compare: (T, T) -> SortDescriptorComparison
let ascending : Bool
init<V: Comparable>(_ keyPath: KeyPath<T,V>, ascending:Bool = true) {
self.compare = {
let v1 = [=10=][keyPath: keyPath]
let v2 = [keyPath: keyPath]
if v1 == v2 {
return .equal
} else if v1 > v2 {
return .greaterThan
} else {
return .lessThan
}
}
self.ascending = ascending
}
func compare(v1:T, v2:T) -> SortDescriptorComparison {
return compare(v1, v2)
}
}
extension Array {
mutating func sort(sortDescriptor: SortDescriptor<Element>) {
self.sort(sortDescriptors: [sortDescriptor])
}
mutating func sort(sortDescriptors: [SortDescriptor<Element>]) {
self.sort() {
for sortDescriptor in sortDescriptors {
switch sortDescriptor.compare(v1: [=10=], v2: ) {
case .equal:
break
case .greaterThan:
return !sortDescriptor.ascending
case .lessThan:
return sortDescriptor.ascending
}
}
return false
}
}
}
extension Sequence {
func sorted(sortDescriptor: SortDescriptor<Element>) -> [Element] {
return self.sorted(sortDescriptors: [sortDescriptor])
}
func sorted(sortDescriptors: [SortDescriptor<Element>]) -> [Element] {
return self.sorted() {
for sortDescriptor in sortDescriptors {
switch sortDescriptor.compare(v1: [=10=], v2: ) {
case .equal:
break
case .greaterThan:
return !sortDescriptor.ascending
case .lessThan:
return sortDescriptor.ascending
}
}
return false
}
}
}
struct Person {
let name : String
let age : Int
}
let jim = Person(name: "Jim", age: 25)
let bob = Person(name: "Bob", age: 30)
let tim = Person(name: "Tim", age: 25)
let abe = Person(name: "Abe", age: 20)
let people = [tim, jim, bob, abe]
let sorted = people.sorted(sortDescriptors: [SortDescriptor(\Person.age), SortDescriptor(\Person.name)])
print(sorted) //Abe, Jim, Time, Bob
这是一个几乎纯功能性的解决方案:
// let's add some semantics
typealias SortDescriptor<T> = (T, T) -> Bool
// type constructor for SortDescriptor
func sortDescriptor<T, U: Comparable>(keyPath: KeyPath<T, U>, ascending: Bool) -> SortDescriptor<T> {
return { ascending == ([=10=][keyPath: keyPath] < [keyPath: keyPath]) }
}
// returns a function that can sort any two element of type T, based on
// the provided list of descriptors
func compare<T>(with descriptors: [SortDescriptor<T>]) -> (T, T) -> Bool {
func innerCompare(descriptors: ArraySlice<SortDescriptor<T>>, a: T, b: T) -> Bool {
guard let descriptor = descriptors.first else { return false }
if descriptor(a, b) { return true }
else if descriptor(b, a) { return false }
else { return innerCompare(descriptors: descriptors.dropFirst(1), a: a, b: b) }
}
return { a, b in innerCompare(descriptors: descriptors[0...], a: a, b: b) }
}
// back to imperative, extend Sequence to allow sorting with descriptors
extension Sequence {
func sorted(by descriptors: [SortDescriptor<Element>]) -> [Element] {
return sorted(by: compare(with: descriptors))
}
}
它基于可重用的小型函数,例如 compare()
,可以在其他范围内轻松重用。
用法示例:
struct Person {
let name : String
let age : Int
}
let jim = Person(name: "Jim", age: 30)
let bob = Person(name: "Bob", age: 35)
let alice = Person(name: "Alice", age: 35)
let aly = Person(name: "Aly", age: 32)
let descriptors = [sortDescriptor(keyPath: \Person.age, ascending: false),
sortDescriptor(keyPath: \Person.name, ascending: true)]
let persons = [jim, bob, alice, aly]
print(persons.sorted(by: descriptors))
我知道 Swift 中泛型的局限性以及它们存在的原因,所以这不是关于编译器错误的问题。相反,我偶尔会 运行 遇到一些情况,这些情况似乎可以通过一些可用资源(即泛型、associatedTypes/protocols 等)的组合来实现,但似乎无法找到解决方案。
在这个例子中,我试图想出一个 Swift 来替代 NSSortDescriptor(只是为了好玩)。当你只有一个描述符时,它工作得很好,但是,就像 NS 版本经常做的那样,创建一个 SortDescriptors 数组来对多个键进行排序会很好。
这里的另一个试验是使用 Swift KeyPaths。因为那些需要 Value 类型,而比较需要 Comparable 值,所以我 运行 遇到了麻烦 where/how 定义类型来满足一切。
这可能吗?这是我想出的最接近的解决方案之一,但是,正如您在底部看到的那样,它在构建数组时达不到要求。
同样,我明白为什么这不能按原样工作,但我很好奇是否有办法实现所需的功能。
struct Person {
let name : String
let age : Int
}
struct SortDescriptor<T, V:Comparable> {
let keyPath: KeyPath<T,V>
let ascending : Bool
init(_ keyPath: KeyPath<T,V>, ascending:Bool = true) {
self.keyPath = keyPath
self.ascending = ascending
}
func compare(obj:T, other:T) -> Bool {
let v1 = obj[keyPath: keyPath]
let v2 = other[keyPath: keyPath]
return ascending ? v1 < v2 : v2 < v1
}
}
let jim = Person(name: "Jim", age: 30)
let bob = Person(name: "Bob", age: 35)
let older = SortDescriptor(\Person.age).compare(obj: jim, other: bob) // true
// Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
var descriptors = [SortDescriptor(\Person.age), SortDescriptor(\Person.name)]
这里的问题是 SortDescriptor
在 T
和 V
上都是通用的,但您只希望它在 T
上通用。也就是说,你想要一个 SortDescriptor<Person>
,因为你关心它比较 Person
。你不需要 SortDescriptor<Person, String>
,因为一旦它被创建,你不关心它是在 String
属性 of Person
.
“隐藏”V
的最简单方法可能是使用闭包来包装关键路径:
struct SortDescriptor<T> {
var ascending: Bool
var primitiveCompare: (T, T) -> Bool
init<V: Comparable>(keyPath: KeyPath<T, V>, ascending: Bool = true) {
primitiveCompare = { [=10=][keyPath: keyPath] < [keyPath: keyPath] }
self.ascending = ascending
}
func compare(_ a: T, _ b: T) -> Bool {
return ascending ? primitiveCompare(a, b) : primitiveCompare(b, a)
}
}
var descriptors = [SortDescriptor(keyPath: \Person.name), SortDescriptor(keyPath: \.age)]
// Inferred type: [SortDescriptor<Person>]
之后,您可能需要一种方便的方法来使用 SortDescriptor
序列与对象进行比较。为此,我们需要一个协议:
protocol Comparer {
associatedtype Compared
func compare(_ a: Compared, _ b: Compared) -> Bool
}
extension SortDescriptor: Comparer { }
然后我们可以使用 compare
方法扩展 Sequence
:
extension Sequence where Element: Comparer {
func compare(_ a: Element.Compared, _ b: Element.Compared) -> Bool {
for comparer in self {
if comparer.compare(a, b) { return true }
if comparer.compare(b, a) { return false }
}
return false
}
}
descriptors.compare(jim, bob)
// false
如果您使用具有条件一致性的较新版本 Swift,您应该能够通过将扩展名的第一行更改为有条件地使 Sequence
符合 Comparer
这个:
extension Sequence: Comparer where Element: Comparer {
扩展@Rob Mayoff 的回答,这是一个完整的排序解决方案
enum SortDescriptorComparison {
case equal
case greaterThan
case lessThan
}
struct SortDescriptor<T> {
private let compare: (T, T) -> SortDescriptorComparison
let ascending : Bool
init<V: Comparable>(_ keyPath: KeyPath<T,V>, ascending:Bool = true) {
self.compare = {
let v1 = [=10=][keyPath: keyPath]
let v2 = [keyPath: keyPath]
if v1 == v2 {
return .equal
} else if v1 > v2 {
return .greaterThan
} else {
return .lessThan
}
}
self.ascending = ascending
}
func compare(v1:T, v2:T) -> SortDescriptorComparison {
return compare(v1, v2)
}
}
extension Array {
mutating func sort(sortDescriptor: SortDescriptor<Element>) {
self.sort(sortDescriptors: [sortDescriptor])
}
mutating func sort(sortDescriptors: [SortDescriptor<Element>]) {
self.sort() {
for sortDescriptor in sortDescriptors {
switch sortDescriptor.compare(v1: [=10=], v2: ) {
case .equal:
break
case .greaterThan:
return !sortDescriptor.ascending
case .lessThan:
return sortDescriptor.ascending
}
}
return false
}
}
}
extension Sequence {
func sorted(sortDescriptor: SortDescriptor<Element>) -> [Element] {
return self.sorted(sortDescriptors: [sortDescriptor])
}
func sorted(sortDescriptors: [SortDescriptor<Element>]) -> [Element] {
return self.sorted() {
for sortDescriptor in sortDescriptors {
switch sortDescriptor.compare(v1: [=10=], v2: ) {
case .equal:
break
case .greaterThan:
return !sortDescriptor.ascending
case .lessThan:
return sortDescriptor.ascending
}
}
return false
}
}
}
struct Person {
let name : String
let age : Int
}
let jim = Person(name: "Jim", age: 25)
let bob = Person(name: "Bob", age: 30)
let tim = Person(name: "Tim", age: 25)
let abe = Person(name: "Abe", age: 20)
let people = [tim, jim, bob, abe]
let sorted = people.sorted(sortDescriptors: [SortDescriptor(\Person.age), SortDescriptor(\Person.name)])
print(sorted) //Abe, Jim, Time, Bob
这是一个几乎纯功能性的解决方案:
// let's add some semantics
typealias SortDescriptor<T> = (T, T) -> Bool
// type constructor for SortDescriptor
func sortDescriptor<T, U: Comparable>(keyPath: KeyPath<T, U>, ascending: Bool) -> SortDescriptor<T> {
return { ascending == ([=10=][keyPath: keyPath] < [keyPath: keyPath]) }
}
// returns a function that can sort any two element of type T, based on
// the provided list of descriptors
func compare<T>(with descriptors: [SortDescriptor<T>]) -> (T, T) -> Bool {
func innerCompare(descriptors: ArraySlice<SortDescriptor<T>>, a: T, b: T) -> Bool {
guard let descriptor = descriptors.first else { return false }
if descriptor(a, b) { return true }
else if descriptor(b, a) { return false }
else { return innerCompare(descriptors: descriptors.dropFirst(1), a: a, b: b) }
}
return { a, b in innerCompare(descriptors: descriptors[0...], a: a, b: b) }
}
// back to imperative, extend Sequence to allow sorting with descriptors
extension Sequence {
func sorted(by descriptors: [SortDescriptor<Element>]) -> [Element] {
return sorted(by: compare(with: descriptors))
}
}
它基于可重用的小型函数,例如 compare()
,可以在其他范围内轻松重用。
用法示例:
struct Person {
let name : String
let age : Int
}
let jim = Person(name: "Jim", age: 30)
let bob = Person(name: "Bob", age: 35)
let alice = Person(name: "Alice", age: 35)
let aly = Person(name: "Aly", age: 32)
let descriptors = [sortDescriptor(keyPath: \Person.age, ascending: false),
sortDescriptor(keyPath: \Person.name, ascending: true)]
let persons = [jim, bob, alice, aly]
print(persons.sorted(by: descriptors))