Quick extension for [String]?

I am trying to write an extension method for [String] .

It seems you can't directly extend [String] (the "Element Type" is bound to the non-protocol "String" type), although I came across this trick:

 protocol StringType { } extension String: StringType { } 

But I still can't make Swift happy with this:

 extension Array where Element: StringType { // ["a","b","c","d","e"] -> "a, b, c, d, or e". func joinWithCommas() -> String { switch count { case 0, 1, 2: return joinWithSeparator(" or ") default: return dropLast(1).joinWithSeparator(", ") + ", or " + last! } } } 

The joinWithSeparator calls are "Ambiguous." I tried everything that I could think of, for example, using (self as! [String]) (and a bunch of similar options), but nothing works.

How can I make the Swift compiler this?

+8
swift
Jul 23 '16 at 19:19
source share
2 answers

You can follow the declaration of joinWithSeparator (Cmd-click on it) and find that it is defined as an extension of the SequenceType protocol instead of the Array type.

 // swift 2: extension SequenceType where Generator.Element == String { public func joinWithSeparator(separator: String) -> String } 

( Note: In Xcode 8 / Swift 3, if you click join(separator:) , you will land on Array , even if it is still implemented inside Sequence , but this will not invalidate the idea below)

We could do the same with your function, where we extend the protocol adopted by Array, and not the array itself:

 // swift 2: extension CollectionType where Generator.Element == String, SubSequence.Generator.Element == String, Index: BidirectionalIndexType { func joinWithCommas() -> String { switch count { case 0, 1, 2: return joinWithSeparator(" or ") default: return dropLast(1).joinWithSeparator(", ") + " or " + last! } } } // swift 3: extension BidirectionalCollection where Iterator.Element == String, SubSequence.Iterator.Element == String { func joinWithCommas() -> String { switch count { case 0, 1, 2: return joined(separator: " or ") default: return dropLast().joined(separator: ", ") + " or " + last! } } } 

Note:

  • we extend CollectionType to be able to use count
  • we restrict Generator.Element == String to use joinWithSeparator
  • we restrict SubSequence.Generator.Element == String to ensure that dropLast(1) can use joinWithSeparator . dropLast(1) returns the associated SubSequence type.
  • we restrict Index: BidirectionalIndexType to last .
+6
Jul 23 '16 at 19:39
source share

edit / update

Swift 4 or later, it is best to limit the elements of the StringProtocol collection, which will also span substrings.

 extension BidirectionalCollection where Element: StringProtocol { var joinedWithCommas: String { guard let last = last else { return "" } return count > 2 ? dropLast().joined(separator: ", ") + " or " + last : joined(separator: " or ") } } 

In Swift 3.1 (Xcode 8.3.2), you can simply expand the array where the element type is String

 extension Array where Element == String { var joinedWithCommas: String { guard let last = last else { return "" } return count > 2 ? dropLast().joined(separator: ", ") + " or " + last : joined(separator: " or ") } } 



 ["a","b","c"].joinedWithCommas // "a, b or c" 
+9
May 05 '17 at 18:48
source share



All Articles