Intermediate

I/O Operations in APL

Read and write data in APL using quad output (⎕←), quote-quad (⍞), the format primitive (⍕), and the ⎕FIO file functions in GNU APL with Docker-ready examples

Input and output are where an otherwise pure, array-oriented language has to talk to the outside world. In the earlier tutorials a bare expression printed its result automatically — that is the only output APL needs for interactive exploration. But once you want to control what appears, build report lines, ask the user for data, or persist results to disk, you reach for APL’s dedicated I/O tools.

APL keeps these tools minimal and consistent with its array philosophy. Output is done by assigning to one of two system variables: (quad) for normal display and (quote-quad) for raw character output without a trailing newline. The same two symbols, read instead of assigned, perform input. Formatting numbers into clean text is the job of the (format) primitive — a single glyph that replaces an entire printf format-string vocabulary. File access in GNU APL is provided by the ⎕FIO family of functions.

This tutorial covers console output, formatted output, keyboard input, and reading and writing files — adapting each to APL’s “operate on whole arrays” mindset rather than the character-at-a-time loops of imperative languages. We use GNU APL throughout, the APL2-style interpreter from the Docker image used across this series.

Console Output

You have already seen that a bare expression displays itself. For deliberate output, assign the value to — the result is sent to the session and followed by a newline. Assign to instead when you want the characters written with no trailing newline, so the next output continues on the same line.

Create a file named output.apl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
⍝ A bare expression is displayed automatically
'Implicit output: a bare expression is shown'

⍝ ⎕← explicitly outputs any array, then adds a newline
 'Explicit output via quad-assignment'
 1 2 3 4 5

⍝ Mix text with a formatted number using catenate (,) and format (⍕)
 'Six times seven is ',  6 × 7

⍝ ⍞← (quote-quad assignment) outputs WITHOUT a trailing newline
 'Same line: '
 'joined by ⍞← then ⎕←'
)OFF

The key distinction is the newline. ⎕← terminates its line, so each ⎕← lands on its own row. ⍞← leaves the cursor mid-line, which is how you stitch several pieces of text — or a prompt and its answer — into a single line. Notice also how , (catenate) joins a literal string with ⍕ 6 × 7: the format primitive turns the number 42 into the two characters 42 so they can sit alongside ordinary text.

Formatted Output

Numbers rarely come out of a computation in exactly the shape you want to show. The (format) primitive is APL’s universal formatter. Used monadically (⍕ value) it produces the default character representation. Used dyadically it takes precision controls on the left: a single number sets the count of decimal places, and a pair sets width decimals for right-aligned, fixed-width columns.

Create a file named format.apl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
⍝ Monadic format ⍕ converts any array to its character representation
  3.14159

⍝ Dyadic format with a single left value = number of decimal places
 2  3.14159
 0  3.14159

⍝ Width and precision together: 10 2 → field width 10, 2 decimals
 10 2  3.14159

⍝ Catenate a label with a formatted number to build a report line
price  19.99
qty    3
 'Line total: $', 2  price × qty
)OFF

2 ⍕ 3.14159 rounds to two decimals (3.14); 0 ⍕ 3.14159 rounds to a whole number (3). The two-element left argument 10 2 reserves a field ten characters wide and right-justifies the value within it — exactly what you want for aligning a column of figures. The last line shows the everyday pattern: compute a value, format it to a fixed precision, and catenate it onto a descriptive label. Because works on arrays, the very same 2 ⍕ would format an entire vector of prices at once.

Reading Input

Input mirrors output: the same and symbols read instead of write when they appear on the right of an expression.

  • (quote-quad) reads one line from the keyboard and returns it as a character vector — exactly what was typed, no interpretation.
  • (quad) reads one line and evaluates it as an APL expression, so typing 3 1 4 1 5 yields an actual five-element numeric vector.

Because both block while waiting for the keyboard, they belong in interactive sessions rather than batch scripts. Start an interactive APL session:

1
docker run --rm -it juergensauermann/gnu-apl-1.8 apl --silent

Then try the following — the six-space indent marks what you type, and the lines below are APL’s response:

1
2
3
4
5
6
7
8
      name Ada
      'Hello, ', name, '!'
Hello, Ada!
      nums 3 1 4 1 5
      +/ nums
14

With , name becomes the character vector Ada, ready to catenate into a greeting. With , the line 3 1 4 1 5 is evaluated into a numeric vector, so +/ nums sums it to 14. This is the division of labour to remember: for raw text, when you want APL to parse the input as data or even as an expression.

Reading and Writing Files

