2015-04-12 61 views
1

我正在编写a library,它为默认的Swift类型创建扩展。检查一个类型是否实现协议

我想检查我的Array扩展是否某个类型实现了某个协议。例如看到这个方法:

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 
    } 
} 

现在,我想改写这个方法来检查,如果TEquatable这样:

/// 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 
} 

其中isEquatableBool值,它告诉我,如果TEquatable。我怎样才能找到这个?

回答

3

目前在Swift中没有一种好的方法。*这就是为什么像sorted这样的函数要么是自由函数,要么是成员的情况下,使用谓词。您正在寻找的测试和管理方法的主要问题是Equatable和类似的协议具有关联的类型或依赖于Self,因此只能在泛型函数中用作约束。

我猜你的目标是,调用者可以跳过提供比较功能,所以它会回落到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 

使用运行时方法,只有在运行程序时才会发现这一点。

好消息是,还有上涨空间。为每个元素搜索一个数组的问题是该函数对于大数组来说会很慢,因为对于每个元素,必须线性搜索已经看过的元素列表。如果你有需要的元素是Hashable(其中Equatable的东西往往是)另一版本超载distinct,您可以使用一组跟踪它们:

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 
     } 
    } 
} 

在编译时,编译器会选择最好的版本该功能和用途。如果你的东西是可散列的,那么这个版本会被挑选出来,如果它只是相等的,它会使用较慢的版本(这是因为Hashable继承自Equatable,并且编译器选择更专用的函数)。在编译时而不是运行时间这样做意味着您不支付支票的罚款,这一切都在前面确定。

*有难看的方法,但既然目标是有吸引力的语法,有什么意义......也许下一个版本将允许方法的约束,这将是很好的。

+0

'我猜你的目标是调用者可以跳过提供比较函数,所以它会回落到Equatable(如果可用)?'是的,但只有当'T'不是'Equatable'且给定闭包是'nil'时才会崩溃。我将在明天某处查看此代码,并接受适用的代码。 – vrwim

+0

有没有办法在扩展中做到这一点?我不想膨胀我的全球范围......特别是因为这是一个图书馆。 – vrwim

+0

你可以创建一个'Collections Collections {static func distinct etc ...}',然后用'Collections.distinct([1,2,3])'调用它,但要记住Swift已经隐含了框架名称空间(例如,如果你正在编写一个框架“CoolStuff”,那么这个函数将是'CoolStuff.distinct'。这就是Swift库的功能,即'filter'实际上是'Swift.filter'(但是所有的Swift项目隐含'import Swift') –

相关问题