2015-04-12 71 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') –

相關問題