Operators in APL
Learn APL operators - arithmetic, comparison, logical, scalar extension, reduction, and right-to-left evaluation with practical Docker-ready examples
In most languages, operators apply to single values, and you write loops to apply them across collections. APL turns this idea inside out: every primitive operator applies to entire arrays without any loop syntax at all. Adding two scalars and adding two thousand-element vectors use exactly the same +.
This tutorial walks through APL’s arithmetic, comparison, logical, and reduction operators, and introduces two ideas that make APL’s operator model distinctive: scalar extension (a scalar automatically pairs with every element of an array) and right-to-left evaluation with equal precedence (no PEMDAS — the parse is uniform).
APL distinguishes between functions (primitives like +, ×, ⌈) and operators in the strict sense (higher-order modifiers like / reduce and \ scan that take functions as arguments). In casual usage we call them all “operators,” but it’s worth knowing the difference: + is a function, while +/ is the reduce operator applied to the + function to produce a sum.
Arithmetic Operators
APL’s arithmetic primitives use traditional mathematical symbols, not the ASCII compromises most languages settle for. Multiplication is × (not *), division is ÷ (not /), and * is reserved for exponentiation — which matches how mathematicians write it.
Create a file named arithmetic.apl:
| |
A few things to notice here. 5 | 17 is the residue function: the right argument divided by the left, taking the remainder — so 17 mod 5 = 2. The order is the reverse of what C-family programmers expect. 2 * 10 is 2 raised to the 10th power, not 2 times 10. And - and ÷ are both monadic (one-argument) and dyadic (two-argument): - 7 negates, ÷ 4 produces the reciprocal 0.25.
Comparison and Logical Operators
Comparisons in APL return 1 for true and 0 for false — there is no separate boolean type. This means the result of a comparison is itself a numeric array you can feed back into arithmetic, which is the foundation of most “branchless” APL idioms.
Create a file named compare.apl:
| |
The last line is a classic APL idiom worth pausing on. 1 2 3 4 5 > 2 produces the boolean vector 0 0 1 1 1, and +/ sums it. In one line, with no if statement and no loop, we counted the elements greater than 2. This pattern — generate a boolean mask, then reduce — replaces an enormous amount of imperative code.
Reduction and Scan
The / operator (reduce) inserts a function between every element of an array. +/ 1 2 3 4 5 is conceptually 1 + 2 + 3 + 4 + 5. The \ operator (scan) does the same thing but keeps every intermediate result, giving you a running total, running max, or running anything-else for free.
Create a file named reductions.apl:
| |
⌈ and ⌊ are dyadic max and min — 3 ⌈ 7 is 7. Combined with reduce, ⌈/ finds the largest element and ⌊/ the smallest. The final expression shows the canonical APL “mean”: sum the vector, divide by its length (⍴ returns the shape, here 5).
Right-to-Left Evaluation
APL has no operator precedence. Every function — +, ×, *, your own user-defined ones — has identical priority, and expressions are parsed strictly right-to-left. This sounds disruptive but is actually liberating: you never need a precedence table, and pipelines read as a chain of transformations.
Create a file named precedence.apl:
| |
The pair 2 × 3 + 4 and its right-to-left interpretation 2 × (3 + 4) = 14 is the single most common stumbling block for APL newcomers. Once you accept the rule, it stops being surprising — and it means you write fewer parentheses than in almost any other language.
Running with Docker
| |
Expected Output
Output from arithmetic.apl:
5
6
15
5
1024
2
11 12 13 14 15
2 4 6 8 10
11 22 33
¯7
0.25
Output from compare.apl:
1
1
0
0 0 0 1 1
0 0 1 0 0
0
1
0
3
Output from reductions.apl:
15
120
9
1
1 3 6 10 15
1 2 6 24 120
3 3 4 4 5 9 9 9
6
Output from precedence.apl:
14
10
11
11
Note: APL uses the high minus sign ¯ (not the regular -) to prefix negative number values, distinguishing them from the subtraction function. So monadic negation of 7 produces ¯7 in the output.
Key Concepts
- Scalar extension: a scalar operand pairs with every element of an array operand, so
10 + 1 2 3is11 12 13. No broadcasting rules to memorize — every primitive does it. - Functions vs operators: primitives like
+and×are functions;/and\are true operators (higher-order) that take a function and produce a derived function like+/(sum) or+\(running sum). - Booleans are numbers: comparisons return
0or1integers you can immediately sum, multiply, or use as a mask.+/ vec > thresholdcounts;(vec > threshold) / vecfilters. - Right-to-left, equal precedence: there is no PEMDAS.
2 × 3 + 4is14. Use parentheses when you want non-default grouping; you’ll need them less often than you expect. - Monadic and dyadic forms: most primitive symbols mean two different things depending on whether they have one argument (
- 7→ negate) or two (10 - 7→ subtract). - Residue order is reversed:
A | BisB mod A, notA mod B. This is consistent with APL’s “left argument is the parameter, right is the data” convention but trips up newcomers. - High minus
¯is not subtraction:¯7is the literal value negative seven;- 7is the subtraction function applied monadically. They produce the same value but mean different things syntactically.
Running Today
All examples can be run using Docker:
docker pull juergensauermann/gnu-apl-1.8:latest
Comments
Loading comments...
Leave a Comment