Skip to content

Repository files navigation

aplnb

aplnb brings MiniAPL to Jupyter and IPython. Use %%apl for APL session output and %apl for native MiniAPL values in Python. Both share a persistent workspace.

See core for the implementation. For the J language, see the sibling project jnb.

Installation

Install aplnb and its MiniAPL runtime:

pip install aplnb

No separate APL installation is needed. To load the magics automatically in IPython and Jupyter, run:

aplnb_install

Or run %load_ext aplnb in an individual notebook. The interpreter starts on the first use of a magic.

Native arrays

The line magic %apl returns a native basedpl.Array. Its immutable data stays in MiniAPL, retaining nesting, exact numbers and empty-array prototypes. No NumPy conversion is needed:

v = %apl 1 2 4
v
[1., 2., 4.]

Python operators use MiniAPL’s array semantics. Python determines precedence, so this multiplies before adding:

v * 2 + 1
[3., 5., 9.]

Indexing is one-based, as in APL. A full : selects an axis. Iteration yields rows of a matrix rather than individual elements:

a = %apl 2 3⍴⍳6
a.shape, a[1, :], a[:, 2], list(a)
((2, 3), [1., 2., 3.], [2., 5.], [[1., 2., 3.], [4., 5., 6.]])

MiniAPL also exposes functions by their word names. plus.reduce() is +/; adding .each() applies it to each nested element:

from basedpl import Array, plus, tally
nested = %apl (1 2)(3 4 5)
nested, plus.reduce().each()(nested)
([Array([1., 2.]), Array([3., 4., 5.])], [3., 12.])

Python integers become exact values. Functions compose too: plus.reduce() / tally builds the mean function +/÷≢. Its result is still a native array, even when scalar:

exact = Array([1, 2, 4])
mean_native = plus.reduce() / tally
mean_native(exact)
Fraction(7, 3)

The native representation uses Python numeric notation: 2 is exact and 2. is approximate. .apl returns the APL display as a string:

v.apl, exact.apl
('1 2 4', '1x 2x 4x')

The apl magics

Hold left Alt/Option for MiniAPL’s glyph keyboard: Alt-h , Alt-minus ×, Alt-equals ÷, Alt-Shift-a . Right Option keeps its native behavior. Chords work in APL input, including strings and comments.

Type a backtick followed by a MiniAPL symbol name: `io then Tab inserts , and 2`times3 becomes 2×3. Suggestions appear beside the cursor as you type. Click a suggestion or keep typing to resolve an ambiguous name. Names and aliases come from MiniAPL’s REPL catalogue.

Completion is active in %%apl cells and on %apl lines, including x = %apl .... Ordinary Python and Markdown input is unchanged. Strings, comments and pasted text are not expanded. Tab explicitly completes an existing name. Enter accepts a unique match before the notebook’s normal newline or execution action. Escape or cursor movement cancels automatic expansion.

The first apl magic also adds a clickable symbol bar, based on Adám Brudzewsky’s APL language bar. Hover over a glyph to see its names. The / button switches between pushing the page down and overlaying it. This choice is remembered per site.

The cell magic (%%apl) displays MiniAPL session output in Adám’s SAX2 APL font:

%%apl
m3 3⍴⍳9
m×10
10 20 30
40 50 60
70 80 90

Assignments are shy: the m← line printed nothing. The line magic returns a native array. Use .np for a NumPy array; install NumPy with pip install numpy to run the conversion examples:

v = %apl 3×⍳4
v.np
array([ 3.,  6.,  9., 12.])
text = %apl 'APL in Python'
text.py
'APL in Python'

.py converts numeric scalars to Python numbers and character vectors to strings. Other arrays become NumPy arrays. .np always returns a NumPy array. Both conversions copy the data:

z = %apl m
z.np
array([[1., 2., 3.],
       [4., 5., 6.],
       [7., 8., 9.]])

To suppress a cell’s output, end the last line with a ;:

%%apl
m×10;

⎕← displays a value explicitly, which is how you show something that would otherwise be shy:

