Quantcast
Channel: NSHipster
Viewing all articles
Browse latest Browse all 382

Swift Comparison Protocols

$
0
0

Objective-C required us to wax philosophic about the nature of equality and identity. To the relief of any developer less inclined towards handwavy treatises, this is not as much the case for Swift.

In Swift, Equatable is a fundamental type, from which Comparable and Hashable are both derived. Together, these protocols form the central point of comparison throughout the language.


Equatable

Values of the Equatable type can be evaluated for equality and inequality. Declaring a type as equatable bestows several useful abilities, notably the ability values of that type to be found in a containing Array.

For a type to be Equatable, there must exist an implementation of the == operator function, which accepts a matching type:

func==(lhs:Self,rhs:Self)->Bool

For value types, equality is determined by evaluating the equality of each component property. As an example, consider a Complex type, which takes a generic type T, which conforms to SignedNumberType:

SignedNumberType is a convenient choice for a generic number type, as it inherits from both Comparable (and thus Equatable, as described in the section) and IntegerLiteralConvertible, which Int, Double, and Float all conform to.

structComplex<T:SignedNumberType>{letreal:Tletimaginary:T}

Since a complex number is comprised of a real and imaginary component, two complex numbers are equal if and only if their respective real and imaginary components are equal:

extensionComplex:Equatable{}// MARK: Equatablefunc==<T>(lhs:Complex<T>,rhs:Complex<T>)->Bool{returnlhs.real==rhs.real&&lhs.imaginary==rhs.imaginary}

The result:

leta=Complex<Double>(real:1.0,imaginary:2.0)letb=Complex<Double>(real:1.0,imaginary:2.0)a==b// truea!=b// false

As described in the article about Swift Default Protocol Implementations, an implementation of != is automatically derived from the provided == operator by the standard library.

For reference types, the equality becomes conflated with identity. It makes sense that two Name structs with the same values would be equal, but two Person objects can have the same name, but be different people.

For Objective-C-compatible object types, the == operator is already provided from the isEqual: method:

classObjCObject:NSObject{}ObjCObject()==ObjCObject()// false

For Swift reference types, equality can be evaluated as an identity check on an ObjectIdentifier constructed with an instance of that type:

classObject:Equatable{}// MARK: Equatablefunc==(lhs:Object,rhs:Object)->Bool{returnObjectIdentifier(lhs)==ObjectIdentifier(rhs)}Object()==Object()// false

Comparable

Building on Equatable, the Comparable protocol allows for more specific inequality, distinguishing cases where the left hand value is greater than or less than the right hand value.

Types conforming to the Comparable protocol provide the following operators:

func<=(lhs:Self,rhs:Self)->Boolfunc>(lhs:Self,rhs:Self)->Boolfunc>=(lhs:Self,rhs:Self)->Bool

What's interesting about this list, however, is not so much what is included, but rather what's missing.

The first and perhaps most noticeable omission is ==, since >= is a logical disjunction of > and == comparisons. As a way of reconciling this, Comparable inherits from Equatable, which provides ==.

The second omission is a bit more subtle, and is actually the key to understanding what's going on here: <. What happened to the "less than" operator? It's defined by the _Comparable protocol. Why is this significant? As described in the article about Swift Default Protocol Implementations, the Swift Standard Library provides a default implementation of the Comparable protocol based entirely on the existential type _Comparable. This is actually really clever. Since the implementations of all of the comparison functions can be derived from just < and ==, all of that functionality is made available automatically through type inference.

Contrast this with, for example, how Ruby derives equality and comparison operators from a single operator, <=> (a.k.a the "UFO operator"). Here's how this could be implemented in Swift.

As a more complex example, consider a CSSSelector struct, which implements cascade ordering of selectors:

importFoundationstructCSSSelector{letselector:StringstructSpecificity{letid:Intlet`class`:Intletelement:Intinit(_components:[String]){var(id,`class`,element)=(0,0,0)fortokenincomponents{iftoken.hasPrefix("#"){id++}elseiftoken.hasPrefix("."){`class`++}else{element++}}self.id=idself.`class`=`class`self.element=element}}letspecificity:Specificityinit(_string:String){self.selector=string// Naïve tokenization, ignoring operators, pseudo-selectors, and `style=`.letcomponents:[String]=self.selector.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceCharacterSet())self.specificity=Specificity(components)}}

Where as CSS selectors are evaluated by specificity rank and order, two selectors are only really equal if they resolve to the same elements:

extensionCSSSelector:Equatable{}// MARK: Equatablefunc==(lhs:CSSSelector,rhs:CSSSelector)->Bool{// Naïve equality that uses string comparison rather than resolving equivalent selectorsreturnlhs.selector==rhs.selector}

Instead, selectors are actually compared in terms of their specificity:

extensionCSSSelector.Specificity:Comparable{}// MARK: Comparablefunc<(lhs:CSSSelector.Specificity,rhs:CSSSelector.Specificity)->Bool{returnlhs.id<rhs.id||lhs.`class`<rhs.`class`||lhs.element<rhs.element}// MARK: Equatablefunc==(lhs:CSSSelector.Specificity,rhs:CSSSelector.Specificity)->Bool{returnlhs.id==rhs.id&&lhs.`class`==rhs.`class`&&lhs.element==rhs.element}

Bringing everything together:

For clarity, assume CSSSelectorconforms to StringLiteralConvertible.

leta:CSSSelector="#logo"letb:CSSSelector="html body #logo"letc:CSSSelector="body div #logo"letd:CSSSelector=".container #logo"b==c// falseb.specificity==c.specificity// truec.specificity<a.specificity// falsed.specificity>c.specificity// true

Hashable

Another important protocol derived from Equatable is Hashable.

Only Hashable types can be stored as the key of a Swift Dictionary:

structDictionary<Key:Hashable,Value>:CollectionType,DictionaryLiteralConvertible{...}

For a type to conform to Hashable,it must provide a getter for the hashValue property.

protocolHashable:Equatable{/// Returns the hash value.  The hash value is not guaranteed to be stable/// across different invocations of the same program.  Do not persist the hash/// value across program runs.////// The value of `hashValue` property must be consistent with the equality/// comparison: if two values compare equal, they must have equal hash/// values.varhashValue:Int{get}}

Determining the optimal hashing value is way outside the scope of this article. Fortunately, most values can derive an adequate hash value from an XOR of the hash values of its component properties.

The following built-in Swift types implement hashValue:

  • Double
  • Float, Float80
  • Int, Int8, Int16, Int32, Int64
  • UInt, UInt8, UInt16, UInt32, UInt64
  • String
  • UnicodeScalar
  • ObjectIdentifier

Based on this, here's how a struct representing Binomial Nomenclature in Biological Taxonomy:

structBinomen{letgenus:Stringletspecies:String}// MARK: HashableextensionBinomen:Hashable{varhashValue:Int{returngenus.hashValue^species.hashValue}}// MARK: Equatablefunc==(lhs:Binomen,rhs:Binomen)->Bool{returnlhs.genus==rhs.genus&&rhs.species==rhs.species}

Being able to hash this type makes it possible to key common name to the "Latin name":

varcommonNames:[Binomen:String]=[:]commonNames[Binomen(genus:"Canis",species:"lupis")]="Grey Wolf"commonNames[Binomen(genus:"Canis",species:"rufus")]="Red Wolf"commonNames[Binomen(genus:"Canis",species:"latrans")]="Coyote"commonNames[Binomen(genus:"Canis",species:"aureus")]="Golden Jackal"

Viewing all articles
Browse latest Browse all 382

Trending Articles