Functions in APL
Define and compose functions in APL using dfns, traditional functions, operators, recursion, and tacit (point-free) trains with Docker-ready examples
In most languages, a function is a named block of statements you call with parentheses. APL takes a different view. Because APL is array-oriented and functional, a function is simply a rule that transforms array arguments into an array result — and crucially, that rule applies to whole arrays at once, with no loops required.
APL goes further than most languages in how many ways it lets you define a function. You can write a compact dfn (dynamic function) using the special argument symbols ⍵ and ⍺, a classic tradfn (traditional function) using the ∇ del editor, a tacit (point-free) function that names no arguments at all, or a higher-order operator that takes other functions as input. Each style suits a different situation.
This tutorial walks through all of them. We will define monadic and dyadic functions, handle local scope, write recursion with guards, and finally reach APL’s signature feature: composing existing functions into new ones without ever mentioning their arguments. Building on the array thinking from earlier tutorials, you’ll see why APL programmers say they “design functions” rather than “write procedures.”
Dynamic Functions (dfns)
A dfn is an anonymous function body written in curly braces and assigned to a name. Inside the braces, ⍵ (omega) refers to the right argument and ⍺ (alpha) to the left argument. A dfn that uses only ⍵ is monadic (one argument); one that uses ⍺ as well is dyadic (two arguments).
Create a file named functions.apl:
| |
Square 5 multiplies 5 by itself. Double 1 2 3 4 5 shows the key APL idea: the same function that doubles a scalar doubles an entire vector element-by-element. 3 Hypot 4 computes the length of a right triangle’s hypotenuse: √(3² + 4²). Remember that APL evaluates right-to-left, so ((⍺*2)+⍵*2)*0.5 raises the sum of squares to the 0.5 power last.
Note that the assignment lines (Square ← ...) produce no output — assignment is silent. Only the lines that evaluate a result, like Square 5, display anything.
Recursion and Guards
APL replaces most loops with array operations, but recursion is still the natural tool for genuinely recursive problems. A dfn can call itself by name, and a guard — written condition: result — provides the base case. If the guard’s condition is true, the dfn returns that result immediately; otherwise execution continues past the ⋄ statement separator.
Create a file named recursion.apl:
| |
Fact 5 expands to 5 × 4 × 3 × 2 × 1, giving 120. Fact 0 hits the guard ⍵≤1 and returns 1. The last line uses the each operator ¨ to apply Fact to every element of 1 2 3 4 5 independently, producing a vector of factorials. (APL also has a built-in factorial, !5, but writing it by hand shows how guards and self-reference work.)
Traditional Functions (tradfns)
The older tradfn style uses the ∇ (del) symbol to open and close a multi-line definition. The first line is a header that names the function, its arguments, and its result variable. Tradfns are useful when a function needs several statements or explicit local variables.
Local variables are declared in the header after a semicolon. Any name listed there is local to the function — it does not leak into or overwrite the global environment. This is APL’s scoping mechanism: unlisted names are global, listed names are local.
Create a file named tradfn.apl:
| |
Greet 'APL' concatenates character vectors with , to build Hello, APL!. Average sums its argument with the reduce +/, counts the elements with tally ≢, and divides — total and count exist only while Average runs. The sum of 4 8 15 16 23 42 is 108 over 6 elements, giving 18.
Operators and Tacit Functions
This is where APL becomes unusual. An operator is a higher-order function: it takes one or more functions as operands and produces a new function. You have already used built-in operators like reduce (/) and each (¨). You can also define your own, and you can compose functions into tacit (point-free) functions that name no arguments at all.
Create a file named higher_order.apl:
| |
Double¨ 1 2 3 applies Double to each element. Twice is a defined operator: it references ⍺⍺, its function operand, so Double Twice 5 means “apply Double twice to 5” — Double Double 5 = Double 10 = 20.
The classic point-free way to write this is a fork, a three-function train: Mean ← +/ ÷ ≢. Applied to an argument ⍵, a fork (f g h) ⍵ computes (f ⍵) g (h ⍵) — here (+/⍵) ÷ (≢⍵), the sum divided by the count — and it never mentions its argument at all. Trains are a hallmark of modern APL and one of the language’s deepest ideas, directly influencing functional programming. They were added to Dyalog APL in 2014, but GNU APL (the APL2-style interpreter we run in Docker) does not support them, so above we express the same composition with the dfn {(+/⍵) ÷ ≢⍵}, which produces the identical result.
The final two lines show that even the familiar +/ and ×/ are operator expressions: the reduce operator / takes the functions + and × to build “sum” and “product.”
Running with Docker
| |
Expected Output
Running functions.apl:
25
2 4 6 8 10
5
Running recursion.apl:
120
1
1 2 6 24 120
Running tradfn.apl:
Hello, APL!
18
Running higher_order.apl:
2 4 6
20
25
15
120
Key Concepts
⍵and⍺are the arguments — a dfn references the right argument as⍵and the optional left argument as⍺; no parameter list is needed.- Functions apply to whole arrays — the same function that transforms a scalar transforms a vector or matrix element-by-element, eliminating most explicit loops.
- Two definition styles — compact dfns (
{...}) for short rules, and tradfns (∇ ... ∇) when you need multiple statements or declared local variables. - Scope is explicit in tradfns — names listed after
;in the header are local; everything else is global. Dfns localize their names automatically. - Guards replace if/else for base cases —
condition: result ⋄ ...lets a recursive dfn return early, as in the factorial example. - Operators are higher-order functions — built-in operators like reduce (
/) and each (¨), plus your own (⍺⍺,⍵⍵), take functions as operands and return new functions. - Tacit functions name no arguments — trains like
+/ ÷ ≢build new functions purely by composing existing ones, the essence of point-free programming (a modern-APL feature in Dyalog; GNU APL approximates it with a dfn). - Right-to-left evaluation — function expressions, including the bodies of your definitions, always evaluate from right to left with equal precedence.
Running Today
All examples can be run using Docker:
docker pull juergensauermann/gnu-apl-1.8:latest
Comments
Loading comments...
Leave a Comment