Beginner

Control Flow in APL

Learn how APL handles control flow through array operations, conditional expressions, and the rare cases where loops are still useful

Control flow in APL looks very different from what you’ve seen in C-family or Python-style languages. Because APL is array-oriented, most decisions and iterations that other languages spell out with if, for, and while simply disappear — a single array expression handles every element at once. When you genuinely need branching, APL offers several distinct techniques: arithmetic conditionals, selection by boolean masks, the :If/:While control structures from GNU APL, and the classic (goto) branch arrow.

This tutorial walks through how to think in arrays first, then shows the explicit conditional and looping constructs for the cases where array thinking isn’t a clean fit. The goal isn’t to translate for (i=0; i<n; i++) into APL — it’s to recognize when you don’t need that loop at all.

Conditionals as Arithmetic

APL’s comparison operators (<, , =, , >, ) return 1 for true and 0 for false. Because booleans are just numbers, you can use them directly in arithmetic — this is how APL replaces many if/else chains. The pattern (cond × a) + (~cond) × b selects a when cond is true and b when it is false, element by element.

Create a file named conditional_arith.apl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
⍝ Conditionals expressed as arithmetic on boolean values
nums  3 ¯7 12 0 ¯4 8

⍝ Absolute value without an if: sign × number
'Original:'
nums
'Absolute:'
(× nums) × nums

⍝ Classify: 1 for positive, 0 for zero, ¯1 for negative
'Sign of each element:'
× nums

⍝ Choose between two values without branching:
⍝   result is "BIG" if n>5, else "small" — for each n
big  nums > 5
'Mask of "big" values:'
big

⍝ Replace negatives with zero (clamp): mask × value
'Negatives clamped to zero:'
(nums  0) × nums
)OFF

The expression (nums ≥ 0) × nums is APL’s version of max(0, n) applied to every element. There is no loop, no if, no temporary array — the boolean mask nums ≥ 0 multiplies element-wise against the values, and 0 annihilates the negatives.

Selection with Compress and Where

When you only want the elements that satisfy a condition — APL’s equivalent of filter — use the compress operator /. A boolean vector on the left of / selects the matching elements on the right.

Create a file named selection.apl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
⍝ Filtering arrays without loops
scores  72 88 45 91 67 53 99 80

⍝ Boolean mask of passing scores (>= 70)
passing  scores  70
'Mask:'
passing

⍝ Compress: keep only elements where mask is 1
'Passing scores:'
passing / scores

⍝ Count how many pass — just sum the boolean mask
'Number passing:'
+/ passing

⍝ Indices of passing scores (Where, monadic ⍸)
'Positions of passing scores:'
 passing

⍝ Average of passing scores only
'Average passing score:'
(+/ passing / scores) ÷ +/ passing
)OFF

The phrase +/ passing reduces the boolean mask with addition — counting trues. This is the array-language idiom that replaces a for loop with an incrementing counter inside an if.

Explicit If/Else Expressions

When a decision really is scalar — picking a single message to display, choosing a branch based on user input — APL still gives you readable conditional expressions. GNU APL supports the :If / :ElseIf / :Else / :EndIf control structure inside defined functions, and you can also use the indexing trick (cond) ⊃ alternatives for an expression-level ternary.

Create a file named if_else.apl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
⍝ Expression-level conditionals using "pick"
⍝ The boolean is used as an index: 0 picks the first, 1 picks the second.

classify  {(1 +> 0)  'non-positive' 'positive'}
'classify 5  →'
classify 5
'classify ¯3 →'
classify ¯3
'classify 0  →'
classify 0

⍝ A defined function using :If / :ElseIf / :Else
∇ msg  grade score
  :If score  90
    msg  'A'
  :ElseIf score  80
    msg  'B'
  :ElseIf score  70
    msg  'C'
  :Else
    msg  'F'
  :EndIf

'grade 95 →'
grade 95
'grade 82 →'
grade 82
'grade 71 →'
grade 71
'grade 40 →'
grade 40
)OFF

Two complementary styles: the one-line classify uses indexing into a list of alternatives — APL’s natural ternary — and the multi-line grade uses the :If block when several branches need to read cleanly top-to-bottom.

While Loops and Iteration

Sometimes you genuinely need iteration that can’t be expressed as an array operation — anything that updates state until a condition is met, like Newton’s method or a search that may stop early. APL has :While and :Until for these cases, and also the older branch arrow that jumps to a labeled line.

Create a file named loops.apl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
⍝ Newton's method for square root using :While
∇ r  newton_sqrt n;guess;next
  guess  n ÷ 2
  next   0
  :While (|guess - next) > 1E¯10
    next   guess
    guess  (guess + n ÷ guess) ÷ 2
  :EndWhile
  r  guess

'sqrt of 2  ≈'
newton_sqrt 2
'sqrt of 16 ='
newton_sqrt 16
'sqrt of 50 ≈'
newton_sqrt 50

⍝ Collatz sequence using :While — record each step
∇ seq  collatz n;cur
  seq  ,n
  cur  n
  :While cur  1
    :If 0 = 2 | cur
      cur  cur ÷ 2
    :Else
      cur  1 + 3 × cur
    :EndIf
    seq  seq , cur
  :EndWhile

'Collatz 6:'
collatz 6
'Collatz 11:'
collatz 11
)OFF

