Exploring Swift 2 Protocol Extensions
/One of Swift 2's most exciting additions are protocol extensions. These allow you to add new methods to anything that implements a protocol. I thought it might be interesting to explore this with a practical example, generating random or repeating sequences from any collection.
The use-case is quite simple. We have a collection and we would like to either select a certain number of random elements from it, or repeat it up to a certain number. We may like to do either of these, but manage our own stopping condition.
It's now incredibly easy to inject the new methods into a protocol's implementers
extension CollectionType { func randomSequence()->ProxySequence<Self>{ return ProxySequence(self, length: nil, random:true) } func repeatingSequence()->ProxySequence<Self>{ return ProxySequence(self,length: nil) } func randomSequence(length length:Int)->ProxySequence<Self>{ return ProxySequence(self, length: length, random:true) } func repeatingSequence(length length:Int)->ProxySequence<Self>{ return ProxySequence(self,length: length) } }
The four methods are quite simple (I could have used default parameters to reduce to just two, taking an optional length that defaulted to nil, but the semantics felt wrong. I either want to specify a length, or I don't and that should be obvious to someone reading the code).
So now anything that implements CollectionType will have these methods (The characters of a String, arrays, sets, dictionaries or even something you've implemented yourself).
I will include the implementation of ProxySequence and its associated GeneratorType here, but it's not really the focus of this post so read only if you care!
struct ProxySequence<C: CollectionType> : SequenceType{ let contents : C let length : Int? let random : Bool init(_ contents:C, length:Int?, random:Bool = false){ self.contents = contents self.length = length self.random = random } func generate() -> ProxySequenceGenerator<C> { return ProxySequenceGenerator<C>(contents, length: length, random: random) } } struct ProxySequenceGenerator<C : CollectionType> : GeneratorType{ let elements : [C.Generator.Element] let length : Int? var index = 0 let random : Bool init(_ contents:C, length:Int?, random:Bool){ self.length = length self.random = random var elements = Array<C.Generator.Element>() for elementIndex in contents.enumerate(){ elements.append(elementIndex.element) } self.elements = elements } mutating func next() -> C.Generator.Element? { if let length = length where index == length{ return nil } defer{ index++ } if random{ return elements[Int(arc4random()) % elements.count] } else { return elements[index % elements.count] } } }
Now you can do some cool stuff... like generate random characters from a String
for character in "hello-world".characters.randomSequence(length: 5){ character }
Or random primes from an array
for prime in [1,2,3,5,7].randomSequence(length: 5){ prime }
Or a repeating sequence of strings from the elements in a dictionary
for even in [0,2,4,6,8].repeatingSequence(length: 10){ even }
What a wonderfully powerful and clean capability!
You can download the full playground here.