Operators, Operators, Operators: Introducing Precedence

Swift 3.0 does operators a new way. Instead of assigning a direct precedence, use precedence groups. The built in groups are:

BitwiseShiftPrecedence > MultiplicationPrecedence (left) > AdditionPrecedence (left) > RangeFormationPrecedence > CastingPrecedence > NilCoalescingPrecedence > ComparisonPrecedence > LogicalConjunctionPrecedence (left) > LogicalDisjunctionPrecedence (left) > TernaryPrecedence (right) > AssignmentPrecedence (right, assignment) > FunctionArrowPrecedence (right) > [nothing]

Plus there’s a DefaultPrecedence, which is higher than TernaryPrecedence.

Each precedence group may define a relationship, which when used in-module must always be higherThan another in-module precedence. You can use lowerThan to describe how a precedence relates to a precedence imported from another module. Relationships are optional. FunctionalArrowPrecedence does not use any relationships.

Each precedence group may define associativity, which is either left or right. Again, this is optional.

Finally, there’s a special notation for operators that perform assignments:

precedencegroup AssignmentPrecedence {
    associativity: right
    assignment: true
    higherThan: FunctionArrowPrecedence
}

You can easily create your own precedence groups:

/// High precedence
precedencegroup HighPrecedence { 
    higherThan: BitwiseShiftPrecedence 
}
/// Very high precedence precedencegroup VeryHighPrecedence {
    higherThan: HighPrecedence
}

For example, consider the following code:

let x = Optional(42)
let y = 5 + x ?? 2 // will not compile

This will not compile unless you parenthesize (x ?? 2) because the compiler cannot distinguish between (5 + x) ?? 2 and 5 + (x ?? 2). Although you might instinctively want to raise the precedence level for the nil-coalescing ?? operator, how would you expect the compiler to handle the following example, provided by Jacob Bandes-Storch:

let nextIndex = foundIndex ?? lastIndex + 1

You can instead work around this by creating a custom high-precedence nil-coalescing operator.

/// Create a new high-precedence operator
infix operator .?? : HighPrecedence

You conform the new operator definition to a precedence group. You do not define the operator’s characteristics in braces. Leave a space before the colon so it is clear that it is not part of the operator.

/// A high-precedence coalescing nil,
/// ensuring that coalescing occurs to
/// produce a value before applying value
/// to other operations
/// - Parameter lhs: an optional value
/// - Parameter rhs: a non-optional fallback value
/// - Returns: The coalesced result of `lhs ?? rhs`
///   performed at high precedence
/// ```
/// let x = Optional(42)
/// let y = 5 + x ?? 2 // won't compile
/// let y = 5 + x .?? 2 // will compile
/// ```
public func .??(lhs: T?, rhs: T) -> T {
    return lhs ?? rhs
}

This produces a variation on ?? that is guaranteed to be executed before any multiplication, addition, or bit-shifting operations.

Precedence groups don’t actually have to mention precedence. You can create operators that refer only to associativity:

precedencegroup LeftAssociativePrecedence {
    associativity: left
}

Unfortunately, the remains a slight bug in the compiler that does not allow you to declare:

postfix operator +? : LeftAssociativePrecedence

but you can still declare

postfix operator +?

Which allows you to convert any item to an optional without having to wrap it directly into an Optional enumeration:

/// Adds optionality by wrapping the
/// rhs value in an Optional enumeration
///
/// - Parameter lhs: a value
/// - Returns: The value wrapped as an Optional
/// ```
/// let x = 42+? // x is Int?
/// ```
public postfix func +?(lhs: T) -> T? { return Optional(lhs) }

This is just a taste of what you can do with Swift 3 optionals. More to follow.

Comments are closed.