Arity
Operators are the special symbols that we use to compactly notate calculations. The data to which operators are applied are operands, which may be literals, variables, or function calls—any expression really. An operator and its operands form an operation.
How an operation is formed and evaluated depends on these four properties:
- arity, or the number of operands
- fixity, or the placement of the operator relative to its operands
- precedence
- associativity
Let's examine each of these in turn.
When we examined functions, we learned that arity is the number of parameters that a function expects. It is also used to describe the number of operands that an operator expects. Most languages have a few unary operators, many binary operators, and a single ternary operator.
A unary operator has only a single operand. The only unary operator in Haskell is the arithmetic negation operator -
. Other languages provide more, including the logical negation operators !
and not
, the bitwise negation operator ~
, the sizeof
operator, the address-of operator &
, the dereference operator *
, and the increment and decrement operators ++
and --
.
Some languages provide the unary +
operator to complement the -
operator. In many circumstances, this operator performs an identity operation, yielding the value of its operand. However, it usually has extra semantic meaning: it converts the type of its operand. In C, +x
converts x
of any numeric type to an int
. In JavaScript, unary +
can be used to turn non-numbers into numbers:
console.log(+true); // prints 1
console.log(+false); // prints 0
console.log('53' + 2); // prints 532
console.log(+'53' + 2); // prints 55
console.log(+'76ers'); // prints NaN
console.log(+true); // prints 1 console.log(+false); // prints 0 console.log('53' + 2); // prints 532 console.log(+'53' + 2); // prints 55 console.log(+'76ers'); // prints NaN
Where Haskell lacks an operator, it provides a function instead. For example, Haskell has no logical negation operator, but it does have a function named not
. It also does not have unary +
, but it does have an id
function that accepts a single parameter:
id 99 -- yields 99
id "snurb" -- yields "snurb"
id 99 -- yields 99 id "snurb" -- yields "snurb"
Imagining a use for the id
function is hard. But one may arise as we use higher-order functions.
A binary operator has two operands. Typical binary operators include the arithmetic operators +
, -
, *
, /
, and %
; the relational operators <
, <=
, >
, and >=
; the equality operators ==
, !=
, /=
, and <>
; the logical operators &&
, ||
, and
, and or
; the subscript operator []
; the bitwise operators <<
, >>
, and ^
; the member operators ->
and .
; and the assignment operators =
, +=
, *=
, and so on.
Haskell has a unique take on some of these binary operators. For instance, there is no %
operator defined in the standard library, which is called the Prelude. Instead it has the mod
function. The not-equal operator is /=
, which is closer to the ≠
sign of mathematical notation.
Additionally, it doesn't have any of the compound assignment operators like +=
. In C, +=
and its cousins are really two operations in one:
x += 1; // effectively expands to x = x + 1
x += 1; // effectively expands to x = x + 1
The left operand is first treated as an rvalue to perform the +
operation. Then it is treated as an lvalue to perform the assignment. Haskell doesn't allow mutation, and these compound operators are therefore omitted from the language.
There are no bitwise operators, but there are named bitwise functions:
> import Data.Bits
> shiftL 4 2
16
> shiftR 100 1
50
> import Data.Bits > shiftL 4 2 16 > shiftR 100 1 50
The subscript operator is !!
rather than []
, and it is used like this:
"#FC621D" !! 6 -- yields 'D'
"#FC621D" !! 6 -- yields 'D'
Recall that lists in Haskell are linked lists. The whole chain must be traversed to reach the given index. The double exclamation offers a reminder to use !!
sparingly.
Ruby provides the rocketship operator <=>
, which is used to order its operands:
91 <=> 93 # yields -1, negative for a < b
91 <=> 91 # yields 0, 0 for a == b
93 <=> 91 # yields 1, positive for a > b
91 <=> 93 # yields -1, negative for a < b 91 <=> 91 # yields 0, 0 for a == b 93 <=> 91 # yields 1, positive for a > b
Java provides similar behavior to the rocketship operator through its compareTo
methods:
Integer x = 91;
Integer y = 93;
x.compareTo(y) // yields value < 0
x.compareTo(x) // yields 0
y.compareTo(x) // yields value > 0
Integer x = 91; Integer y = 93; x.compareTo(y) // yields value < 0 x.compareTo(x) // yields 0 y.compareTo(x) // yields value > 0
Haskell provides a compare
function rather than an operator:
compare 91 93 -- yields LT
compare 91 91 -- yields EQ
compare 93 91 -- yields GT
compare 91 93 -- yields LT compare 91 91 -- yields EQ compare 93 91 -- yields GT
A ternary operator has three operands. Most languages have just one ternary operator: the conditional expression. In the C family of languages and in Ruby, it has this syntax:
legClothes = temperature > 25 ? "shorts" : "pants";
legClothes = temperature > 25 ? "shorts" : "pants";
This operator branches like an if statement, but instead of achieving some side effect, it chooses between its then and else values. Haskell's conditional expression has this form:
legClothes = if temperature > 25 then "shorts" else "pants"
legClothes = if temperature > 25 then "shorts" else "pants"
Haskell programs are comprised of expressions and not statements. It therefore has no conditional statement. If there's an if
, it's a value-producing expression. We may write it with linebreaks, but it's still an expression:
legClothes =
if temperature > 25
then "shorts"
else "pants"
legClothes = if temperature > 25 then "shorts" else "pants"
In imperative languages the else branch of a conditional statement may be omitted if there's nothing to do when the condition is false. That's not legal in Haskell's conditional expression. A value must always be produced, so the else branch must be present. Additionally, the branches must produce values of the same type.
Sometimes mathematicians and novice programmers long for a ternary interval operator so that they can write code like 0 <= x <= 100
. Alas, most mainstream languages treat this as two binary operations. The first, 0 <= x
, yields a boolean value, and the second confusingly compares the boolean to 100, which probably isn't the behavior we want and may not typecheck. Python is a notable exception. Its parser recognizes chains like 0 <= x <= 100
and evaluates them according to our mathematical expectations.