Introduction

The following document is recommended for people with previous programming experience, but with no prior experience with APL or any of its cousins, such as J, K, Q, BQN, etc.

What KAP is and isn’t

About APL

At its core, KAP is a programming language based on APL.

APL is a programming language invented by Ken Iverson in 1966 and is particularly famous for its use of non-ASCII symbols. There exists many implementations of APL today, with the most commonly used one being Dyalog. Dyalog is a commercial product which is free for non-commercial use.

At the risk of scaring away a beginner, here is a classic example of APL code (which also happens to be valid KAP code):

{⊃1 ⍵ ∨.∧ 3 4 = +/ +⌿ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}

This is a function that computes an iteration of Conway’s Game of Life. The above example can also be found in the Wikipedia page for APL.

How KAP is different from APL

APL requires the programmer to take a very different approach to programming compared to most other programming languages. In particular, it strongly de-emphasises the use of loops. Traditional APL doesn’t even have a looping constructs or if statements, with the only way to do flow control is to use goto. Dyalog has looping constructs, but they are still cumbersome to use.

APL has often been been referred to as a “functional” programming language. This is partially true, but it does lack first-class functions. First-class functions refers to the ability to work with functions as if they were values themselves. For example, you could create an array of functions. Some APL derivatives such as BQN supports this concept, and Dyalog can also do it, albeit in a very roundabout way.

KAP grew out of a desire to see if it was possible to nicely integrate these programming constructs together with APL. The question as to whether that goal has been achieved is left to the reader to determine.

Another difference that makes KAP almost unique among APL dialects is that it is centred around lazy evaluation.

What KAP can be used for

If the reader is asking themselves if they are supposed to build the software supporting their new startup in KAP, then the answer is almost certainly no (although if they do, the author would very much like to hear about it).

Currently, the language can be seen as an alternative to Excel when working with data in array form. Excel is great for visualising data, but performing calculations with it quickly becomes difficult as the code tends to be hidden in cells in the document. For a programmer, this artefact of Excel is probably the most frustrating, since the code is arguably more important than the data itself.

The best way to get started is to start using KAP as a calculator by running the GUI frontend and simply typing expressions to compute things. When users of other programming languages may take to an IDE and start writing tens or even hundreds of lines of code to perform certain calculations, KAP can do the same in sometimes just a single line of code. It is therefore often not necessary to actually open an editor to write a program, and the user can instead simply perform their calculation directly in the tool itself.

Using the KAP software distribution

Running KAP

Download

Currently, KAP is only available in source form and can be downloaded from Github. The source code can be downloaded using the following command:

git clone https://github.com/lokedhs/array

Running the UI

There are different ways to run KAP, but the easiest way to get started is to use the built-in UI. The client can be started using the following command:

./gradlew gui:run

This is the recommended way to run KAP at the moment, as it implements all language features as well as provides an easy way to enter the all the symbols used by the language.

Trying KAP online

KAP can be compiled to Javascript, and a simple web-based demo is available on https://kapdemo.dhsdevelopments.com/

Note that currently this website is very limited, and the user experience is not great. In particular, it does not provide a simple way to enter the special symbols.

Inputting APL symbols

