Intermediate

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
⍝ A monadic dfn: ⍵ is the right argument
Square  {×}
Square 5

⍝ Functions apply to whole arrays automatically — no loop needed
Double  {2 ×}
Double 1 2 3 4 5

⍝ A dyadic dfn: ⍺ is the left argument, ⍵ the right
Hypot  {((⍺*2)+*2)*0.5}
3 Hypot 4

)OFF

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:

1
2
3
4
5
6
7
8
9
⍝ Factorial: the guard ⍵≤1 supplies the base case
Fact  {1: 1 ⋄ ⍵ × Fact ⍵-1}
Fact 5
Fact 0

⍝ Recursion still cooperates with array thinking via each (¨)
Fact¨ 1 2 3 4 5

)OFF

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
⍝ result ← Greet name : a monadic tradfn returning a string
∇ result  Greet name
result  'Hello, ', name, '!'

⍝ Locals 'total' and 'count' are declared after the semicolons
∇ avg  Average nums ; total ; count
total  +/nums
count  nums
avg  total ÷ count

Greet 'APL'
Average 4 8 15 16 23 42

)OFF

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
⍝ Each (¨) is a built-in operator: apply a function per element
Double  {2 ×}
Double¨ 1 2 3

⍝ A user-defined operator: ⍺⍺ is the function operand, applied twice
Twice  {⍺⍺ ⍺⍺ ⍵}
Double Twice 5

⍝ Point-free idea: mean = sum ÷ count. True trains (+/ ÷ ≢) are a
⍝ Dyalog APL feature; GNU APL is APL2-style and has no trains, so we
⍝ express the same composition inside a dfn here.
Mean  {(+/⍵) ÷ }
Mean 10 20 30 40

⍝ Reduce (/) is itself a higher-order operator over a function
+/ 1 2 3 4 5
×/ 1 2 3 4 5

)OFF

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

1
2
3
4
5
6
7
8
# Pull the GNU APL image
docker pull juergensauermann/gnu-apl-1.8

# Run each example (the -f flag reads expressions from the file)
docker run --rm -v $(pwd):/app -w /app juergensauermann/gnu-apl-1.8 apl --silent --noColor --noCIN -f functions.apl
docker run --rm -v $(pwd):/app -w /app juergensauermann/gnu-apl-1.8 apl --silent --noColor --noCIN -f recursion.apl
docker run --rm -v $(pwd):/app -w /app juergensauermann/gnu-apl-1.8 apl --silent --noColor --noCIN -f tradfn.apl
docker run --rm -v $(pwd):/app -w /app juergensauermann/gnu-apl-1.8 apl --silent --noColor --noCIN -f higher_order.apl

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 casescondition: 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
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining