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

Swift Operators

$
0
0

What would a program be without statements? A mish-mash of classes, namespaces, conditionals, loops, and namespaces signifying nothing.

Statements are what do the work of a program. They are the very execution of an executable.

If we were to take apart a statement—say 1 + 2—decomposing it into its constituent parts, we would find an operator and operands:

1+2
left operandoperatorright operand

Although expressions are flat, the compiler will construct a tree representation, or AST:

1 + 2 AST

Compound statements, like 1 + 2 + 3

(1 + 2)+3
left operandoperatorright operand

1 + 2 + 3 AST

Or, to take an even more complex statement, 1 + 2 * 3 % 4, the compiler would use operator precedence to resolve the expression into a single statement:

1+((2 * 3) % 4)
left operandoperatorright operand

1 + 2 * 3 % 4 AST

Operator precedence rules, similar to the ones you learned in primary school, provide a canonical ordering for any compound statement:

1 + 2 * 3 % 4
1 + ((2 * 3) % 4)
2 + (6 % 4)
2 + 2

However, consider the statement 5 - 2 + 3. Addition and subtraction have the same operator precedence, but evaluating the subtraction first (5 - 2) + 3 yields 6, whereas evaluating subtraction after addition, 5 - (2 + 3), yields 0. In code, arithmetic operators are left-associative, meaning that the left hand side will evaluate first ((5 - 2) + 3).

Operators can be unary and ternary as well. The ! prefix operator negates a logical value of the operand, whereas the ++ postfix operator increments the operand. The ?: ternary operator collapses an if-else expression, by evaluating the statement to the left of the ? in order to either execute the statement left of the : (statement is true) or right of : (statement is false).

Swift Operators

Swift includes a set of operators that should be familiar to C or Objective-C developers, with a few additions (notably, the range and nil coalescing operators):

Prefix

  • ++: Increment
  • --: Decrement
  • +: Unary plus
  • -: Unary minus
  • !: Logical NOT
  • ~: Bitwise NOT

Infix

Exponentiative {precedence 160}
<<Bitwise left shift
>>Bitwise right shift
Multiplicative { associativity left precedence 150 }
*Multiply
/Divide
%Remainder
&*Multiply, ignoring overflow
&/Divide, ignoring overflow
&%Remainder, ignoring overflow
&Bitwise AND
Additive { associativity left precedence 140 }
+Add
-Subtract
&+Add with overflow
&-Subtract with overflow
|Bitwise OR
^Bitwise XOR
Range { precedence 135 }
..<Half-open range
...Closed range
Cast { precedence 132 }
isType check
asType cast
Comparative { precedence 130 }
<Less than
<=Less than or equal
>Greater than
>=Greater than or equal
==Equal
!=Not equal
===Identical
!==Not identical
~=Pattern match
Conjunctive { associativity left precedence 120 }
&&Logical AND
Disjunctive { associativity left precedence 110 }
||Logical OR
Nil Coalescing { associativity right precedence 110 }
??Nil coalescing
Ternary Conditional { associativity right precedence 100 }
?:Ternary conditional
Assignment { associativity right precedence 90 }
=Assign
*=Multiply and assign
/=Divide and assign
%=Remainder and assign
+=Add and assign
-=Subtract and assign
<<=Left bit shift and assign
>>=Right bit shift and assign
&=Bitwise AND and assign
^=Bitwise XOR and assign
|=Bitwise OR and assign
&&=Logical AND and assign
||=Logical OR and assign

Postfix

  • ++: Increment
  • --: Decrement

Member Functions

In addition to the aforementioned standard operators, there are some de facto operators defined by the language:

  • .: Member Access
  • ?: Optional
  • !: Forced-Value
  • []: Subscript
  • []=: Subscript Assignment

Overloading

Swift has the ability to overload operators such that existing operators, like +, can be made to work with additional types.

To overload an operator, simply define a new function for the operator symbol, taking the appropriate number of arguments.

For example, to overload * to repeat a string a specified number of times:

func*(left:String,right:Int)->String{ifright<=0{return""}varresult=leftfor_in1..<right{result+=left}returnresult}
"a"*6// "aaaaaa"

This is, however, a controversial language feature.

Any C++ developer would be all too eager to regale you with horror stories of the non-deterministic havoc this can wreak.

Consider the following statement:

[1,2]+[3,4]// [1, 2, 3, 4]

By default, the + operator acts on two arrays by appending the right hand to the left hand.

However, when overridden thusly:

func+(left:[Double],right:[Double])->[Double]{varsum=[Double](count:left.count,repeatedValue:0.0)for(i,_)inenumerate(left){sum[i]=left[i]+right[i]}returnsum}

The result is now an array with the pairwise sums of each element, expressed as Double:

[1,2]+[3,4]// [4.0, 6.0]

And if the operator were also overloaded to work with Int types, with:

func+(left:[Int],right:[Int])->[Int]{varsum=[Int](count:left.count,repeatedValue:0)for(i,_)inenumerate(left){sum[i]=left[i]+right[i]}returnsum}

The result would then be an array of pairwise sums, expressed as Int.

[1,2]+[3,4]// [4, 6]

Herein lies the original sin of operator overloading: ambiguous semantics.

