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:
| |
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:
| |
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:
| |
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:
| |
# 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:
| |
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:
| |
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
| |
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!:nforeigns are the I/O core —1!:1reads,1!:2writes (replacing),1!:3appends; the right argument is a J file number (2= screen) or a boxed file name. echoandsmoutputadd a newline;1!:2does not — appendLFyourself when writing raw bytes to the screen.":is the workhorse formatter — monadic":turns any noun into text, and dyadic(width)j(decimals) ": valuegives 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 array —
1!:1(orfread) reads everything at once, and array verbs process it directly. <;._2splits text into lines without a loop — cut on the trailing delimiter, then use#,{.,{:,/:~, ande.to work on all lines at once.fread,fwrite, andfappendare the readable standard-library wrappers around the1!:nforeigns and take plain, unboxed file names.- Input is always text —
stdin ''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
Comments
Loading comments...
Leave a Comment