Intermediate

Functions in J

Learn how functions work in J - defining verbs explicitly and tacitly, monadic and dyadic cases, recursion, scope, and higher-order modifiers with Docker-ready examples

In most languages a “function” is a named block of statements that you call with arguments. J has functions too, but it calls them verbs, and the way you build them reflects J’s array-oriented, functional, tacit heritage. A verb in J is not just a procedure—it is a transformation that applies across whole arrays at once, and it can be combined with other verbs without ever naming its arguments.

J verbs come in two flavors depending on how many arguments they take. A monadic verb takes one argument on its right (referred to as y inside an explicit definition). A dyadic verb takes two arguments, one on the left (x) and one on the right (y). Many of J’s primitive verbs are both: - negates one argument (- 5) but subtracts two (8 - 5). Your own verbs can be defined for either or both cases.

There are two distinct styles for writing verbs in J. Explicit definitions spell out the steps using the named arguments x and y, which will feel familiar if you come from imperative languages. Tacit (point-free) definitions describe how verbs combine and never mention their arguments at all. This tutorial covers both styles, plus recursion, variable scope, and how J’s adverbs and conjunctions act as higher-order functions.

Explicit Verbs: Monadic, Dyadic, and Multi-line

The explicit form uses : to declare a verb. 3 : '...' defines a monadic verb whose argument is y. 4 : '...' defines a dyadic verb whose arguments are x (left) and y (right). For longer bodies, verb define ... ) lets you write multiple lines, where the final expression is the result.

Create a file named functions.ijs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
NB. Functions in J are called "verbs"

NB. Monadic verb: one argument, named y
square =: 3 : 'y * y'

NB. Dyadic verb: two arguments, x on the left and y on the right
NB. %: is square root; ^ is power
hypot =: 4 : '%: (x ^ 2) + (y ^ 2)'

NB. Multi-line explicit verb using "verb define"
NB. =. creates a LOCAL variable; the last line is the result
celsius2f =: verb define
  scaled =. y * 9 % 5
  scaled + 32
)

echo square 5
echo 3 hypot 4
echo celsius2f 100

square 5 multiplies the argument by itself. 3 hypot 4 supplies 3 as x and 4 as y, computes 9 + 16, then takes the square root. celsius2f 100 shows a local variable scaled bound with =.—J evaluates right-to-left, so y * 9 % 5 is 100 * (9 % 5), which is 180, and the final line adds 32.

Tacit Verbs: Forks and Hooks

Tacit programming is where J shines. Instead of describing steps over x and y, you compose verbs into a new verb that carries no argument names. The two key patterns are:

  • Hook (f g): applied monadically, (f g) y becomes y f (g y)
  • Fork (f g h): applied monadically, (f g h) y becomes (f y) g (h y)

The classic example is the mean (average): sum the elements, then divide by how many there are. As a fork, +/ % # reads as “(sum) divided-by (count)”.

Create a file named tacit.ijs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
NB. Tacit verbs name no arguments - they describe how verbs combine

sum   =: +/        NB. +/ is "plus inserted between items" = sum
count =: #         NB. # counts the items
mean  =: +/ % #    NB. fork: (sum) % (count)

echo sum 1 2 3 4 5
echo count 1 2 3 4 5
echo mean 1 2 3 4 5

NB. @: is the "at" conjunction: apply *: to the whole array, then +/
NB. *: squares each element
sumsq =: +/ @: *:
echo sumsq 1 2 3 4

The mean verb never mentions its argument—it is defined purely in terms of +/, %, and #. The sumsq verb composes two verbs with the conjunction @:: first square every element (*: turns 1 2 3 4 into 1 4 9 16), then reduce with +/ to get 30.

Recursion

J verbs can call themselves. Inside an explicit definition, the special name $: refers to the verb currently being defined—a self-reference that works even for anonymous verbs. J also provides control words if. do. else. end. for the branching that recursion usually needs.

