# J


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

`basedpl.j` runs the [J language](https://www.jsoftware.com/) inside the
Python process. It loads libj, the engine library in every J
installation, so no separate J process runs. The module provides the `J`
session class, the `%%j` and `%j` magics, and a Jupyter kernel for J.
Importing it does not start J.

Install J with `pip install jlanguage`, or from
[jsoftware.com](https://www.jsoftware.com/). The Python layer also needs
IPython.

``` python
from fastcore.test import *
```

## Finding J

------------------------------------------------------------------------

### find_j

``` python
def find_j():
```

*Locate the J binary directory: the one containing libj and profile.ijs*

`find_j` checks for libj beside `jconsole`, resolving symlinks first.
The `jlanguage` package installs a `jconsole` script without libj beside
it, so `find_j` asks the `jlang` module for that installation’s path. It
also checks common installation directories.

``` python
jbin = find_j()
assert (jbin/_LIBJ).exists()
jbin.parts[-2:]
```

    ('jlang', 'bin')

## Sessions

------------------------------------------------------------------------

### J

``` python
def J(
    jbin:NoneType=None
):
```

*A J session: an engine from libj, with J’s standard library loaded*

`J` starts an engine, then runs `profile.ijs` to load J’s standard
library. Pass `jbin` to choose a J installation other than the one
`find_j` returns.

------------------------------------------------------------------------

### J.\_\_call\_\_

``` python
def __call__(
    code
):
```

*Run `code`, returning displayable output, or None if there is none*

------------------------------------------------------------------------

### J.run

``` python
def run(
    code
):
```

*Run `code` (one or more lines of J), returning its output; raises
`JError` on J errors*

`run` returns J’s output as text. Calling the session returns the same
text as `AplOut`, which displays verbatim, or `None` when there is no
output. Names persist between calls:

``` python
j = J()
j('m =: 2 3 $ 10 * 1 + i. 6\nm')
```

<pre class="aplnb_out sax2">10 20 30
40 50 60</pre>

``` python
test_eq(j.run('+/ , m'), '210\n')
test_is(j('m2 =: 10 * m'), None)
```

Lines that open a multi-line definition take the following lines as its
body:

``` python
j('mean =: 3 : 0\n(+/ y) % # y\n)\nmean 1 2 3 4')
```

<pre class="aplnb_out sax2">2.5</pre>

A J error raises `JError`. Its message is the session’s output,
including J’s error display:

``` python
with expect_fail(JError, contains='domain error'): j.run("m + 'x'")
```

Extended precision works, as in 25 factorial:

``` python
j('*/ 1 + i. 25x')
```

<pre class="aplnb_out sax2">15511210043330985984000000</pre>

## Python values

`getm` reads a named J noun as Python data. Characters become a string.
Booleans, integers and floats become a number, or nested lists for an
array. Boxed, extended and rational values raise `JError`.

------------------------------------------------------------------------

### J.getm

``` python
def getm(
    name
):
```

*Read noun `name` into Python: str for characters; int/float scalars and
(nested) lists otherwise*

``` python
test_eq(j.getm('m'), [[10,20,30],[40,50,60]])
j('x =: 1r3')
with expect_fail(JError, contains='unsupported J type'): j.getm('x')
```

`j[expr]` evaluates an expression and returns its value as Python data.
It assigns the result to `pytmp`, reads it, then erases that name.

------------------------------------------------------------------------

### J.\_\_getitem\_\_

``` python
def __getitem__(
    expr
):
```

------------------------------------------------------------------------

### J.pyval

``` python
def pyval(
    expr
):
```

*Evaluate `expr` and return the result as a Python value*

``` python
test_eq(j['m > 25'], [[0,0,1],[1,1,1]])
test_eq(j['+/ % # 1 2 3 4'], 0.25)
j["'py' , 'val'"]
```

    'pyval'

`j[name] = value` assigns Python data. A string becomes a J character
list. A number or nested list becomes an integer array, or a float array
if any item is a float.

------------------------------------------------------------------------

### J.\_\_setitem\_\_

``` python
def __setitem__(
    nm, v
):
```

*Assign Python value `v` (scalar, string, or nested list) to noun `nm`*

``` python
j['q'] = [[1,2],[3,4.5]]
j['s'] = "it's"
test_eq(j['s'], "it's")
j['+/ , q']
```

    10.5

A ragged list has a different number of items than its shape implies, so
assigning it raises `JError`:

``` python
with expect_fail(JError, contains='shape'): j['r'] = [[1,2],[3]]
```

`fn` turns a J verb into a Python callable. Pass one argument for
monadic use, or two for dyadic use with the left argument first.

------------------------------------------------------------------------

### J.fn

``` python
def fn(
    code
):
```

*A Python callable applying J verb `code` monadically or dyadically
(left argument first)*

``` python
sq = j.fn('*:')
test_eq(sq([1,2,3]), [1,4,9])
test_eq(j.fn('+/')([1,2,3]), 6)
j.fn('{.')(2, [5,6,7])
```

    [5, 6]

## Interrupting

`interrupt` stops a running sentence with a J attention interrupt. Call
it from another thread, because the thread running J cannot run Python
code until J returns. The session stays usable after the interrupt.

------------------------------------------------------------------------

### J.interrupt

``` python
def interrupt():
```

*Stop the running sentence with an attention interrupt; call it from
another thread*

``` python
import threading, time
from concurrent.futures import ThreadPoolExecutor
```

``` python
j('spin =: 3 : 0\nn =. 0\nwhile. n < 1e9 do. n =. n + 1 end.\n)')
t0 = time.time()
threading.Timer(0.5, j.interrupt).start()
with expect_fail(JError, contains='attention interrupt'): j('spin 0')
assert time.time()-t0 < 5
j('2+2')
```

<pre class="aplnb_out sax2">4</pre>

`interrupt` is the only method that works from another thread. J sets
its recursion limit from the stack of the thread that created the
session. So a session runs J only on that thread, and raises `JError` on
any other:

``` python
with expect_fail(JError, contains='thread'): ThreadPoolExecutor().submit(j.run, '2+2').result()
```

## Exit requests and closing

`close` frees the engine. A `with` block calls `close` when it ends. A
closed session cannot run code.

------------------------------------------------------------------------

### J.\_\_exit\_\_

``` python
def __exit__(
    *args
):
```

------------------------------------------------------------------------

### J.\_\_enter\_\_

``` python
def __enter__():
```

------------------------------------------------------------------------

### J.close

``` python
def close():
```

*Free the engine; the session cannot run code afterwards*

`exit 7` asks J to exit with code 7. The session records the code in
`exited` and runs no more lines, without raising an error. The host
decides what an exit request means.

------------------------------------------------------------------------

### J.exited

``` python
def exited():
```

*The exit code requested by `exit`, or None*

``` python
with J() as j2:
    test_is(j2.exited, None)
    j2('exit 7')
    test_eq(j2.exited, 7)
    test_is(j2('2+2'), None)
```

## Magics

Run `%load_ext basedpl.j` to register two magics in IPython or Jupyter:

- `%%j` runs a cell and displays its output verbatim. A trailing `;`
  hides the output.
- `%j expr` returns the value of `expr` as Python data, as `j[expr]`
  does.

The magics share one session, which starts on the first call.

------------------------------------------------------------------------

### JMagic

``` python
def JMagic(
    jbin:NoneType=None
):
```

*IPython `%j`/`%%j` magics, driving a lazily-started `J` session*

`load_ipython_extension` runs when IPython loads the extension. It calls
`create_j_magic`, which registers a `JMagic` with any IPython shell:

------------------------------------------------------------------------

### load_ipython_extension

``` python
def load_ipython_extension(
    ipython
):
```

*Register the `j` magics: `%load_ext basedpl.j`*

------------------------------------------------------------------------

### create_j_magic

``` python
def create_j_magic(
    shell:NoneType=None
):
```

*Create a `JMagic` and register its `j` line/cell magic with `shell`,
returning it*

``` python
%%j
m3 =: 3 3 $ i. 9
m3 +/ . * m3
```

<pre class="aplnb_out sax2">15 18  21
42 54  66
69 90 111</pre>

`%j` returns Python data, which you can assign:

``` python
z = %j m3
z
```

    [[0, 1, 2], [3, 4, 5], [6, 7, 8]]

A trailing `;` hides a cell’s output:

``` python
%%j
big =: 1000 1000 $ i. 5
big + big;
```

## The J kernel

The J kernel runs J cells in Jupyter clients such as JupyterLab,
nbclient, and agents using clikernel. Names persist across cells. An
interrupt stops a computation and keeps the session. Install the kernel
with `python -m basedpl.j install`. Jupyter then lists it as J.

------------------------------------------------------------------------

### install_j_kernel

``` python
def install_j_kernel(
    prefix:NoneType=None, # Install under `prefix/share/jupyter/kernels` if given, else in the user Jupyter directory
):
```

*Register the J kernel with Jupyter as kernelspec `j`, returning its
directory*

`install_j_kernel` writes a kernelspec named `j`. It starts the kernel
with `python -m basedpl.j CONNECTION_FILE`, using the current Python.
Pass `prefix` to install into an environment, such as `sys.prefix`,
instead of your user Jupyter directory.

`run_j_kernel` serves the kernel. It runs J on a Rust thread, through
the same engine as `J`. Before the first request, it runs
`basedpl/startup.ijs` from your XDG configuration directory if that file
exists. The default path is `~/.config/basedpl/startup.ijs`.

------------------------------------------------------------------------

### run_j_kernel

``` python
def run_j_kernel(
    connection_file
):
```

*Serve a J kernel on the Jupyter connection in `connection_file`*

Running the module serves a kernel, or installs one when its argument is
`install`:

## Limitations

- `%j` and `j[expr]` return booleans, integers, floats and characters as
  Python data. Boxed, extended and rational values raise `JError`. Use
  `%%j` to display them.
- macOS and Linux are supported. The libj binding is untested on
  Windows.

## Learning J

Start with [Learning
J](https://www.jsoftware.com/help/learning/contents.htm) and the [J
wiki](https://code.jsoftware.com/wiki/). Its
[NuVoc](https://code.jsoftware.com/wiki/NuVoc) page is the reference for
every primitive.
