Getting started

Calculate with arrays, then build functions.

Install

Requires Python ≥3.10.

pip install basedpl

This installs the bapl command and the basedpl Python package.

First calculations

Run bapl to open the REPL. Type an expression and press Enter. ⍝ introduces a comment; examples here use it to show the result.

APL evaluates right-to-left. Parentheses change grouping, and so do spaces: each part between spaces is evaluated first.

2×3+4                 ⍝ 14
(2×3)+4               ⍝ 10
2×3 + 4               ⍝ 10
14
10
10

Numbers separated by spaces form a vector. Functions work on every element.

10+1 2 3              ⍝ 11 12 13
11 12 13

⍳ generates indices, ← assigns a name, and +/ sums.

v←⍳5 ⋄ v              ⍝ 0 1 2 3 4
+/⍳5                  ⍝ 10
0 1 2 3 4
10

To enter ⍳5, type backtick, iota, then 5. The digit accepts the glyph and enters the argument. Symbol entry also supports abbreviations and Tab completion.

Numbers and arrays

Bare numbers are approximate. Use x for exact integers and r for fractions.

1÷3                   ⍝ 0.3333333333333333
1x÷3x                 ⍝ 1r3
0.3333333333333333
1r3

j separates real and imaginary parts.

1j2×1j¯2              ⍝ 5
5

⍴ reshapes a vector; +/ sums each row.

+/2 3⍴⍳6              ⍝ 3 12
3 12

Indices start at 0, and negative indices count from the end. Comparisons use tolerance 1E¯14; 0.3=0.1+0.2 is true. See language rules and the glyph index.

Nested arrays

Brackets write a vector, so brackets round vectors make a nested vector. Each (¨) applies its operand to each element:

n←[[1 2] [3 4 5]]
+/¨n
3 12

The same Each/reduction pattern works with division:

÷/¨n
0.5 3.75

Example algorithms

Let’s create a function to list primes.

A prime has exactly two positive divisors. |⌝ forms an outer product of remainders; ⍨ supplies the same argument on both sides. Indices start at 0, so the candidates are 1+⍳10. Each row below marks the multiples of one candidate:

n←1+⍳10
0=|⌝⍨n
1ₓ 1ₓ 1ₓ 1ₓ 1ₓ 1ₓ 1ₓ 1ₓ 1ₓ 1ₓ
0ₓ 1ₓ 0ₓ 1ₓ 0ₓ 1ₓ 0ₓ 1ₓ 0ₓ 1ₓ
0ₓ 0ₓ 1ₓ 0ₓ 0ₓ 1ₓ 0ₓ 0ₓ 1ₓ 0ₓ
0ₓ 0ₓ 0ₓ 1ₓ 0ₓ 0ₓ 0ₓ 1ₓ 0ₓ 0ₓ
0ₓ 0ₓ 0ₓ 0ₓ 1ₓ 0ₓ 0ₓ 0ₓ 0ₓ 1ₓ
0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 1ₓ 0ₓ 0ₓ 0ₓ 0ₓ
0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 1ₓ 0ₓ 0ₓ 0ₓ
0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 1ₓ 0ₓ 0ₓ
0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 1ₓ 0ₓ
0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 0ₓ 1ₓ

+⌿ sums down the rows, counting each candidate’s divisors:

+⌿0=|⌝⍨n
1ₓ 2ₓ 2ₓ 3ₓ 2ₓ 4ₓ 2ₓ 4ₓ 3ₓ 4ₓ

2= marks primes. Where (⍸) returns their positions. An array next to an argument selects from it, so n applied to the positions gives the primes:

n ⍸2=+⌿0=|⌝⍨n
2 3 5 7

A dfn names its argument ⍵. Given the candidates 1+⍳50, it lists the primes up to 50:

{⍵ ⍸2=+⌿0=|⌝⍨⍵}1+⍳50
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47

Finally, name it. The trailing 1+⍳ generates the candidates:

