~ / track G / neighboring paradigms
Array programming (APL / J)
AdvancedArray programming treats whole arrays as primary values. Operations are
"lifted" to apply element-wise (or rank-wise) without writing loops. APL,
J, K, and Q are the canonical languages; NumPy and Clojure's core.matrix
borrow the same ideas. Once you internalize it, a loop-heavy algorithm
collapses into a one-liner that reads as a transformation of shape.
The core insight
In a normal language: "for each element, do X." In an array language: "X
applies to arrays." Adding two vectors isn't a loop; it's just +.
NumPy in Python is the most familiar example:
import numpy as np
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
a + b # array([11, 22, 33])
a * 2 # array([2, 4, 6])
(a + b).sum() # 66
In Clojure with core.matrix (or just plain seqs for 1D):
The pattern: broadcast operations over arrays of compatible shapes, reduce along an axis when you want a scalar.
APL: notation as a tool of thought
APL pushes this further with single-character operators:
+/ 1 2 3 4 5 ⍝ sum: 15
×/ 1 2 3 4 5 ⍝ product: 120
1 2 3 + 10 20 30 ⍝ elementwise: 11 22 33
⍳5 ⍝ iota: 1 2 3 4 5
+/⍳100 ⍝ sum 1..100: 5050
The famous APL one-liner for the average:
avg ← {(+/⍵) ÷ ≢⍵}
(+/⍵) sums the argument, ÷ divides by ≢⍵ (the count). The notation
is the algorithm — there's no loop, no temporary variable.
Rank and broadcasting
The deep idea in array languages is rank — the number of dimensions of a
value. Operators have an expected rank; values are automatically lifted to
match. + between a scalar and a vector adds the scalar to each element.
Between a vector and a matrix, between two matrices of compatible shape —
the rules generalize. NumPy calls this broadcasting; APL/J call it rank
polymorphism.
Why think this way
- No loop bugs. No off-by-ones, no accumulator typos. The shape changes describe the algorithm.
- Performance. Whole-array primitives are dispatched to optimized C/Fortran code (BLAS, SIMD, GPU kernels).
- Composability. A pipeline of array operations is itself an array operation; you can transform an algorithm by transforming its shape, not rewriting its body.
- Data-parallel by nature. "Apply f to every element" is the most paralleliseable shape; array languages were designed for this before GPUs existed.
Where Clojure folks meet array thinking
core.matrix,dtype-next, andneanderthalgive Clojure NumPy-like arrays with fast kernels.- ML and data work in Clojure (Cortex, MXNet bindings) live in this style.
- Even plain
mapv/reduceover a homogeneous vector is array-shaped thinking.
Check yourself
? quiz
What's the practical advantage of writing `a + b` (whole-array add) instead of `for i: c[i] = a[i] + b[i]`?
Exercise
Without writing any explicit loops, compute the dot product of two vectors
a and b of equal length: Σᵢ aᵢ · bᵢ.