Fixity

Dear Computer

Chapter 6: Expressions

Fixity

Operators are placed near their operands in three common ways. Prefix operators appear before their operands, postfix operators appear after their operands, and infix operators appear between their operands, as shown here for a hypothetical operator @ and operands x and y:

@x      <- prefix
x@      <- postfix
x @ y   <- infix
@x      <- prefix
x@      <- postfix
x @ y   <- infix

An operator's placement is also called its fixity.

Most binary and ternary operators are infix operators, and most unary operators are prefix operators. However, the increment operator ++ and decrement operator -- have both prefix and postfix forms in languages with mutability. They both increment and yield a value, but they differ in the order in which these happen. Their placement determines the order:

JavaScript
let x = 1;
console.log(x++);  // post-increment: yield 1, assign x = 2

let y = 1;
console.log(++y);  // pre-increment: assign y = 2, yield 2
let x = 1;
console.log(x++);  // post-increment: yield 1, assign x = 2

let y = 1;
console.log(++y);  // pre-increment: assign y = 2, yield 2

The prefix form ++y puts the ++ first, reflecting that the increment has priority. The value of the expression is the incremented number. The postfix form x++ puts the x first, reflecting that the unincremented value of x has priority. The value of the expression is the unincremented value of x. These operators yield different values according to their fixity. If we never embed these operations in a larger expression that consumes the yielded value, their difference doesn't really matter.

The increment and decrement operators are not present in Haskell. Nor in Ruby or Python. These operators are often used to update indices in count-controlled for loops in the C family of languages. Ruby and Python do not have these loops, favoring range-based and for-each loops, so the operators are less useful. The more general binary operators += and -= are available in Python and Ruby.

Haskell's binary operators can be either prefix or infix. In prefix form, they must be parenthesized:

Haskell
5 + 6     -- yields 11
(+) 5 6   -- yields 11
5 + 6     -- yields 11
(+) 5 6   -- yields 11

You may be wondering why we would ever want the prefix form. That is a reasonable question that we will address when we discuss a feature called partial function application in a later chapter.

Consider also the div function, which is used to perform integer division. Usually, function names appear in a prefix position, but Haskell lets us treat a function as an infix operator using backticks:

Haskell
div 16 5      -- yields 3
16 `div` 5    -- yields 3
div 16 5      -- yields 3
16 `div` 5    -- yields 3

The flexible fixity of operators and functions hints at a subtle truth of Haskell: operators and functions are implemented with the same machinery. The only difference between them is that an operator defaults to having infix placement, while a function defaults to having prefix placement.

The Haskell lexer decides what is an operator and what is a function by examining the characters in their tokens. A function is made of alphanumeric characters and an operator is made of punctuation.

The Lisp family of languages uses prefix operators exclusively. One benefit of this form is that operators that are normally binary naturally become variadic:

Racket
(+ 1 2)       ; yields 3
(+ 1 2 3 4)   ; yields 10
(+ 1 2)       ; yields 3
(+ 1 2 3 4)   ; yields 10
← ArityPrecedence →