JNJ provides bindings to the J language to be used in Janet code.
J is a powerful array programming language in the APL lineage. It has a few features that might make it useful to expose from within a more general-purpose language:
JNJ requires the presence of libj.{so,dylib,dll}
, provided by the J
Language. You might need to install an -devel
package for J depending
on your package manager.
It will attempt to load libj.so
at runtime. If libj.so
is not present in
your search path for dlopen()
, you can specify the path when you install
jnj
:
LIBJ_DIR=/directory/containing/sofile jpm install https://git.sr.ht/~subsetpark/jnj
See eval/eval*
and j/j*
for details.
jnj
is designed to allow you to use J from within Janet. Thus, it has two
main functions:
To do so, jnj introduces two main abstract types:
All J computations take place within an instance of the J Engine. For
convenience, a default instance is created at import time and available at
jnj/j-engine
. The eval
and j
commands both use this default instance,
while eval*
and j*
allow you to specify your own.
All of the above functions have the same basic signature (modulo the presence or absence of an engine argument): they take a verb and 0, 1, or 2 arguments.
The difference is that j
and j*
will evaluate their J command and return
native Janet datatypes. eval
and eval*
, on the other hand, will return
the other abstract type, a J Array.
J Arrays can be inspected for rank, shape, etc., but they can also be passed
as arguments to further eval
/j
calls. Thus, they’re an efficient way to
perform more complex procedures, involving multiple calls to the J
interpreter, without having to convert data in and out of the J
representation. They are also the most efficient and convenient way to create
multi-dimensional arrays, as opposed to nesting multiple Janet array/tuples.
eval
/j
The verb can be any J sentence, verb or verb sequence. It can be a symbol or a string.
If there are no arguments, the sentence will be evaluated and returned.
repl:3:> (jnj/j "3 4 $ i. 10")
((0 1 2 3) (4 5 6 7) (8 9 0 1))
Arguments should be native Janet terms or a J Array. If they’re present,
the verb will be applied to them. If there’s a single argument Y, it will be
evaluated in the form <VERB> <Y>
. If there are two arguments, X and Y, they
will be evaluated in the form <X> VERB <Y>
.
repl:4:> (jnj/j "$" [3 4] (range 10))
((0 1 2 3) (4 5 6 7) (8 9 0 1))
repl:5:> (def mat (jnj/eval "$" [3 4] (range 10)))
<jnj/jarray float [2/12]>
repl:6:> (jnj/j '$ mat)
(3 4)
In the first example, we evaluate a J sentence using native datatypes and return the result as native datatypes. In the second, we evaluate the same command and return the result as a J Array, which we can then use as an argument in subsequent commands.
let-j
The let-j
/let-j*
convenience macros provide a simple wrapper around
eval
and j
which is more expressive and efficient.
The above example could also be written as:
repl:1> (let-j [mat ("$" [3 4] (range 10))]
('$ mat))
(3 4)
to-j-array
As noted above, JNJ provides the J Array abstract type as a way to efficiently store J values. J provides an efficient and terse way to construct multi-dimensional arrays; however, if you have some existing Janet data which you want to use as an argument to J, you can convert it:
repl:2:> (to-j-array [[1 1 1][2 2 2][3 3 3]])
<jnj/jvalue float [2/9]>
repl:3:> (j "+/" _)
(6 6 6)
eval, eval*, from-j-array, j, j*, j-engine, jnj-primitives/init, let-j, let-j*, to-j-array
function | source
(eval verb & args)
Evaluate verb
and args
with the default J engine instance (see eval*
for details).
function | source
(eval* je verb & args)
Evaluate verb
with arguments args
in the context of je
. Returns a
j-array abstract type which can be used for further evaluations, or converted
into a tuple matrix.
function | source
(from-j-array res)
Turn a j-array abstract type result into a Janet term of the appropriate shape:
function | source
(j verb & args)
Evaluate verb
with args
in the default J engine instance (see j*
for
details).
function | source
(j* je verb & args)
Evaluate verb
with args
in the context of je
, returning a native Janet
datatype:
nil | source
The default J instance, used by jnj/j
.
function | source
(jnj-primitives/init &)
macro | source
(let-j bindings expr)
Evaluate let-j bindings
and expr
in the default J engine instance (see
let-j*
for details).
macro | source
(let-j* je bindings body)
Use a series of intermediate bindings to compute a complex J value and convert it back to Janet.
bindings
should be, like with let
, alternating binding keywords and J expressions.
A binding keyword is any keyword, which can then be referred to in subsequent expressions.
A J expression is a tuple of the form that can be passed to eval*
or j*
-
an arbitrary symbol-or-string sentence followed by 0, 1 or 2 args. In
addition to the arg types understood by eval*
and j*
, an arg can also be
a keyword, in which case it will refer to the result bound to that keyword
earlier in the bindings
form.
Finally, body
is the J expression to be evaluated - again, a sentence
followed by 0, 1 or 2 arguments, where the arguments can include any keywords
specified in the bindings.
Here’s an example that creates a 4x3 matrix, gets its shape, and then sums the shape:
> (let-j* je
> [x ("$" [3 4] [0 1])
> y ("$" x)]
> ('+/ y))
7
Because the intermediate expressions in let-j only consist in the J engine, it can also be used to handle datatypes not yet understood by JNJ, like boxes:
> (let-j* je
> [box ("<" "foo")
> arglist (";" box "bar")]
> ("#" arglist))
2
This same fact makes let-j operations somewhat more efficient than multiple
eval
calls, as the intermediate values don’t need to be copied back into
the J runtime.
function | source
(to-j-array matrix)
Turn an arbitrarily nested array/tuple of numbers into a J Array.
NB: This function performs some validation on its input to assert that it can
be transformed into a J Array. So if the data is not already in Janet form,
it might be more efficient to generate it using jnj/eval
.