简体   繁体   中英

Key path value type 'Int' cannot be converted to contextual type 'String'

I am trying to pass a multiple tuples containing a KeyPath and a type of a sort order to a method that should do sorting.

I have this method:

extension Array {
    mutating func sort<T: Comparable>(by criteria: (path: KeyPath<Element, T>, order:OrderType)...) {
        
        criteria.forEach { path, order in
            //...
            sort { first, second in
            order.makeComparator()(
                first[keyPath: path],
                second[keyPath: path]
            )
        }
        }
    }
}

and I am using it like this:

var posts = BlogPost.examples
        
posts.sort(by:(path:\.pageViews, order: .asc), (path:\.sessionDuration, order: .desc))

Now, cause both pageViews and sessionDuration properties are integers , this will work.

But if I want to pass two properties of different types (say String and Int ), I am getting this error:

Key path value type 'Int' cannot be converted to contextual type 'String'

Here is rest of the code, but I guess is not that relevant:

enum OrderType: String {
    case asc
    case desc
}

extension OrderType {
    func makeComparator<T: Comparable>() -> (T, T) -> Bool {
        switch self {
        case .asc:
            return (<)
        case .desc:
            return (>)
        }
    }
}

How should I define sort method so that I it accept heterogenous key paths?

There are no variadic generics (yet), so you'll need to write a AnyComparable type eraser. This one is adapted from this post here .

struct AnyComparable: Equatable, Comparable {
    private let lessThan: (Any) -> Bool
    private let value: Any
    private let equals: (Any) -> Bool

    public static func == (lhs: AnyComparable, rhs: AnyComparable) -> Bool {
        lhs.equals(rhs.value) || rhs.equals(lhs.value)
    }
    
    public init<C: Comparable>(_ value: C) {
        self.value = value
        self.equals = { $0 as? C == value }
        self.lessThan = { ($0 as? C).map { value < $0 } ?? false }
    }

    public static func < (lhs: AnyComparable, rhs: AnyComparable) -> Bool {
        lhs.lessThan(rhs.value) || (rhs != lhs && !rhs.lessThan(lhs.value))
    }
}

With that, you can write your sort method signature as:

mutating func sort(by criteria: (path: KeyPath<Element, AnyComparable>, order:OrderType)...) {
    
    
}

To make it easier for us to pass key paths with the type AnyComparable in, we can make an extension:

extension Comparable {
    // this name might be too long, but I'm sure you can come up with a better name
    var anyComparable: AnyComparable {
        .init(self)
    }
}

Now we can do:

someArray.sort(by: (\.key1.anyComparable, .asc), (\.key2.anyComparable, .asc))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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