Exponentiation operator and precedence group

Unary operator precedence

In languages based on or inspired by C, unary (prefix and postfix) operators have higher precedence than all binary (infix) operators.

This widely adopted design is also observed in Swift: Infix operators are assigned to varying precedence groups, and precedence groups can be ordered higher-than, ordered lower-than, or remain unordered relative to one another. However, all of these precedence groups have lower precedence than prefix operators (which in turn have lower precedence than postfix operators).

Unfortunately, this design clashes with mathematical custom regarding the exponentiation operator.

In mathematical notation, −2² is equivalent to −(2²) = −4. To keep with this custom, any exponentiation operator would require higher precedence than the prefix operator -. Like some but not all programming languages, Python (for example) implements exactly this design:

#  Python
print(-2 ** 2) #  Prints "-4"

Besides that it is not possible in Swift to assign an infix operator to a higher precedence than that of a prefix operator, users have commented that it is not desirable to do so in the first place:

While a subset of users will expect an exponentiation operator to observe mathematical custom, others will expect that -2 ** 2 will be parsed unambiguously as (-2) ** 2 based on the behavior of existing infix operators both in Swift and in other "C family" languages.

These two expectations are diametrically opposing: no design which accepts the combination of prefix - and ** without parentheses (whether or not ** is surrounded by spaces) will behave intuitively to all users.

Fortunately, there is precedent for a solution reconciling these competing expectations in a "C family" language. In JavaScript, prefix - and ** are explicitly unordered relative to each other, requiring the use of parentheses to clarify user intent:

// JavaScript
print(-2 ** 2)
// Illegal expression.
// Wrap left hand side or entire exponentiation in parentheses.

The same feature would align well with the current design and direction of Swift: We explicitly allow for infix operator precedence groups to be unordered relative to each other, and for operators within a precedence group to be non-associative, precisely with the intent that combinations of such operators should require parentheses to clarify the order of operations.

Removing binary operator precedence relative to unary operators

Recall that all binary operator precedence groups have lower precedence than all unary operators, even as binary operator precedence groups can be unordered relative to each other.

Unary operators, taken as a notional precedence group, therefore sit atop every precedence hierarchy however bifurcated. If the name of this precedence group were utterable, the current design for precedence groups is equivalent to having an implied lowerThan: _UnaryOperatorPrecedence for every precedence group:

// ...
precedencegroup AdditionPrecedence {
  higherThan: RangeFormationPrecedence
  lowerThan: _UnaryOperatorPrecedence // Implied
}
precedencegroup MultiplicationPrecedence {
  higherThan: AdditionPrecedence
  lowerThan: _UnaryOperatorPrecedence // Implied
}
precedencegroup BitwiseShiftPrecedence {
  higherThan: MultiplicationPrecedence
  lowerThan: _UnaryOperatorPrecedence // Implied
}

There are a number of (possibly elaborate) changes to Swift's design for operators and precedence groups to allow for an "apex" precedence group such as the proposed ExponentiationPrecedence to be unordered relative to unary operators. However, conceptually, it suffices merely to suppress the implied lowerThan.

The underlying implementation would differ from the existing check for unordered precedence groups because unary operators are not involved in that checking, but it would not require an elaborate overhaul within the compiler.

One possible expression of this concept would be to add a declaration modifier explicitly for such "apex" precedence groups:

@apex precedencegroup ExponentiationPrecedence {
  higherThan: MultiplicationPrecedence
  // Not implied because we're declaring an 'apex' group--
  // lowerThan: _UnaryOperatorPrecedence
}

Apex precedence groups would have the following behaviors and restrictions:

  • An apex precedence group has no explicit or implied lower-than precedence relationships
  • Another precedence group cannot be declared higher-than an apex precedence group

Besides the proposed ExponentiationPrecedence, the unutterable _UnaryOperatorPrecedence would be conceptually an apex precedence group; this would be merely another way of describing the "C family" design that no binary operator can have higher precedence.

It is conceivable that users will want to create other custom operators, unordered relative to all standard library operators or bifurcating the standard library hierarchy, which they will want to make "apex" operators.

2 Likes