The :While block runs as long as its condition is true; :Until runs at least once and stops when its condition becomes true. Note how newton_sqrt uses local variables (declared with ;guess;next after the function header) — these are private to the function call.

Loop-Free Iteration with Reduce and Scan

The most important “control flow” tool in APL isn’t a loop at all — it’s the reduce operator / and the scan operator \. Both apply a function between every adjacent pair of elements in an array, replacing whole categories of accumulating loops.

Create a file named reduce_scan.apl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
⍝ Reduce: collapse an array with a function between elements
nums  1 2 3 4 5

'Sum (+/):    '
+/ nums
'Product (×/):'
×/ nums
'Maximum (⌈/):'
/ nums
'Minimum (⌊/):'
/ nums

⍝ Scan: like reduce, but keeps every intermediate result
'Running sum (+\):    '
+\ nums
'Running product (×\):'
×\ nums
'Running max (⌈\):    '
\ 3 1 4 1 5 9 2 6

⍝ Combine reduce with a boolean: "are all positive?"
nums2  4 7 2 9 1
'All positive? '
/ nums2 > 0
'Any zero?     '
/ nums2 = 0

⍝ FizzBuzz-style classification without a loop
n  15
fizz  0 = 3 | n
buzz  0 = 5 | n
'Numbers 1..15:'
n
'Divisible by 3:'
fizz
'Divisible by 5:'
buzz
'Divisible by both:'
fizz  buzz
)OFF

+/, ×/, ⌈/, ⌊/, ∧/, and ∨/ together replace most for loops that compute a running total, a maximum, or a “does any element satisfy X” check. When you find yourself reaching for :While in APL, pause and ask whether reduce or scan can do it instead — usually they can.

Running with Docker

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

# Run each example
docker run --rm -v $(pwd):/app -w /app juergensauermann/gnu-apl-1.8 apl --silent --noColor --noCIN -f conditional_arith.apl
docker run --rm -v $(pwd):/app -w /app juergensauermann/gnu-apl-1.8 apl --silent --noColor --noCIN -f selection.apl
docker run --rm -v $(pwd):/app -w /app juergensauermann/gnu-apl-1.8 apl --silent --noColor --noCIN -f if_else.apl
docker run --rm -v $(pwd):/app -w /app juergensauermann/gnu-apl-1.8 apl --silent --noColor --noCIN -f loops.apl
docker run --rm -v $(pwd):/app -w /app juergensauermann/gnu-apl-1.8 apl --silent --noColor --noCIN -f reduce_scan.apl

Expected Output

Output from conditional_arith.apl:

Original:
3 ¯7 12 0 ¯4 8
Absolute:
3 7 12 0 4 8
Sign of each element:
1 ¯1 1 0 ¯1 1
Mask of "big" values:
0 0 1 0 0 1
Negatives clamped to zero:
3 0 12 0 0 8

Output from selection.apl:

Mask:
1 1 0 1 0 0 1 1
Passing scores:
72 88 91 99 80
Number passing:
5
Positions of passing scores:
1 2 4 7 8
Average passing score:
86

Output from if_else.apl:

classify 5  →
positive
classify ¯3 →
non-positive
classify 0  →
non-positive
grade 95 →
A
grade 82 →
B
grade 71 →
C
grade 40 →
F

Output from loops.apl:

sqrt of 2  ≈
1.414213562
sqrt of 16 =
4
sqrt of 50 ≈
7.071067812
Collatz 6:
6 3 10 5 16 8 4 2 1
Collatz 11:
11 34 17 52 26 13 40 20 10 5 16 8 4 2 1

Output from reduce_scan.apl:

Sum (+/):    
15
Product (×/):
120
Maximum (⌈/):
5
Minimum (⌊/):
1
Running sum (+\):    
1 3 6 10 15
Running product (×\):
1 2 6 24 120
Running max (⌈\):    
3 3 4 4 5 9 9 9
All positive? 
1
Any zero?     
0
Numbers 1..15:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Divisible by 3:
0 0 1 0 0 1 0 0 1 0 0 1 0 0 1
Divisible by 5:
0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
Divisible by both:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

Key Concepts

  • Booleans are numbers — comparison operators return 0 or 1, and you can multiply, add, and sum them like any other value. Most simple if/else patterns collapse into arithmetic on boolean masks.
  • Compress (/) replaces filter loops — a boolean vector on the left selects matching elements from the right. Combine with +/ to count, or with reduction to compute summary statistics on the matches.
  • Reduce (+/, ×/, ⌈/, ∧/, ∨/) replaces accumulator loops — any pattern of “start with X, walk the array, combine with each element” is a reduction.
  • Scan (\) replaces running-total loops — when you need every intermediate value of a reduction, use scan instead.
  • :If / :ElseIf / :Else / :EndIf is the explicit conditional block for defined functions; (cond) ⊃ alternatives is the inline ternary form.
  • :While and :Until exist for iterations that genuinely depend on a runtime stop condition (numerical methods, convergence, search). Reach for them last — array operations cover most cases.
  • Defined functions use ∇ … ∇ and can declare local variables after the header with ;name, keeping state private to a single call.
  • Think in shapes, not steps — before writing a loop in APL, ask whether the answer can be expressed as a transformation of a whole array. The answer is usually yes.

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