l>

documentation

Quickstart

First expressions, q-sql queries, file and CSV I/O, computing directly on compressed columns, and a little array math.

Most databases decompress a column before they can use it: ask for a max and every value expands to full width just to return one number. In l, a compact column stays fully usable — sortable, groupable, summable — through the same operations as any other value. Compression isn’t overhead you add and strip away; it’s the form the data computes in.

Start the REPL

With l on your path (see Installation), start a session:

l

You land at the l> prompt; \\ leaves.

l>1+1
2

Values, lists, tables

q reads right-to-left, no precedence, so 2*3+4 is 2*(3+4). Data is atoms, lists, dictionaries, and tables; a table is named columns side by side, so most of what follows is vector math.

l>2*3+4
14
l>til 5                              / a list
0 1 2 3 4
l>`a`b`c!10 20 30                    / a dictionary: keys!values
a| 10
b| 20
c| 30
l>t:([]sym:`IBM`MSFT`IBM`AAPL;px:120 340 121 175;sz:100 200 300 150)
l>t
sym  px  sz 
------------
IBM  120 100
MSFT 340 200
IBM  121 300
AAPL 175 150

Querying with q-sql

q-sql looks like SQL and reads right-to-left like the rest of q:

l>select sym,px from t where px>150
sym  px 
--------
MSFT 340
AAPL 175
l>select sum sz by sym from t        / group, then aggregate
sym | sz 
----| ---
AAPL| 150
IBM | 400
MSFT| 200
l>update val:px*sz from t            / derive a column
sym  px  sz  val  
------------------
IBM  120 100 12000
MSFT 340 200 68000
IBM  121 300 36300
AAPL 175 150 26250
l>exec sum sz from t                 / pull a single value out
750j

Data in and out

Values serialize to disk as-is and read straight back; tables round-trip through CSV with a header row.

l>`:/tmp/t set t                     / serialize the table (binary)
`:/tmp/t
l>t ~ get `:/tmp/t                   / read it back — identical
1b
l>`:/tmp/t.csv 0: csv 0: t           / write CSV
`:/tmp/t.csv
l>("SII"; enlist ",") 0: `:/tmp/t.csv   / read CSV back into a table
sym  px  sz 
------------
IBM  120 100
MSFT 340 200
IBM  121 300
AAPL 175 150

"SII" types the three columns as symbol, int, int; enlist "," sets the delimiter and asks for a table, taking column names from the header.

Compute on compressed

Assign a large, regular column to a name and l stores it compactly. Inspect it by name with -17!, which returns (compressed?; bytes stored; bytes raw):

l>v:10+til 1000000                   / a million consecutive integers
l>-17!`v
1b
40j
4000000j

Four million bytes to forty. Consecutive integers are almost entirely redundant, so l keeps a base and a step instead of the values. Inspect by name — the backtick matters: passing the value, -17!v, decompresses it first. For a friendlier spelling:

l>.Q.cinfo:{-17!x}
l>.Q.cinfo`v
1b
40j
4000000j

Every verb runs on the compressed column directly, without decompressing it:

l>sum v
500009500000j
l>max v
1000009
l>count where v>1000005
4

Not everything compresses. Store a table and inspect each column:

l>trade:([]sym:1000000?`A`B`C;price:1000000?100.0;size:100*1000000?50)
l>-17!`trade
sym  | 1b 4000032j 8000000j
price| 0b 8000000j 8000000j
size | 1b 2000032j 4000000j

Symbols and sizes compress; the random prices report 0b and keep full width — random doubles have no structure to exploit.

A little math, a little AI

q is also a numeric engine — whole-array math and matrices. A numerically stable softmax is one line:

l>softmax:{e%sum e:exp x-max x}
l>softmax 1 2 3 4.0
0.0320586 0.08714432 0.2368828 0.6439143

A dense layer is a matrix times a vector; the activation is just max against zero:

l>w:(0.1 0.2 0.3;-0.4 0.1 -0.2)      / a 2x3 weight matrix
l>w mmu 1 2 3.0                      / two pre-activations
1.4 -0.8
l>{0.0|x} w mmu 1 2 3.0              / relu zeroes the negative unit
1.4 0

Stored compressed, those weight rows are multiplied on their packed values — the same bandwidth saving, applied to linear algebra.