Having been limited to basic arithmetic operators across many years and programming languages, overloading of operators has become commonplace:

  • Computing Sum of Integers: 1 + 2 // 3
  • Computing Sum of Floats: 1.0 + 2.0 // 3.0
  • Appending to String: "a" + "b" // "ab"
  • Appending to Array: ["foo"] + ["bar"] // ["foo", "bar"]

It makes sense that + would work on numbers—that's just math. But think about it: why should adding two strings together concatenate them? 1 + 2 isn't 12 (except in Javascript). Is this really intuitive, or is it just familiar.

PHP uses . for string concatenation (which is objectively a terrible idea). Objective-C allows consecutive string literals to be appended with whitespace.

In the run-up to it's initial stable release, Swift still has some work to do in resolving ambiguities in operator semantics. Recent changes, such as the addition of the nil coalescing operator (??), and the decision for optionals not to conform to BooleanType (confusing in the case of Bool?) are encouraging, and demonstrate the need for us to collectively ask ourselves "does this really make sense?", and file radars appropriately.

I'm specifically concerned about the semantics of array operators, as demonstrated in the previous example. My 2 cents: arrays should forego the + and - operators in lieu of <<:

func<<<T>(inoutleft:[T],right:[T])->[T]{left.extend(right)returnleft}func<<<T>(inoutleft:[T],right:T)->[T]{left.append(right)returnleft}

Custom Operators

An even more controversial and exciting feature is the ability to define custom operators.

Consider the arithmetic operator found in many programming languages, but missing in swift is **, which raises the left hand number to the power of the right hand number (the ^ symbol, commonly used for superscripts, is already used to perform a bitwise XOR).

To add this operator in Swift, first declare the operator:

infixoperator**{associativityleftprecedence160}
  • infix specifies that it is a binary operator, taking a left and right hand argument
  • operator is a reserved word that must be preceded with either prefix, infix, or postfix
  • ** is the operator itself
  • associativity left means that operations are grouped from the left
  • precedence 160 means that it will evaluate with the same precedence as the exponential operators << and >> (left and right bitshift).
func ** (left: Double, right: Double) -> Double {
    return pow(left, right)
}
2**3// 8

When creating custom operators, make sure to also create the corresponding assignment operator, if appropriate:

infixoperator**={associativityrightprecedence90}func**=(inoutleft:Double,right:Double){left=left**right}

Note that left is inout, which makes sense, since assignment mutates the original value.

Custom Operators with Protocol and Method

Function definitions for the operators themselves should be extremely simple—a single LOC, really. For anything more complex, some additional setup is warranted.

Take, for example, a custom operator, =~, which returns whether the left hand side matches a regular expression on the right hand side:

protocolRegularExpressionMatchable{funcmatch(pattern:String,options:NSRegularExpressionOptions)->Bool}extensionString:RegularExpressionMatchable{funcmatch(pattern:String,options:NSRegularExpressionOptions=nil)->Bool{letregex=NSRegularExpression(pattern:pattern,options:nil,error:nil)returnregex.numberOfMatchesInString(self,options:nil,range:NSMakeRange(0,self.utf16Count))!=0}}infixoperator=~{associativityleftprecedence130}func=~<T:RegularExpressionMatchable>(left:T,right:String)->Bool{returnleft.match(right,options:nil)}
  • First, a RegularExpressionMatchableprotocol is declared, with a single method for matching regular expressions.
  • Next, an extension adding conformance to this protocol to String is declared, with a provided implementation of match, using NSRegularExpression.
  • Finally, the =~ operator is declared and implemented on a generic type conforming to RegularExpressionMatchable.

By doing this, a user has the option to use the match function instead of the operator. It also has the added benefit of greater flexibility in what options are passed into the method.

Actually, there's an even more clever way that this could be done. We'll look into that more next week.

This is all to say: a custom operator should only ever be provided as a convenience for an existing function.

Use of Mathematical Symbols

Custom operators can begin with one of the ASCII characters /, =, -, +, !, *, %, <, >, &, |, , or ~, or any of the Unicode characters in the "Math Symbols" character set.

This makes it possible to take the square root of a number with a single prefix operator (⌥v):

prefixoperator{}prefixfunc(number:Double)->Double{returnsqrt(number)}
4// 2

Or consider the ± operator, which can be used either as an infix or prefix to return a tuple with the sum and difference:

infixoperator±{associativityleftprecedence140}func±(left:Double,right:Double)->(Double,Double){return(left+right,left-right)}prefixoperator±{}prefixfunc±(value:Double)->(Double,Double){return0±value}
2±3// (-1, 5)±4// (-4, 4)

For more examples of functions using mathematical notation in Swift, check out Euler.

Custom operators are hard to type, and therefore hard to use. Exercise restraint when using custom operators with exotic symbols. After all, code should not be copy-pasted.


Operators in Swift are among the most interesting and indeed controversial features of this new language.

When overriding or defining new operators in your own code, make sure to follow these guidelines:

Guidelines for Swift Operators

  1. Don't create an operator unless its meaning is obvious and undisputed. Seek out any potential conflicts to ensure semantic consistency.
  2. Custom operators should only be provided as a convenience. Complex functionality should always be implemented in a function, preferably one specified as a generic using a custom protocol.
  3. Pay attention to the precedence and associativity of custom operators. Find the closest existing class of operators and use the appropriate precedence value.
  4. If it makes sense, be sure to implement assignment shorthand for a custom operator (e.g. += for +).

Viewing all articles
Browse latest Browse all 382

Trending Articles