我正在编写a library来创建默认swift类型的扩展。
我想检查一下我的数组扩展是否是某种类型实现了某种协议。例如,请参见此方法:

extension Array {
    /// Compares the items using the given comparer and only returns non-equal values
    /// :returns: the first items that are unique according to the comparer
    func distinct(comparer: (T, T) -> Bool) -> [T] {
        var result: [T] = []
        outerLoop: for item in self {
            for resultItem in result {
                if comparer(item, resultItem) {
                    continue outerLoop
                }
            }
            result.append(item)
        }
        return result
    }
}

现在我想重写这个方法来检查T是否是Equatable这样:
/// Compares the items using the given comparer and only returns non-equal values
/// :returns: the first items that are unique according to the comparer
func distinct(comparer: ((T, T) -> Bool)?) -> [T] {
    var result: [T] = []
    outerLoop: for item in self {
        for resultItem in result {
            if isEquatable ? comparer!(item, resultItem) : item == resultItem {
                continue outerLoop
            }
        }
        result.append(item)
    }
    return result
}

其中isEquatable是一个Bool值,它告诉我T是否Equatable。我怎么能找到这个?

最佳答案

目前在swift中没有一个好的方法可以做到这一点。*这就是为什么像sorted这样的函数要么是自由函数,要么对于成员来说是谓词。您所寻找的test-and-cast方法的主要问题是Equatable和类似的协议具有关联的类型或依赖于Self,因此只能在泛型函数中用作约束。
我猜您的目标是调用者可以跳过提供comparator函数,因此如果可用的话,它将返回到Equatable?如果不是的话就崩溃?这里的问题是,函数在运行时(参数是Equatable)确定一些东西,而这实际上应该在编译时确定。这并不好——最好在编译时完全确定这些东西。
因此您可以编写一个需要Equatable的自由函数:

func distinct<C: CollectionType where C.Generator.Element: Equatable>
  (source: C) -> [C.Generator.Element] {

    var seen: [C.Generator.Element] = []
    return filter(source) {
        if contains(seen, $0) {
            return false
        }
        else {
            seen.append($0)
            return true
        }
    }
}

let uniques = distinct([1,2,3,1,1,2])  // [1,2,3]

然后,如果试图用不可比较的方法调用它,就会出现编译时错误:
let incomparable = [1,2,3] as [Any]
distinct(incomparable)  // compiler barfs - Any isn’t Equatable

使用运行时方法,只有在运行程序时才能发现这一点。
好消息是,也有好处。为每个元素搜索数组的问题是,对于大型数组,函数将非常慢,因为对于每个元素,必须线性搜索已看到元素的列表。如果使用另一个版本重载distinct,该版本要求元素为Hashable(通常情况下是这样),则可以使用集合来跟踪它们:
func distinct<C: CollectionType where C.Generator.Element: Hashable>
  (source: C) -> [C.Generator.Element] {

    var seen: Set<C.Generator.Element> = []
    return filter(source) {
        if seen.contains($0) {
            return false
        }
        else {
            seen.insert($0)
            return true
        }
    }
}

在编译时,编译器将选择函数的最佳版本并使用它。如果你的东西是散列的,那么这个版本会被选择,如果它是可计算的,那么它将使用较慢的版本(这是因为Equatable继承自Hashable,而编译器选择更专门的函数)。在编译时而不是运行时执行此操作意味着您不必为检查支付罚金,这都是预先确定的。
*有一些丑陋的方法,但是由于目标是吸引人的语法,所以重点是什么…也许下一个版本将允许对方法进行约束,这将是很好的。

关于swift - 检查类型是否实现协议(protocol),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/29593024/

10-09 02:33