Intermediate

I/O Operations in J

Read and write files, format output, and capture keyboard input in J using foreign conjunctions and array-oriented text processing

Input and output in J are handled by foreign conjunctions — the !: family of system verbs you first met in Hello World, where 1!:2 (2) wrote a string to the screen. The 1 class is the file/console family: 1!:1 reads, 1!:2 writes (replacing), and 1!:3 appends. The right argument is either a J file number (2 is the screen) or a boxed file name like <'data.txt'.

Because J is an array language, I/O has a distinctive flavor: you rarely read a file line-by-line in a loop. Instead you slurp the whole file into a single character array with one call, then reshape, cut, and reduce it with array verbs. A file is just a long string, and a string is just an array of characters — so every array tool you already have applies to file contents directly.

This tutorial covers screen output beyond a simple greeting (formatting numbers, joining strings), whole-file reading and writing, appending, reading standard input, and processing file text as an array. Along the way you will see both the low-level foreign conjunctions and their friendlier standard-library wrappers.

Console Output and Formatting

echo and smoutput both write a value to the screen followed by a newline, and both format their argument automatically — numbers and arrays become readable text with no extra work. The raw 1!:2 (2) foreign writes exactly the bytes you give it and adds no newline, so you must append LF yourself. LF (line feed), CR, and TAB are predefined character nouns in J.

To build a line of text, join pieces with , (append). Numbers must first be turned into text with the monadic ": (default format, called “thorn”). The dyadic ": gives you fixed-width, fixed-decimal formatting: the left argument (width)j(decimals) controls the layout, so 5j2 means a field five characters wide with two decimal places.

Create a file named console_io.ijs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
NB. console_io.ijs - console output and formatting in J

NB. echo and smoutput each add a trailing newline
echo 'echo adds a trailing newline'
smoutput 'smoutput does the same'

NB. 1!:2 writes raw bytes to screen (file 2) - add LF yourself
('1!:2 needs an explicit newline', LF) 1!:2 (2)

NB. echo formats numbers and arrays automatically
echo 100
echo 1 2 3 4 5
echo 2 3 $ i. 6

NB. Join strings with , (append)
echo 'Language: ' , 'J'

NB. Turn a number into text with ": before joining
echo 'Count is ' , ": 42

NB. Fixed-decimal formatting: (width)j(decimals)
echo 'Price: ' , 5j2 ": 19.5

Notice that echo 2 3 $ i. 6 prints a genuine 2-by-3 matrix. J’s default formatter knows the shape of its argument, so a rank-2 array prints as rows and columns without any layout code.

Reading and Writing Files

Writing a file is a single dyadic call: the data on the left, the boxed file name on the right. 1!:2 replaces the file’s contents entirely, while 1!:3 appends to whatever is already there. Reading is the monadic 1!:1 applied to the boxed name, which returns the whole file as one character string — trailing newlines and all.

Because the newline is just another character, you assemble a multi-line file by interleaving LF between your lines with ,.

Create a file named file_io.ijs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
NB. file_io.ijs - reading and writing files in J

NB. Build the file contents (LF separates the lines)
lines =: 'Apple', LF, 'Banana', LF, 'Cherry'

NB. 1!:2 replaces the file with our data (box the file name)
lines 1!:2 <'fruit.txt'
echo 'Wrote fruit.txt'

NB. 1!:1 reads the whole file back as one string
content =: 1!:1 <'fruit.txt'
echo 'File contents:'
echo content

NB. 1!:3 appends to an existing file
(LF, 'Date') 1!:3 <'fruit.txt'
echo 'After appending a line:'
echo 1!:1 <'fruit.txt'

Named wrappers

The standard library ships convenience verbs that box the file name for you and read more like ordinary code. They are exactly equivalent to the foreigns above:

1
2
3
4
NB. These take a plain (unboxed) file name
lines fwrite 'fruit.txt'      NB. same as: lines 1!:2 <'fruit.txt'
data =: fread 'fruit.txt'     NB. same as: 1!:1 <'fruit.txt'
extra fappend 'fruit.txt'     NB. same as: extra 1!:3 <'fruit.txt'

Prefer fread/fwrite/fappend in real scripts; the 1!:n forms are shown here so you understand what they do underneath.

Processing File Text as an Array

Here is where J diverges sharply from imperative languages. Rather than looping over lines, you read the whole file and then cut it into pieces with ;._2, the cut conjunction that splits a string on its final character. <;._2 boxes each resulting line so you get a list of boxed strings you can index, sort, and test with ordinary array verbs — no loop, no line counter.