The use of non-ASCII symbols is something that many beginners find complicated. It is possible to configure a separate input keymap in most operating systems that support APL symbols. However, the easiest way for a beginner to start using KAP is to use the graphical UI. It provides a REPL as well as a text editor that provides easy access to APL symbols using two different mechanisms:

  • Press Alt or Meta together with other keys.
    For example, to generate type Meta+r.

  • Press ` followed by other keys.
    Type ` followed by Space to generate a `.

KAP basics

Printing text

The first thing anyone learning a new language wants to know is how to print a message to the screen. In KAP, printing is done using io:print or io:println. The difference between the two is that the latter adds a newline after the output.

The following code prints a string to the screen:

io:println "Example of printing a string"

The name of the function is println, which resides in the io namespace. It is possible to import a namespace so that one does not have to specify the namespace every time the function is used. This is not done in this tutorial in order to make it very clear which namespace a given symbol belongs to. Many common functions belong to the kap namespace which is imported by default. Those symbols are given without the namespace.

When using the REPL to enter KAP expressions, the return value of the expression is always printed to the screen. This is the reason why when typing the above example into the REPL, the string will be printed twice. The first is the result of the call to io:println and the second is the return value of the expression. io:println always returns the value it printed, which results in the double output.

Comments

Comments in KAP are indicated using the symbol and extends to the end of the line.

⍝ This entire line is a comment
io:println "This will be printed"    ⍝ This is another comment

Mathematical functions

Just like any other programming languages, KAP provides functions to perform mathematical computations, the main ones are:

  • + — addition

  • - — subtraction

  • × — multiplication

  • ÷ — division

  • * — exponentiation

  • | — modulo

In KAP, just like in most APL implementations, evaluation happens from right-to-left. This is probably the biggest difference compared to other languages. This means that the following:

3×4+5

evaluates to 27. In other words, it’s interpreted as 3×(4+5). This may seem somewhat strange, but the decision to interpret the code like this provides two distinct benefits: First and foremost, it removes any ambiguity as to the order in which computation will be performed. Secondly, it reduces the number of parentheses that are needed when writing complex code.

Variables

Variables in KAP can be global, or they can have local bindings. The difference between the two types of bindings will be obvious later in the tutorial, but for now this distinction can be ignored.

Variables have names starting with an alphabetic character or underscore, followed by zero or more alphabetical characters, digits or underscore. A variable is assigned using like this:

foo ← 123        ⍝ Assigns the value 123 to the variable foo
bar ← 1+2+3      ⍝ Assigns the value 6 to the variable bar
xyz ← foo + bar  ⍝ Assigns the value 129 to the variable xyz

Statement separators

Individual statements are separated either by a newline or the symbol . Thus, the following:

io:println a
io:println b

is equivalent to:

io:println a ⋄ io:println b

Monadic and dyadic function invocation

Two terms that any beginner learning APL will quickly come across are the terms monadic and dyadic. These terms refer to the two different ways in which a function can be called:

  • Monadic function invocation takes its argument to the right of the name.
    Example, assuming FN is the name of the function: FN arg0.

  • Dyadic function invocation takes two arguments on each side of the function name.
    Example: arg0 FN arg1.

The call to io:println above is monadic, in that the function argument is to the right:

io:println rightArg   ⍝ The value rightArg is to the right of the function name

An example of a dyadic invocation that we’ve already seen is the invocation of the function +:

10 + 11               ⍝ The function + is called with two arguments: 10 and 11

It is important to note that there is nothing special about +. It’s a regular function just like io:println. It just happens to consist of a single character instead of a word. KAP allows the programmer to define their own functions with a single character name and the details on this will be explained later in this document.

Functions can support monadic, dyadic or both kinds of invocations. An example of a function that allows both monadic and dyadic invocation is -:

foo - 3               ⍝ Compute the result of 3 subtracted from foo
-foo                  ⍝ Negate the value of foo (if foo was 10, then the result is -10)

The rule for deciding whether a function invocation is monadic or dyadic is that if there is anything to the left that is a valid argument, then it’s a dyadic invocation, otherwise it’s monadic. An example will help clear this up:

10×-2                 ⍝ Result is -20

Looking from the right, the rightmost - does not have a value to the left (to the left of the - is a × symbol), and it must therefore be a monadic invocation resulting in the value -2. The call to × is dyadic since it has a 10 to the left, so the result is the product of 10 and -2 which is -20.

Working with arrays

KAP programming is about arrays. While the language has other container types, the array is the main way that data is organised. Arrays are particularly important in KAP because a lot of functions are designed to work on sets of data using a single operation. The reason array languages can get away with having poor support for flow control is that in many cases they are not needed, since one does not usually have to iterate over multiple values, and instead perform a single operation that acts on arrays of data in one go.

While KAP has stronger flow control constructs than most other array languages, the language’s strength is still the focus on arrays, which is why this topic is explained even before discussing how flow control works.

Constructing arrays

Literal arrays

In many languages, arrays have only a single dimension. Taking Javascript as an example, an array is a collection of objects which is given inside square brackets:

// The following is Javascript code:
var foo = [1, 2, "string", [4, 5]];

In the example above, the array consists of 4 values. The first two are numbers, the third is a string and the fourth is another array that contains two more numbers.

In KAP, the same declaration would look like this:

value ← 1 2 "string" (4 5)

As can be seen from the comparison above, KAP parses everything as arrays by default. If more than one value is given separated by spaces, the individual values will be concatenated together and interpreted as a 1-dimensional array.

Iota function

It is very common to need an array consisting of numbers in ascending order. For example, a 5-element array containing the values 0 1 2 3 4. In fact, this is so common that a function is provided to do exactly this: , called “iota”.

When invoked monadically, accepts an argument indicating the size of the resulting array:

    ⍳5
┏━━━━━━━━━┓
┃0 1 2 3 4┃
┗━━━━━━━━━┛

The function will be used in a lot of examples below.

Accessing data in arrays

Reading single values

KAP uses the square brackets to read values from an array, so to read second value from a 1-dimensional array, one would use the following syntax:

foo ← 10 11 12 13 14 15 16
bar ← foo[1]                      ⍝ bar now contains the value 11

All arrays are zero-indexed, meaning that the first element (the value 10) can be read using foo[0], the second using foo[1], etc.

Reading multiple values

The value inside square brackets (i.e. the index) does not need to be a single number. One can specify an array as in index itself. The result will be an array with the corresponding values for each index. For example:

    foo ← 10 11 12 13 14 15 16
    foo[4 5 0]
┏━━━━━━━━┓
┃14 15 10┃
┗━━━━━━━━┛

The result is a 3-element array containing the values 14, 15 and 10. These are the values in the original array at indexes 4, 5 and 0.

To read a sequence of values from an array, the function can be used together with array lookup. Thus, to read the first 6 elements from an array, the following can be used:

    foo ← 10 11 12 13 14 15 16 17 18 19
    foo[⍳6]
┏━━━━━━━━━━━━━━━━━┓
┃10 11 12 13 14 15┃
┗━━━━━━━━━━━━━━━━━┛

Of course, KAP provides other ways to slice arrays (as would be expected from an array language) and these methods will be discussed later.

Array dimensionality

All arrays have a dimensionality, or “rank” as it is often referred to. Arrays in most languages are 1-dimensional, meaning that values in the array are addressed using a single number. When creating an array using the syntax described in the previous section, the result is a 1-dimensional array.

Rank-0 arrays

A rank-0 array contains a single value:

zero dim

Rank-1 arrays

Rank-1 arrays are often referred to as vectors, and are the default type of arrays in almost all programming languages. Elements are referenced using a single index:

one dim

Rank-2 arrays

A 2-dimensional array is similar to a spreadsheet, and have elements that are indexed using two numbers:

two dim

Rank-3 arrays

One can think of 3-dimensional arrays as a stack of 2-dimensional arrays, where the first index indicates the sheet, the second the row and the third is the column:

three dim

Rank-4 arrays

A 4-dimensional array can be thought of as multiple stacks of sheets. One needs 4 numbers to find a given cell, with the first number being the stack and the remaining three numbers as per the rank-3 array.

four dim

KAP supports arrays with a large number of dimensions (the exact number is 231-1), but in practice it’s rare to work with arrays with more than 4 dimensions. The principles that are illustrated in the previous paragraphs extend naturally to any number of dimensions.

Creating multidimensional arrays

To create an array of different dimensionality, the function is used. When called dyadically, this function takes an array of numbers to the left that indicates the sizes of the resulting array’s dimensions, and changes the dimensions of the array specified on the right to conform to the requested dimensions. This operation is called “reshape” in APL, and is called that because it gives a new shape to existing data.

That description was probably a bit confusing, so an example is in order:

foo ← 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
bar ← 3 5 ⍴ foo

After running the above, the variable foo will contain a 1-dimensional array, while bar contains a 2-dimensional version of the same data. The operation performed by is referred to as “reshape” because it changes the shape of the array while preserving content.

Content of foo:

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

Content of bar:

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

To read a value from a 2-dimensional array, one have to use two indices:

value ← bar[1;2]

After the above have been run, the variable value contains 7. That is to say, it contains the value in the second row and third column.

Just like the 1-dimensional case, one can always specify an array instead of a single value when reading values from a multidimensional array. An example:

bar[1;0 2 3]

This will return the following 1-dimensional array:

5

7

8

It is also possible to read all values along a given axis by omitting the index:

bar[;4]

This returns the following:

0

5

10

Finding the dimensionality of an array

In the previous section, it was explained how is called dyadically to set the dimensions of an array. When called monadically, the function returns the dimensionality of its argument.

foo ← 3 5 ⍴ ⍳15
bar ← ⍴ foo

After this code has been run, the variable bar will contain the array 3 5.

From this it can be seen that ⍴ X ⍴ Y will always return X. This is because ths expression is parsed as ⍴ (X ⍴ Y), which is equivalent to first reshaping Y with dimensions X, and then returning the dimensions of this array.

Applying functions on arrays

Scalar functions

Earlier in this tutorial, the basic mathematical functions were mentioned, including +, -, etc. These function belong to a category of functions which either act on single elements, or entire arrays at the same time. These functions are referred to in the APL literature as "Scalar Functions".

For monadic invocation on arrays, the function acts on each array element individually, returing a new array with the same dimensions as its argument. The below example calls × monadically on an array. This function returns the signum of its argument (1 if positive, 0 if it was zero and -1 if the value was negative):

    foo ← 2 3 ⍴ 1 ¯2 ¯10 20 11 12
    ×foo
┏━━━━━━━┓
┃1 -1 -1┃
┃1  1  1┃
┗━━━━━━━┛

Of course, the same thing can be written without assigning to a variable, as moving forward in this tutorial, this will be the way examples will be written:

    × 2 3 ⍴ 1 ¯2 ¯10 20 11 12
┏━━━━━━━┓
┃1 -1 -1┃
┃1  1  1┃
┗━━━━━━━┛

For dyadic invocation, the following rules are applied:

  • If both arguments are scalar, then the result is simply the two arguments applied to each other. I.e. 10+11 results in 21.

  • If one of the arguments is an array, the scalar argument is applied to each element in the array.

  • If both of the arguments are arrays, then the dimensions must match, and the function is applied on the matching values in each array.

Some examples:

Add 1 to each element in an array.

    1 + 10 20 30
┏━━━━━━━━┓
┃11 21 31┃
┗━━━━━━━━┛

Add two arrays.

    10 20 30 + 1+⍳3
┏━━━━━━━━┓
┃11 22 33┃
┗━━━━━━━━┛

Adding two arrays of different dimension will raise an error.

    1 2 3 + ⍳4
Error at: 1:7: function add: Arguments must be of the same dimension, or one of the arguments must be a scalar

The error message here explains that the arguments to the "add" function does not have the same dimensions.

Structural functions

Functions that are not scalar are called structural, which means that the behaviour of the function does not follow the simple rules outlined above.

A few structural functions have already been introduced, and . The dimensionality of the values returned from these functions depend on the arguments, and not just their dimensions. For example, the dimensionality of the array returned by the dyadic invocation of depends on the arguments on the left side of the function.

Operators

Operators are used to derive a new function from another function. Operators are written immediately following a function. For example, / is the “reduction operator”:

+/ array

The above code takes the + function and derives a new function using the reduction operator. The new function performs an additive reduction on its argument:

    +/10 20 30 40 50 60
210

The above returns the sum of the values in the array. In KAP, the reduction is performed left-to-right (which is different to how other versions of APL performs this operation), with the above example evaluated as follows:

 10+20 → 30
 30+30 → 60
 60+40 → 100
100+50 → 150
150+60 → 210

The reduction operator is very useful for a lot of purposes. For example, the function returns the maximum of two values. When used to together with reduce it can be used to find the maximum value in an array:

    ⌈/ 3 8 4 3 100 2 8 12 9 6
100

Datatypes

KAP supports the following basic datatypes:

  • Number (see next section)

  • Character

  • Symbol

  • Hashtable

  • Array

Numeric types

KAP supports the following numeric types:

  • 64-bit integers. These are written as plain decimal numbers: 123 or ¯12

  • 64-bit floating point numbers. A number if floating point if it contains a decimal point: 123.789

  • Complex numbers of the form 12J98. The first value represents the real part and the second is the imaginary part. Both the real and imaginary parts are always 64-bit floating point values even if they do not contain a decimal point.

Negative numbers are written using the ¯ symbol rather than -. In other words, the value -22 is written as ¯22. The reason for this is that - is a regular function, so an expression such as 3 -2 will be interpreted as 1. Writing it as 3 ¯2 makes this consistent.

Characters

A character is written as a @ followed by the character. For example, @b. A 1-dimensional array of characters is a string and can be written using double quotes. Thus, the array @f @o @o is the same as "foo".

The interpreter will print 1-dimensional arrays as strings if every element in the array is a character. If any element is not a character, the entire array will be printed in the regular fashion:

    @x @y @z
"xyz"
    @x @y @z 4
┏━━━━━━━━━━┓
┃@x @y @z 4┃
┗━━━━━━━━━━┛

Symbols

Symbols are objects that represents names. They are mostly used in advanced concept such as when defining new syntax. The most common usage of symbols is when using keywords, which are symbols that belong to the special keyword namespace. These are entered by prefixing the symbol name by a colon: :foo.

Hashtables

Hashtables are a special datatype that maps an object such as a string to another object. These are described in a later section.

Flow control

If statements

The if statement looks similar to that of C:

if(a < b) {
    io:println "a is less than b"
} else {
    io:println "a is not less than b"
}

However, in KAP, the if statement is a value which is set to the result of the last form in the evaluated clause. An example:

foo ← if(a < b) {
    10
} else {
    20
}

After executing the above, the variable foo will have the value 10 if a was less than b.

While loops

KAP provides a while loop that is similar to that of C and many of its descendents. A while loop terminates once its condition is false. The following example prints the numbers 0 to 19:

i ← 0
while(i < 20) {
    io:println i
    i ← i+1
}

Exceptions

TODO: Explain try/catch

KAP Reference

List of built-in KAP functions

Functions

In the below list, when discussing a dyadic function call, A and B refers to the left and right argument respectively.

Function Monadic Dyadic

+

Complex conjugate

Addition

-

Negation

Subtraction

×

Signum

Multiplication

÷

Reciprocal

Division

|

Magnitude

Modulo (note that the arguments are reversed, for a|b, the result is B modulo A)

Ceiling (return the smallest integer greater than or equal to the argument)

Maximum of the two arguments

Floor (return the largest integer smaller than or equal to the argument)

Minimum of the two arguments

Natural logarithm

Base-A logarithm of B

If the argument is a number, return a list from 0 to N-1. If the argument is an array, the result is sets of numbers where each value is the index in the corresponding axis.

Return the index of B in A

Return the shape of the argument

Reshape B to the dimensions specified in A

Return the argument itself

Return B

Return the argument itself

Return A

=

Not defined

Compare cells of A to corresponding cells in B

Not defined

Not-equals comparison of cells in A with the corresponding cells in B

<

Not defined

Less than

>

Not defined

Greater than

Not defined

Less than or equal

Not defined

Greater than or equal

Not defined

Indexed lookup from B by index A

Enclose

Partition B from specification A

Disclose. If the argument is an enclosed value, return the contained value, otherwise return the argument.

Pick

Not defined

For boolean arguments, return the logical and of A and B. For numeric values, return the GCD of A and B.

Not defined

For boolean arguments, return the logical or of A and B. For numeric values, return the LCM of A and B.

Not defined

Logical Nand

Not defined

Logical Nor

~

Logical not

Remove elements in B from A

,

Return the argument converted to a 1-dimensional array

Concatenate A and B along the major axis

Return the argument converted to a 2-dimensional array of one column

Concatenate A and B along the minor axis. This is equivalent to ,[0]

Return the first value in the array. If the array is empty, return 0.

Return the first A values in B. If B has fewer values than A, return 0 for the remaining results.

Drop the first value in the argument

Drop the first A values in B. If A is negative, drop the last -A values of B.

?

Return an array of the same dimensions as the argument, with each value being a random number between 0 and less than N.

Return A unique numbers between 0 and less than B

Reverse the order of the cells along the minor axis

Rotate the content of the cells in array B by A steps to the left along the minor axis

Reverse the order of the cells along the major axis

Rotate the content of the cells in array B by A steps to the left along the major axis

Transpose the array. Reverse the order of the dimensions.

Reorder the dimensions of B according to A

Not defined

True if A is equal to B

Size of the array along the major axis

True is A is not equal to B

Not defined

Find elements of A in B

Not defined

Return an array o booleans of the same dimension as B, where a cell is set to true if A is can be found at that point.

Return the indexes into the argument ordered by increasing value

Not defined

Return the indexes into the argument ordered by decreasing value

Not defined

/

Not defined

For each element in A, select that number of instances of the corresponding element in B. Selection along the major axis.

Not defined

For each element in A, select that number of instances of the corresponding element in B. Selection along the minor axis.

Return the argument as a string

Not defined

Parse the string as a number

Not defined

Return all unique elements in the argument

Return all unique elements in both arguments

Not defined

Return the intersection of elements in arrays A and B

!

Return the factorial of the argument

Return the binomial of A to B

Operators

Name Description

/

Reduce along last axis

Reduce along first axis (this is equivalent to FN/[0])

¨

Call function on each cell

For monadic calls, FN⍨ a is equivalent to a FN a. For dyadic calls, the arguments are reversed.

Rank. When called as a (FN⍤N) b Calls the function on all elements of N major axis of the arguments. N can be an array of 1, 2 or 3 integers.

Derive bitwise function

Compose functions.

parallel

Derive parallel version of the function if available

Flow control

If statements

If statement with a single clause. Returns if the condition is false.

if (a) {
    result
}

If statement with both then and else clauses:

if (a) {
    resultIfTrue
} else {
    resultIfFalse
}

When statement

The when statement can be used to check for multiple conditions. This is preferred to a long list of if/else if/else statements.

when {
    (conditionA) { resultIfA }
    (conditionB) { resultIfB }
    (1)          { elseResult }
}

While statement

Executes the body until the condition is false:

while (a) {
    code
}

Unwinding

The unwindProtect statement is used to execute one code block after another one, regardless of whether the first one performed a non-local exit (such as by throwing an exception):

unwindProtect { mainCode } { unwind }

Throwing an exception

Exceptions are thrown using . Exception have a type, represented by a symbol and some associated data. The following example throws an exception of type :foo with data "test":

:foo → "test"

When called monadically, will throw an execption of type :error.

Catching exception

TODO: Need to define a syntax extension to make exception catching nice