%%apl
v2×⍳5
⎕←v
2 4 6 8 10

Convert to NumPy to use its methods:

a = %apl m
a.np.sum(axis=0)
array([12., 15., 18.])

Example algorithms

The fibonacci sequence:

fib = %apl {⍵,+/¯2↑⍵}⍣151 1
fib.np
array([1.000e+00, 1.000e+00, 2.000e+00, 3.000e+00, 5.000e+00, 8.000e+00,
       1.300e+01, 2.100e+01, 3.400e+01, 5.500e+01, 8.900e+01, 1.440e+02,
       2.330e+02, 3.770e+02, 6.100e+02, 9.870e+02, 1.597e+03])

Explanation:

  1. 1 1: Initial seed (first two Fibonacci numbers)
  2. {⍵,+/¯2↑⍵}: Function that appends the sum of the last two elements
  3. ⍣15: Apply the function 15 times
  4. : Identity function, passes the initial argument (1 1) to the iteration

Prime number sieve:

%%apl
primes ← {⍵×2=+0=⍵∘.|⍵}⍳
(primes 50)~0
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47

Explanation:

  1. ⍳50 generates integers 1 to 50
  2. ⍵∘.|⍵ creates a matrix of remainders, with candidate divisors in the rows
  3. 0= marks the entries with no remainder
  4. +⌿ sums columns, counting divisors for each number
  5. 2= selects numbers with exactly two divisors
  6. ⍵× keeps those numbers and replaces the rest with zero
  7. ~0 removes the zeros

Using MiniAPL from Python

The magics use basedpl.Session. Calling a session returns a native array or function and prints explicit APL output. apl.eval(...) returns a Result with the native .value and captured .output without printing. Both suppress implicit APL display. Use .np or .py when you need a converted result:

import numpy as np
from basedpl import Session
apl = Session()
apl('3 3⍴⍳9').np
array([[1., 2., 3.],
       [4., 5., 6.],
       [7., 8., 9.]])

Keyword arguments bind Python values in the workspace. Square brackets read an APL expression or assign a value:

apl(x=np.arange(1, 6))
apl['v'] = [3,1,4,1,5]
apl['{⍵[⍋⍵]}v'].np
array([1, 1, 3, 4, 5])

fn makes a composable Function from an APL function expression. Pass one argument for a monadic call or two for a dyadic call. Python integers stay exact, so .py converts this mean to a Fraction rather than a float:

mean = apl.fn('{(+/⍵)÷≢⍵}')
mean([1,2,4]).py
Fraction(7, 3)

fn is late-bound: apl.fn('foo') follows later redefinitions of foo. Use create_magic(session=apl) to share a Python session with the magics.

Use a session as a context manager (with Session() as apl:), or close it when finished:

apl.close()

Dyalog reference sessions

Use aplnb.dyalog when you need Dyalog as an independent reference interpreter. Dyalog must be installed separately. This session API does not change the %apl or %%apl magics, which continue to use MiniAPL:

from aplnb.dyalog import Apl
with Apl() as dyalog:
    total = dyalog.pyval('+/⍳10')
total
55

pyval returns JSON-converted Python values. run returns session output as text. See Dyalog sessions for assignment, function calls and error handling.

Errors and interruption

APL errors raise basedpl.AplError. The magics display output produced before the error. Incomplete input raises a syntax error without resetting the workspace.

Interrupt a calculation with the notebook’s stop button. Set a per-evaluation deadline with Session(timeout=seconds) or magic.session.timeout = seconds. Sessions use a Rust worker thread. Cooperative cancellation preserves the workspace and completed assignments. Native-library calls and individual BigInt operations can delay cancellation; the thread is never forcibly killed.

MiniAPL implements a subset of Dyalog APL. See its README for supported language features and differences.

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. Interpreter-specific features and user commands differ in MiniAPL.

About

An IPython magic for Dyalog APL

Resources

Stars

4 stars

Watchers

7 watching

Forks

Releases

Used by

Contributors

Languages