Create a file named process_lines.ijs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
NB. process_lines.ijs - treat file text as an array of lines

text =: 'Cherry', LF, 'Apple', LF, 'Banana', LF

NB. <;._2 splits on the final character (LF) into boxed lines
rows =: <;._2 text

echo 'Number of lines: ' , ": # rows
echo 'First line: ' , > {. rows
echo 'Last line: ' , > {: rows

NB. Membership test across every line at once: e. (is-element)
echo 'Has Apple? ' , ": (<'Apple') e. rows

# rows counts the lines, {. and {: take the first and last items, and > (open) unboxes a line back into a plain string. The final e. asks “is this box one of these boxes?” and answers for the entire list in a single operation — the kind of whole-array question that replaces an explicit search loop.

Reading Standard Input

J is not a stream-oriented language: there is no readline and no built-in prompt. Instead the standard stdin verb slurps all of standard input at once — stdin '' returns everything piped in as a single character string, exactly like reading a file. This suits J’s array style perfectly: read the whole input, then split it into an array of lines and work on them together. Input always arrives as text, so parse a line into a number with the monadic ". (do/execute), which evaluates a string as a J expression.

Create a file named read_input.ijs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
NB. read_input.ijs - read all of stdin, then process it as lines

NB. stdin '' returns everything piped in as one string
input =: stdin ''

NB. Split into an array of lines on the trailing LF (same idiom as before)
lines =: <;._2 input

name =: > {. lines           NB. first line, unboxed
age  =: ". > {: lines        NB. last line, parsed into a number

echo 'Hello, ' , name , '!'
echo 'Next year you will be ' , ": age + 1

exit ''

Feed the script its answers on stdin, running with -i so the container accepts the pipe. A run where the user supplies Ada then 36 looks like:

1
printf 'Ada\n36\n' | docker run --rm -i -v $(pwd):/app -w /app nesachirou/jlang read_input.ijs
Hello, Ada!
Next year you will be 37

Reading the whole of stdin and cutting it into lines reuses the very same <;._2 idiom from the previous section — standard input is just another character array. The trailing exit '' closes the interpreter once the script finishes rather than leaving it waiting for more input.

Running with Docker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Pull the official image
docker pull nesachirou/jlang

# Console output and formatting
docker run --rm -v $(pwd):/app -w /app nesachirou/jlang console_io.ijs

# Reading and writing files
docker run --rm -v $(pwd):/app -w /app nesachirou/jlang file_io.ijs

# Processing file text as an array
docker run --rm -v $(pwd):/app -w /app nesachirou/jlang process_lines.ijs

The -v $(pwd):/app mount is what lets file_io.ijs write fruit.txt back to your current directory — the file appears on your host after the container exits.

Expected Output

Running console_io.ijs:

echo adds a trailing newline
smoutput does the same
1!:2 needs an explicit newline
100
1 2 3 4 5
0 1 2
3 4 5
Language: J
Count is 42
Price: 19.50

Running file_io.ijs:

Wrote fruit.txt
File contents:
Apple
Banana
Cherry
After appending a line:
Apple
Banana
Cherry
Date

Running process_lines.ijs:

Number of lines: 3
First line: Cherry
Last line: Banana
Has Apple? 1

Key Concepts

  • The 1!:n foreigns are the I/O core1!:1 reads, 1!:2 writes (replacing), 1!:3 appends; the right argument is a J file number (2 = screen) or a boxed file name.
  • echo and smoutput add a newline; 1!:2 does not — append LF yourself when writing raw bytes to the screen.
  • ": is the workhorse formatter — monadic ": turns any noun into text, and dyadic (width)j(decimals) ": value gives fixed-width, fixed-decimal output.
  • Join text with , (append) — format numbers to text first, since , only concatenates arrays of the same type.
  • A whole file is one character array1!:1 (or fread) reads everything at once, and array verbs process it directly.
  • <;._2 splits text into lines without a loop — cut on the trailing delimiter, then use #, {., {:, /:~, and e. to work on all lines at once.
  • fread, fwrite, and fappend are the readable standard-library wrappers around the 1!:n foreigns and take plain, unboxed file names.
  • Input is always textstdin '' reads all of standard input as one string; split it into lines and convert with the monadic ". when you need a number.

Running Today

All examples can be run using Docker:

docker pull nesachirou/jlang:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining