Verbs
An Implementation of J
typedef A(*AF)(); #define F1(f) A f(J jt, A w); #define F2(f) A f(J jt,A a,A w);AF is the data type of a function having these properties. F1 and F2 are used to specify the headers of functions implementing verbs (and adverbs and conjunctions); the majority of functions in the implementation are so specified. (jt is the global variables parameter; a and w denote a and w, traditionally the names given to the left and right arguments of APL functions.) Verbs are represented by arrays of type VERB; the details of this representation are discussed in Adverbs and Conjunctions.
File vm.c | File je.h | ||
F1(jtjdot1){R tymes(a0j1,w);} | extern F1(jtjdot1); | ||
F2(jtjdot2){R plus(a,tymes(a0j1,w));} | extern F2(jtjdot2); |
/* i: */ pdef(CICO, VERB, jtjico1, jtjico2, 0L, RMAX,RMAX); /* I. */ pdef(CICAP, ADV, jticap, 0L, 0L, 0L, 0L ); /* j. */ pdef(CJDOT, VERB, jtjdot1, jtjdot2, 0L, 0L, 0L ); /* L. */ pdef(CLDOT, VERB, jtlevel1, 0L, RMAX,0L, 0L ); /* L: */ pdef(CLCO, CONJ, 0L, jtlco, 0L, 0L, 0L );The macro ds(x) is defined as pst[(UC)(x)], and is a convenient reference for a primitive; for example, ds(CJDOT) is the verb j. as an array (in short, ds(CJDOT) is j.).
j. %&0j1 n&j. %&0j1@(-&n) j.&n -&(j.n)The obverse of j. is implemented as case CJDOT in inv; those for n&j. and j.&n are implemented as case CJDOT in invamp. The identity function for j. is $&0@(}.@$), and is implemented as case CJDOT in iden. j. has no variants; the implementation of a variant would have required a case in fit.
Rank
Verb (function) rank was introduced by Iverson [1978 §6],
further developed in Iverson [1983, 1987, 1995],
and implemented in SHARP APL, SHARP APL/HP, SAX, A, and J
(see Bernecky et al. [1983, 1987], Hodgkinson [1986],
Steinbrook [1986], Whitney [1989], and Hui et al. [1990],
respectively). This description first appeared in Hui [1995].
A verb of rank r is defined on arguments
with rank bounded by r;
the extension to higher-rank arguments is the same for all verbs.
The rank conjunction " (operator) augments the default ranks of
a verb by user-specified ranks. It provides for the
generalization of a verb to higher-rank arrays,
and could justifiably be called the generalization
or extension operator; it also provides for consistent application
to lower-rank arrays, subsuming and superseding
the anomalous bracket-axis operator.
Various aspects of rank are here discussed in terms of a model in J,
updated from Hui [1987 §A.2].
Frames and Cells. A rank r splits the argument shape into
the frame and the cell shape; a positive r specifies the number
of trailing cell axes, while a negative r specifies the
negative of the number of leading frame axes.
rk =: #@$ er =: (0:>.(+rk))`(<.rk) @. (0:<:[) fr =: -@er }. $@] cs =: -@er {. $@] boxr =: ]`(<@$ , [ $: */@[}.])@.(*@#@]) cells =: fr $ cs boxr ,@]For rank r and argument y, the phrase r er y computes the effective rank (non-negative and bounded by #$y); r fr y computes the frame and r cs y the cell shape; and r cells y computes the array of cells with shape r fr y, each cell individually boxed and shaped s=: r cs y (r cells y is <"r y). The recursively-defined verb s boxr y produces the list of such cells.
x=:1 2 3 y=:i.3 2 y x*"0 _1 y 0 1 0 1 2 3 4 6 4 5 12 15 0 er x _1 er y 0 1 0 fr x _1 fr y 3 3 0 cs x _1 cs y 2 0 cells x _1 cells y ┌─┬─┬─┐ ┌───┬───┬───┐ │1│2│3│ │0 1│2 3│4 5│ └─┴─┴─┘ └───┴───┴───┘Agreement. In the dyad v"r, commonly the left and right frames match, that is, the two cell arrays have the same shape; if not, several design choices are possible: In scalar agreement, one frame must be empty, and the single cell is reshaped using the other frame; in suffix agreement, one frame must be a suffix of the other, and again the list of cells is reshaped using the other frame; finally, in prefix agreement, one frame must be a prefix of the other, and each cell is reshaped with the excess in the other frame. All three agreements are proper generalizations of scalar extension in APL\360, with cells acting the role of scalars. Agreement results in the two cell arrays having the same shape ("the frame").
pfx =: <.&rk agree =: (pfx {. $@[) -: (pfx {. $@]) frame =: [:`($@([^:(>&rk))) @. agree rag =: frame $ ([: */ rk@]}.$@[) # ,@] lag =: rag~rag and lag apply to both cell arrays (the results of cells in the previous section), producing cell arrays with the same shape. If v"r itself were used in the model, rag could be defined more directly from the specification: (rk@]}.$@[) $"1 0 ] — each cell is reshaped with the excess in the other frame. In the continuing example, rag and lag have no effect because the left and right frames match.
[xc=.0 cells x [yc=._1 cells y ┌─┬─┬─┐ ┌───┬───┬───┐ │1│2│3│ │0 1│2 3│4 5│ └─┴─┴─┘ └───┴───┴───┘ [xa=.xc lag yc [ya=.xc rag yc ┌─┬─┬─┐ ┌───┬───┬───┐ │1│2│3│ │0 1│2 3│4 5│ └─┴─┴─┘ └───┴───┴───┘Assembly. After agreement, the phrase v&.> applies v under > to corresponding boxed left and right argument cells, to produce an array of boxed result cells. It remains to assemble the overall result from the individual results.
mrk =: >./@:(rk&>)@, crank =: mrk ,:@]^:(-rk)&.> ] msh =: >./@:( $&>)@, cshape=: <@msh {.&.> ] asm =: > @ cshape @ crank rank =: 2 : 0 'm l r'=.3&$&.|.y. ([: asm [: x.&.> m&cells) : ([: asm l&cells@[ (lag x.&.> rag) r&cells@]) )The conjunction rank integrates the model components. The left argument x. is the verb v; the right argument y. is reshaped from the right to exactly 3 numbers and assigned to m, l, and r.
[ za=. xa *&.> ya ┌───┬───┬─────┐ │0 1│4 6│12 15│ └───┴───┴─────┘ asm za 0 1 4 6 12 15 x * rank 0 _1 y 0 1 4 6 12 15Zero Frame. If the frame contains 0 (as in 3*"1 i.0 4), there are no argument cells to apply v to, and the shape of a result cell (the value of sir) is indeterminate. Pesch [1986] describes a variety of strategies to address this problem. In J, the shape is calculated if v is uniform (see below); otherwise v is applied to a cell of fills.
A rank1ex( A w,A self,I m, AF f1); A rank2ex(A a,A w,A self,I l,I r,AF f2); F1RANK(m, f1,self); F2RANK(l,r,f2,self);a and w are the left and right arguments of the verb; f1 and f2 are functions which implement the monad and dyad; m,l,r are ranks; and self is an array representing the verb. For example, the dyad ": has ranks 1 _ and is implemented by the function thorn2, which uses F2RANK as follows:
F2(jtthorn2){PROLOG;A da,ea,h,ma,s,y,*yv,z;B e,*ev; ... F2RANK(1,RMAX,jtthorn2,0); an=AN(a); t=AT(w); ... }If the argument ranks are not greater than the verb ranks, then F2RANK (F1RANK) does nothing, and execution proceeds to the statement following the macro; if the argument ranks are greater, then F2RANK (F1RANK) invokes rank2ex (rank1ex), and on return therefrom exits f with the result obtained therefrom. In this scheme, rank2ex (rank1ex) invokes f repeatedly, but with arguments of rank bounded by the verb ranks.
Atomic (Scalar) Verbs
Not Yet Available
Obverses, Identities, and Variants
Verbs have additional parts — obverse, identity, and variants —
which can not be specified as static data structures.
Such information is embodied in functions.
• Obverses
A verb u is an obverse (usually the inverse) of a verb v
if x=u v x
for a significant subdomain of v.
The obverse is used in the conjunctions under
(&.) and power (^:).
For example, exponential ^ and logarithm ^.
are obverses, and:
3 +&.^. 4 is ^ (^.3) + ^.4 ^ ^:_1 is ^. 3 *&.^ 4 is ^.(^ 3) * ^ 4 ^.^:_1 is ^Obverses are produced by the function inv in file ai.c. (inv implements ^:_1.) The logic is a combination of table look-up and nested branch tables (switch/case).
Error Handling
When an error is encountered in a function, the global
variable jerr is set to an error number, and zero is returned.
Therefore, when calling a function that can not have zero as a valid result
(but does return a result), the returned value must be checked for zero;
when calling a "void" function or one whose range
includes zero, jerr must be inspected.
Error numbers range between 1 and NEVM,
and are referenced by the EV* names (file jerr.h).
The function jsignal (d.c)
applies to an error number, sets jerr to this number, and
displays the appropriate error message; jsignal exits
immediately if jerr is already nonzero.
evm is a list of the error messages.
These messages are initialized by
function evinit (file i.c),
and may be inspected and changed by the user
through 9!:8 and 9!:9.
The macro ASSERT (file j.h) is used
extensively in argument validation.
It applies to a proposition and an error number. For example,
the following statements check whether w is a literal atom:
ASSERT(!AR(w),EVRANK); ASSERT(LIT&AT(w),EVDOMAIN);If the proposition is nonzero, execution proceeds to the next statement; otherwise, the indicated error is jsignal-ed and a zero is returned. The macros RZ and RE (file j.h) are used in function calls. RZ returns zero if its argument is zero; RE evaluates its argument, and returns zero if jerr is nonzero. For example, the function iota (implementing the monad i.) exploits RZ as follows:
F1(jtiota){A z;I m,n,*v; F1RANK(1,jtiota,0); if(AT(w)&XNUM+RAT)R cvt(XNUM,iota(vi(w))); RZ(w=vi(w)); n=AN(w); v=AV(w); if(1==n){m=*v; R 0>m?apv(-m,-m-1,-1L):apv(m,0L,1L);} m=prod(n,v); z=reshape(mag(w),apv(ABS(m),0L,1L)); DO(n, if(0>v[i])z=irs1(z,0L,n-i,jtreverse);); R z; }The arguments of a function may be the result of another function; the convention is that a function checks its arguments for zero and returns zero immediately in such cases. Thus, in iota above:
z=reshape(mag(w),apv(ABS(m),0L,1L));If reshape did not check for zero arguments, the statement would have to be elaborated:
RZ(t0=mag(w)); RZ(t1=apv(ABS(m),0L,1L)); z=reshape(t0,t1);A conventional function is a function that follows the conventions described herein — return zero on zero arguments and on errors. The defined type AF (file jtype.h) typifies a conventional function. Most functions in the system are conventional; in particular, all functions implementing primitives are conventional. Expressions and statements that use only conventional functions need not employ RZ or RE, and the resulting programs are neater. For example, consider functions shape and nub (file v.c), implementing the monads $ and ~., respectively:
F1(jtshape){RZ(w); R vec(INT,AR(w),AS(w));} F1(jtnub){R repeat(nubsieve(w),w);}shape must check for zero arguments RZ(w), because it applies the unconventional macros AR and AS to the argument w. In contrast, nub applies only conventional functions to its argument and to results of conventional functions on that argument.