Create a file named recursion.ijs:

1
2
3
4
5
6
7
8
NB. Recursion with $: (self-reference) and if./do./else./end.
factorial =: 3 : 'if. y <= 1 do. 1 else. y * $: y - 1 end.'

echo factorial 5
echo factorial 0

NB. J also has a built-in factorial verb: !
echo ! 5

factorial 5 expands to 5 * 4 * 3 * 2 * 1, giving 120. The base case returns 1 when y is 0 or 1. The last line shows that J already provides factorial as the primitive verb !, so ! 5 matches our hand-written version—a reminder that in array languages you often reach for a primitive before writing a recursion.

Higher-Order Modifiers: Adverbs and Conjunctions

J does not pass functions to functions the way Python passes a callback. Instead it has two kinds of modifiers that operate on verbs themselves:

  • Adverbs take one verb and produce a new verb. The reduce adverb / turns a dyadic verb into a whole-array operation: +/ is sum, */ is product, >./ is maximum.
  • Conjunctions combine two things into a new verb. The bond conjunction & fixes one argument (2 & * always doubles); the atop conjunction @: composes verbs.

These modifiers are J’s higher-order functions—they take verbs as operands and return new verbs.

Create a file named higher_order.ijs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
NB. The / adverb inserts a verb between array items
echo +/ 1 2 3 4 5      NB. sum
echo */ 1 2 3 4 5      NB. product
echo >./ 3 1 4 1 5 9   NB. maximum

NB. The & conjunction bonds a value to a verb, making a new monadic verb
double =: 2 & *
echo double 1 2 3 4

NB. Build a verb from other verbs: increment each (>:), then sum (+/)
incrsum =: +/ @: >:
echo incrsum 1 2 3 4

+/, */, and >./ each apply the reduce adverb to a different verb, collapsing the whole array to a single value. double is built by bonding 2 to multiplication, producing a verb that scales every element. incrsum composes increment (>: adds one to each element, giving 2 3 4 5) with sum, yielding 14.

Running with Docker

1
2
3
4
5
6
7
8
# Pull the official J image
docker pull nesachirou/jlang:latest

# Run each example (the default entrypoint is ijconsole)
docker run --rm -v $(pwd):/app -w /app nesachirou/jlang functions.ijs
docker run --rm -v $(pwd):/app -w /app nesachirou/jlang tacit.ijs
docker run --rm -v $(pwd):/app -w /app nesachirou/jlang recursion.ijs
docker run --rm -v $(pwd):/app -w /app nesachirou/jlang higher_order.ijs

The -v $(pwd):/app flag mounts your current directory into the container and -w /app makes it the working directory, so the .ijs files you created are visible to J.

Expected Output

Running functions.ijs:

25
5
212

Running tacit.ijs:

15
5
3
30

Running recursion.ijs:

120
1
120

Running higher_order.ijs:

15
120
9
2 4 6 8
14

Key Concepts

  • Verbs are J’s functions — a monadic verb takes one argument (y), a dyadic verb takes two (x on the left, y on the right), and many verbs support both cases.
  • Two definition styles — explicit (3 : '...', 4 : '...', or verb define ... )) uses named arguments; tacit (point-free) composes verbs without naming arguments.
  • Forks and hooks(f g h) y is (f y) g (h y) and (f g) y is y f (g y); these composition rules let you build verbs like +/ % # for the mean.
  • Scope is controlled by the assignment arrow=. creates a local variable inside a verb, while =: creates a global one.
  • $: enables recursion — it is a self-reference to the verb being defined, paired with if. do. else. end. for branching.
  • Adverbs and conjunctions are higher-order — adverbs like / modify one verb (+/ is sum), and conjunctions like & and @: combine operands into new verbs.
  • Prefer primitives over loops — many tasks that need an explicit function elsewhere are a single primitive verb in J (! for factorial, +/ for sum), reflecting its array-oriented design.

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