GNU APL accesses files through the ⎕FIO (file I/O) function family. These functions work with byte vectors — vectors of integers in the range 0–255 — so you convert between text and bytes with ⎕UCS, which maps characters to their Unicode code points and back. Reading a whole file is a single call — ⎕FIO.read_file returns a file’s bytes — while writing uses a short handle-based sequence: open the file with ⎕FIO.fopen, push the bytes with ⎕FIO.fwrite, and release it with ⎕FIO.fclose.

The example below writes three lines to a file, reads them back, and then processes the contents with ordinary array operations — no character-by-character loop in sight.

Create a file named file_io.apl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
⍝ ── Writing a file ──────────────────────────────────────────
nl    ⎕UCS 10                       ⍝ newline character (LF) from its code
text  'APL file I/O', nl, 'Line two', nl, 'Line three'

⍝ Open for writing, send the byte vector with fwrite, then close
handle  'w' ⎕FIO.fopen 'notes.txt'      ⍝ 'w' = open for writing
wrote   (⎕UCS text) ⎕FIO.fwrite handle  ⍝ ⎕UCS turns chars into bytes
closed  ⎕FIO.fclose handle              ⍝ flush and close the handle
 'Wrote ', (  text), ' characters to notes.txt'

⍝ ── Reading a file ──────────────────────────────────────────
⍝ ⎕FIO.read_file returns a byte vector; ⎕UCS turns it back into text
raw  ⎕UCS ⎕FIO.read_file 'notes.txt'
 'File contents:'
 raw

⍝ ── Processing the contents ─────────────────────────────────
⍝ With the text in an array, ordinary primitives answer questions
 'Total characters: ',   raw
 'Number of lines: ',  1 + +/ raw = nl
)OFF

The flow is deliberately array-shaped. ⎕UCS 10 is the linefeed character, used both to join the lines for writing and, on the way back, to count them: raw = nl produces a boolean vector marking every newline, +/ adds those up, and 1 + accounts for the final line that has no trailing newline. Tallying lines becomes one expression rather than a loop with a counter. Likewise ≢ raw reports the total character count directly. Each of the handle ←, wrote ←, and closed ← assignments captures (and silently discards) its call’s result so it does not print — only the lines we explicitly send to appear.

The same handle that ⎕FIO.fopen returns also drives incremental reads: ⎕FIO.fgets pulls one line at a time, whereas the ⎕FIO.read_file used above slurps an entire file at once — pick whichever fits your data. For line-oriented text there is also ⎕FIO.write_lines, which takes a nested vector of strings and appends a newline to each, a one-call alternative to assembling the byte vector by hand.

Running with Docker

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

# 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 output.apl
docker run --rm -v $(pwd):/app -w /app juergensauermann/gnu-apl-1.8 apl --silent --noColor --noCIN -f format.apl
docker run --rm -v $(pwd):/app -w /app juergensauermann/gnu-apl-1.8 apl --silent --noColor --noCIN -f file_io.apl

The file_io.apl run creates notes.txt inside the mounted $(pwd) directory, so after it finishes you will find the written file alongside your scripts on the host.

Expected Output

Running output.apl:

Implicit output: a bare expression is shown
Explicit output via quad-assignment
1 2 3 4 5
Six times seven is 42
Same line: joined by ⍞← then ⎕←

Running format.apl:

3.14159
3.14
3
      3.14
Line total: $59.97

Running file_io.apl:

Wrote 32 characters to notes.txt
File contents:
APL file I/O
Line two
Line three
Total characters: 32
Number of lines: 3

Key Concepts

  • ⎕← is normal output, ⍞← is output without a newline — assign to to display a value and end the line; assign to to keep writing on the same line, ideal for prompts and joined text.
  • and also read input — on the right of an expression, returns a raw character vector while evaluates the typed line into data; both block on the keyboard, so they suit interactive use.
  • (format) replaces printf — monadic for the default text form, dyadic with decimals or width decimals for fixed-precision, right-aligned columns, and it formats whole arrays at once.
  • Build report lines by catenating, joins a literal label with a formatted number ('Total: ', ⍕ value), since both sides are just character vectors.
  • ⎕FIO is GNU APL’s file interface⎕FIO.read_file returns a whole file’s bytes in one call, while writing uses the ⎕FIO.fopen⎕FIO.fwrite⎕FIO.fclose handle sequence (or ⎕FIO.write_lines for line-oriented text).
  • ⎕UCS bridges text and bytes — file functions deal in byte vectors, so convert characters to code points (and back) with ⎕UCS; ⎕UCS 10 is the newline character.
  • Process file contents as arrays, not loops — once a file is read into a vector, counting lines, measuring length, or filtering is just +/, , and the primitives you already know.
  • Right-to-left evaluation still applies — I/O expressions like ⎕← 'Six times seven is ', ⍕ 6 × 7 evaluate the rightmost term first, so the multiplication and formatting happen before the catenation and display.

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