Blog › ICP guides

J language developer on retainer: rank conjunction, tacit programming, trains, array verb design, and J array language engineering on monthly retainer

October 24, 2026 · ~17 min read

A financial analytics system written in J was computing wrong column-wise means on a price matrix for 4 queries per day. The system used a J verb defined as mean_col =: +/ % # — a tacit train that computes the sum divided by the count — to aggregate monthly price data across a 12-month × 8-region matrix. On months where some regions had partial data, the dataset was stored as a non-rectangular boxed array of lists with varying lengths. The J developer on retainer diagnosed the root cause: the verb mean_col was applied to the boxed array without a rank specification. J’s default rank for +/ is the rank of the entire argument — for a rank-2 matrix, +/ sums down the leading axis (producing column sums), but for a rank-1 list of boxes, +/ attempts to sum the boxes themselves, which is a type error or produces unexpected results when the boxed items are lists of different lengths. The fix added the rank conjunction "1 to force row-level application: mean_col"1 applied the mean_col verb to each rank-1 item (row) of the array independently, producing correct per-row means that were then aggregated column-wise in a subsequent step. Wrong column means: 4 per day → 0.

The work log entry read “fixed aggregation error in price matrix, 13h.” It names the symptom and the duration. It cannot explain to a client why the fix required understanding J’s rank system, why +/ applies over the leading axis by default, why the rank of the argument changes when the matrix becomes a boxed array of lists, why "1 forces row-level application, why the default rank of +/ % # as a tacit train is determined by the ranks of its component verbs, or how J’s rank system interacts with non-rectangular data where the shape primitive $ returns a scalar for a boxed array rather than the matrix dimensions. The 13 hours of rank analysis (tracing the verb application through the array shape to determine which rank was being used), shape inspection (using $ and #@$ to examine the actual shapes of the data before and after the boxed-array change), rank conjunction experimentation (testing "0, "1, and "2 rank specifications against both rectangular and boxed-array inputs), and train verification (confirming that mean_col"1 produced the correct result across all data shapes) are not visible in the diff beyond the added "1 rank specification.

J’s rank system: the " conjunction, cell shapes, and axis control

Rank is J’s fundamental mechanism for specifying which cells of an array a verb operates on. Every J verb has an intrinsic rank — a pair of numbers (m, n) where m is the monadic rank (the rank of cells the verb processes when called with one argument) and n is the left and right dyadic rank. The intrinsic rank of +/ (sum-insert, which inserts + between all items) is (_, _) — infinite rank — meaning it applies to the entire argument array as a whole. For a rank-2 matrix, applying +/ with infinite rank means the verb sees the whole matrix as one 2-cell and sums down the leading axis, producing a rank-1 array of column sums. For a rank-1 list, +/ with infinite rank sees the whole list and produces a scalar sum. The rank conjunction " overrides a verb’s intrinsic rank: f"k creates a new verb that applies f to each rank-k cell of its argument. f"0 applies f to each atom (scalar). f"1 applies f to each rank-1 cell (row of a matrix, item of a list). f"2 applies f to each rank-2 cell (matrix slice of a rank-3 array). Infinite rank f"_ is explicit statement of the default. For a dyadic verb, f"(m,n) specifies left rank m and right rank n separately: f"(1 2) applies f to rank-1 cells of the left argument paired with rank-2 cells of the right argument.

The shape primitive $ returns the shape of an array: $ 3 4 $ 0 returns 3 4 (a 3×4 matrix). The count primitive # returns the number of items along the leading axis: # 3 4 $ 0 returns 3. The rank primitive #@$ returns the rank (number of dimensions): #@$ 3 4 $ 0 returns 2. For boxed arrays — arrays whose items are boxes created with < — the shape is the shape of the box array, not the shapes of the boxed items. A 3-item list of boxes has shape 3 and rank 1; the boxes themselves may contain arrays of different shapes. This is why a non-rectangular dataset stored as a boxed list of price-row lists has rank 1 (the list of boxes), not rank 2 (the matrix), and why applying +/ to it with default (infinite) rank sums the boxes rather than the items inside the boxes. To apply a verb inside the boxes, use the each adverb "_1 (or equivalently every): f"_1 y applies f to the content of each box in y, unboxing each item, applying f, and re-boxing the result. For operations where the result should not be re-boxed (such as summation), combine each with > to unbox before operating: (f @ >) "0 or use > to open the boxed array into a padded rectangular array (which fills missing elements with the fill element for the type, typically 0 for numbers) before applying rank-sensitive verbs.

J’s key array primitives: i. (iota) generates indices — i. 5 is 0 1 2 3 4; i. 3 4 is a 3×4 matrix of row-major indices. # count (monadic: number of items) and tally. , ravel (monadic: flatten to list) and append (dyadic: join). ,. stitch (dyadic: join along last axis). ,: laminate (dyadic: join into new leading axis). |: transpose. /: grade up (returns permutation that would sort ascending). \: grade down. { from (index into): 0 { y selects item 0; (0 2) { y selects items 0 and 2. {. head (first item); }. behead (all but first). {: tail (last item); }: curtail (all but last). |. reverse (monadic) and rotate (dyadic: n |. y rotates by n). | absolute value (monadic) and residue/mod (dyadic). <. floor; >. ceiling. % reciprocal (monadic) and divide (dyadic). ^ exponential (monadic) and power (dyadic). ^. natural log (monadic) and log-base (dyadic). ! factorial (monadic) and binomial coefficient (dyadic). p. evaluate polynomial. q: prime factorization. A. anagram index. C. cycle representation.

Insert (u/) applies a dyadic verb u between all items: +/ 1 2 3 4 = 1 + 2 + 3 + 4 = 10. Prefix (u\) applies u to successive prefixes: +\ 1 2 3 4 = 1 3 6 10 (running sum). Suffix (u\:) and infix (n u\ y, applies u to each n-item window). The table conjunction (x u/ y or u/~ for the self-table): applies u to all pairs from x and y, producing a rank-2 result. Composition: f @ g (atop, monadic only: f(g y)); f @: g (atop, dyadic: f(x g y)); f & g (bond/compose, monad: f(g y), with pre-application of g on both sides for dyadic); f &. g (under: apply g, apply f, apply inverse of g). The under conjunction &. automatically uses J’s obverse (functional inverse) system: each primitive has a registered inverse, so f &. > opens all boxes (>), applies f, and re-boxes the results (<, the inverse of >). The power conjunction ^: iterates a verb: f^:n y applies f n times; f^:_ y iterates until convergence.

J tacit programming, trains, gerunds, and control structures

Tacit (point-free) J verbs are verbs whose definitions contain no explicit references to their arguments (x and y) or any locally named variables. A tacit verb is built entirely from primitive verbs, adverbs, and conjunctions. The two fundamental tacit patterns are the hook and the fork. A hook is a two-verb train (f g): monadically, it computes y f (g y) (apply g to y, then apply f with y as left argument and the result as right argument); dyadically, it computes x f (g y). A fork is a three-verb train (f g h): monadically, it computes (f y) g (h y) (apply both f and h to y, then apply g to their results); dyadically, (x f y) g (x h y). Forks compose naturally: (f g h k l) is a five-verb train equivalent to (f g (h k l)), where (h k l) is itself a fork. The mean verb mean =: +/ % # is a fork: it applies +/ (sum) and # (count) to the argument, then applies % (divide) to their results. Adding "1: mean"1 applies the mean fork to each rank-1 cell (row). A hook can be introduced to normalize data before averaging: mean_norm =: mean @ (%: @ ,) computes the mean of the raveled square roots — the atop @ composes the ravel-and-square-root step into the mean.

Explicit J verb definitions use the {{ }} syntax (J 9.x) or the older (: ...) form. Inside an explicit verb: y is the right argument; x is the left argument (if dyadic); local variables are assigned with =.; global variables are assigned with =:; return value is the last expression. Control structures inside explicit verbs use the .-terminated control words: if. cond do. expr end., for. list do. expr end., while. cond do. expr end., select. expr case. val do. expr fcase. val do. expr end.. The for_item. list do. ... end. form binds the current item to item and the current index to item_index for each iteration. Explicit verbs are useful for logic that is too complex for tacit expression, but J practitioners prefer tacit style because explicit verbs are less composable and prevent J’s rank polymorphism from applying to sub-expressions. A common refactor pattern: identify the independent sub-computations in an explicit verb (those that depend only on y and not on intermediate results of each other), and replace the explicit body with a fork or hook train.

Gerunds are lists of boxed verbs: f`g`h creates a gerund containing three verbs as boxes. Gerunds are used with the m`n adverbs to select which verb to apply based on a condition, and with the ; (link) conjunction to create verb lists. The agenda adverb m`n @. f selects verb m if f y is 0, and verb n if f y is 1: a two-way conditional verb. (m0`m1`m2) @. f is a three-way conditional using a numeric selector. Gerunds can also be used with ;. (cut) for parsing: 1;._2 y cuts y at delimiters, 2;._2 y cuts and excludes delimiters, and so on. The under conjunction &. (discussed above) uses the inverse of a primitive, which can be extended using the obverse conjunction :: f : g defines a verb with monad f and dyad g; when used as the right argument of &., the dyadic form g is treated as the inverse of the monadic form f. This is the mechanism for composing custom invertible transformations into under pipelines.

J I/O: 1!:1 < 'filename' reads a file as a string; 1!:2 < 'filename' writes a string to a file. fread and fwrite in the files/fio library. CSV parsing: ','&(;:) tokenizes on comma; <;._2 (y, LF) cuts on newlines. Foreign function calls: 15!:0 loads a shared library; 15!:1 calls a function from the loaded library; function signatures are specified using J type descriptors (x for integers, f for floats, c for chars). The JHS (J HTTP Server) allows J verbs to serve HTTP requests: handler verbs are registered for URL paths and receive the request body as y. The J package manager JPM installs packages from the J Software repository: install 'math/lapack' installs LAPACK bindings; require 'csv' loads the CSV parsing utilities; require 'plot' loads the J plot library for Gnuplot-backed chart generation.

How HourTab tracks J language developer retainer hours

J retainer work shares the invisible-work problem with all array language retainers, with the additional challenge that J’s most common retainer tasks — rank specification, tacit train restructuring, gerund design, under conjunction adoption — produce diffs whose surface area is minimal relative to the analytical work required. Adding "1 to a verb is a two-character change; the value is correct axis-level aggregation for all future array shapes, including non-rectangular inputs that were previously producing wrong results silently (J does not raise an exception for wrong-axis aggregation — it produces a numerically plausible but wrong result). Restructuring a seven-intermediate-variable explicit verb into a fork train is a diff that removes seven lines and replaces them with one; the value is composability (the train can be embedded inside other trains), rank polymorphism (the train inherits rank behavior from its component verbs), and performance (J avoids materializing intermediate arrays for tacit trains in many execution paths). Replacing a three-step explicit pipeline with a single under conjunction call is a diff that reduces three lines to one; the value is correctness (J’s automatic inverse is provably correct for registered primitives, whereas manual inverse computation is error-prone) and maintenance (the inverse relationship is declared once, not reimplemented twice).

HourTab gives J language developers a public retainer-hours URL they send to clients — typically quantitative finance firms using J for array-based financial analysis, scientific computing teams processing large numerical datasets with J’s compact array primitives, and industrial systems using J’s JHS server for web-based analytical dashboards — at the start of an engagement. For J retainers, each work log entry should name the mechanism ("k rank conjunction addition for cell-level verb application; $ shape and #@$ rank assertion design before rank-sensitive operations; fork/hook/atop tacit train construction; @ atop and @: dyadic atop composition; ^: power conjunction for iteration; &. under conjunction with automatic inverse design; ` gerund and tie adverb construction; m`n @. f agenda conditional design; +/"1 row-wise aggregation vs +/ whole-array aggregation; </> boxing and unboxing pipeline design; <;._2 cut delimiter parsing; 1!:1/1!:2 file I/O; 15!: FFI binding), the specific verb name and the rank error, and the before/after observable metric. J retainers are often compared to APL developer retainers for array programming work, as J is a descendant of APL (designed by Ken Iverson, the creator of APL, and Roger Hui), but J uses ASCII characters instead of APL’s special symbol set and extends APL’s rank system with explicit rank conjunctions and the tacit programming system. HourTab’s work log makes the rank conjunction repair and tacit train restructuring visible to clients who would otherwise see only the symptom — wrong numerical results — and not understand why the fix required understanding J’s rank system and how it interacts with boxed arrays.

Track J language developer retainer hours without the status emails

HourTab gives J developers a public URL per client retainer. One link, no login, live burn-down. Your clients stop asking “how many hours do I have left?” and your work log becomes the proof of value that gets the retainer renewed.

See HourTab pricing →

FAQ: J language developer retainers

What does a J language developer on retainer typically do?

A J language developer on monthly retainer covers four principal service areas: rank and axis correctness ("k rank conjunction application to verbs for cell-level processing; $ shape and #@$ rank assertions before rank-sensitive operations; /: grade up and \: grade down for ordering; |: transpose; , ravel and ,. stitch; </> boxing and unboxing pipelines); tacit programming and train design (fork/hook/atop train construction; @ atop and @: dyadic atop; &. under with automatic inverse; ^: power conjunction for iterated application; ` gerund and tie; m`n @. f agenda conditional); array verb design (+/"1 row-wise sum; */"1 row-wise product; <;._2 cut delimiter parsing; ;: tokenization; {\"1 column selection; i./#/$/,/|://:/\:/{./}./{:/}: core primitives); and J I/O and integration (1!:1/1!:2 file read/write; fread/fwrite; 15!: FFI for C library calls; JHS HTTP server configuration; JPM package installation).

What J language work is most commonly underlogged in a retainer?

Rank conjunction audit (verb applying at default infinite rank producing wrong-axis aggregation on non-rectangular boxed array; added "1 rank; wrong aggregations: 4/day → 0; 12–22 hrs invisible in rank analysis and shape tracing), tacit train restructuring (explicit verb with 7 intermediate assignments; restructured into fork train eliminating intermediate materialization; processing time: 8s → 0.4s; 14–24 hrs invisible in train composition verification), and under conjunction adoption (manual inverse computation in 3-step pipeline with 3 correctness bugs; restructured using &. with J’s automatic inverse; 3 bugs eliminated; 10–16 hrs invisible in &. inverse correctness verification).

What are typical J language developer retainer rates?

Entry-level J developers (1–2 years, basic verb/noun/adverb/conjunction grammar, i./#/$/, basic primitives, simple explicit verb definitions with {{ }}, basic rank-1 application) bill at $70–$125/hr. Mid-level J engineers (2–4 years, rank conjunction design for multi-dimensional array operations, tacit fork/hook/atop train composition, gerund and tie adverb design, &. under conjunction, J array processing for numerical data, J I/O via 1!: file verbs) bill at $115–$210/hr. Senior J architects (4–8 years, full J application architecture for numerical computing, JHS web server application design, complex tacit train systems with gerund-based polymorphism, J FFI via 15!:, J performance optimization using explicit rank specifications and boxed array avoidance, J package integration via JPM) bill at $170–$315/hr. Monthly retainer ranges: $2,400–$5,400/mo advisory (15–25 hrs), $7,500–$20,000/mo for full J array computing platform engagements.

What should a J language developer retainer agreement include?

A J language developer retainer agreement should specify: rank and axis scope ("k rank conjunction; $/#@$ shape/rank assertion; /:/\: grade up/down; |: transpose; ,/,./,: ravel/stitch/laminate; </>/; boxing); tacit programming scope (fork/hook/atop trains; @/@: atop; ^: power conjunction; &. under; ` gerund; m`n @. f agenda); array verb design scope (+/ sum; */ product; <;._2 cut; ;: tokenization; {\"1 column selection; core primitives i./#/$/ ,/|:/ /: /\:/{./ }./{:/ }:); I/O and integration scope (1!:1/1!:2 file I/O; fread/fwrite; 15!: FFI; JHS web server; JPM package manager); and hour logging format (verb name; rank error type; axis before/after rank fix; J version and platform).

How should J language developer retainer hours be logged?

Log each J retainer session with: advisory category ("k rank conjunction addition; $/#@$ shape assertion design; /:/\: ordering; fork/hook/atop train construction; @/@: atop composition; ^: power conjunction; &. under conjunction with automatic inverse; ` gerund and tie adverb; m`n @. f agenda conditional; +/"1 row-wise vs +/ whole-array aggregation; </> boxing pipeline; <;._2 cut parsing; 1!:1/1!:2 file I/O; 15!: FFI binding; JHS server session design), the specific verb name and the rank error (verb computing column-wise means on non-rectangular price matrix — +/ applying at default rank over raveled array instead of column-wise; added "1 rank conjunction; wrong means: 4/day → 0), and the before/after metric (wrong aggregations per day: 4 → 0; processing time: N → M seconds after tacit train restructuring; inverse computation bugs: 3 → 0 after &. adoption). Include J version, platform, and whether the fix required rank conjunction addition, tacit train restructuring, gerund redesign, or &. under adoption.