primes ← {⍵ ⍸2=+⌿0=|⌝⍨⍵}1+⍳
primes 50
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47

The built-in prime glyph ℙ returns the prime at a position, counting from 0. Applied to ⍳15, it produces the same list directly:

ℙ ⍳15
2ₓ 3ₓ 5ₓ 7ₓ 11ₓ 13ₓ 17ₓ 19ₓ 23ₓ 29ₓ 31ₓ 37ₓ 41ₓ 43ₓ 47ₓ

The fibonacci sequence:

{⍵,+/¯2↑⍵}⍣15 [1 1]
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

Explanation:

  1. [1 1]: Initial seed (first two Fibonacci numbers). Without the brackets, 15 1 1 would be one vector.
  2. {⍵,+/¯2↑⍵}: Function that appends the sum of the last two elements
  3. ⍣15: Apply the function 15 times

Labelled arrays

Arrays can carry keys for positions and names for axes, without becoming a separate table type. A keyed vector pairs each axis name with its position keys, in axis order. Here axes: labels the rows by city and the columns by month:

axes←["city":["NY" "LA"] "month":["Jan" "Feb" "Mar"]]
sales←axes:[10 20 30 ⋄ 40 50 60]
sales
   Jan Feb Mar
NY  10  20  30
LA  40  50  60

sales applied to a key selects that row. ⌷ selects positions by key, with one key for each axis. ⍠ applies a function along the axes its operand names. Summing over month leaves a total for each city, and keeps its labels.

sales "LA"
"LA" "Feb"⌷sales
+/⍠"month" sales
["month":3]⍴["Jan":40 "Feb":50 "Mar":60]
50
["city":2]⍴["NY":60 "LA":150]

Unlabelled arrays keep their usual positional behaviour. See Axis keys for construction, updates and alignment rules.

CSV and JSON

CSV headers become keys on a vector of columns. Numeric columns use compact storage. These two orders total 101:

nl←•ucs 10
orders←•csv "price,qty",nl,"10.5,2",nl,"20,4"
+/orders.price×orders.qty
101

JSON objects use the same keyed arrays. Parse a record and select a field:

record←•json "{""name"":""Ada"",""scores"":[8,9,10]}"
record.name
+/record.scores
Ada
27ₓ

•tojson converts back to JSON text. For files, compose with •nget and •nput, as in •csv •nget "orders.csv". See files, CSV and JSON for dialect and file options.

•tojson record
{"name":"Ada","scores":[8,9,10]}

Regex

•r compiles a Rust regex into functions for matching, positions, groups and replacement:

codes←•r "([A-Z]+)-([0-9]+)"
codes.match "AB-12 CD-3"
codes.position "AB-12 CD-3"
"$2:$1" codes.replace "AB-12 CD-3"
"AB-12" "CD-3"
0ₓ 6ₓ
12:AB 3:CD

Probability distributions

Construct a standard normal, then evaluate its CDF and quantiles. Distribution methods accept arrays:

normal←•normal 0 1
normal.cdf ¯1 0 1
normal.quantile 0.025 0.5 0.975
0.15865525394505725 0.5 0.8413447460549428
¯1.9599639845400545 0 1.9599639845400538

Sampling takes a shape. A generator from •rand on the left makes the draws repeatable:

(•rand 1) normal.sample 2 3
0.8439986136221761   0.7155607127305931 ¯1.9158714436187585
0.45249647038446317 ¯0.815791206711391   0.3583251371673788

For discrete distributions, density gives probability mass. A fair coin tossed twice has probabilities ¼, ½, ¼ for zero, one or two heads:

coin←•binomial 2 0.5
coin.density 0 1 2
0.25 0.5 0.25

See distributions for the 17 families, and regex for captures and replacement options.

Learning APL

To start learning APL, follow the 17 video series run by Jeremy Howard, and have a look at the study notes. These use Dyalog APL. bAsedPL differs: indices count from 0, brackets write vectors, and spaces group expressions. See the language rules.