april

2024-10-12

April is a subset of the APL programming language that compiles to Common Lisp.

Upstream URL

github.com/phantomics/april

Author

Andrew Sengul, Max Rottenkolber <max@mr.gy>

License

Apache-2.0
README

April

Array Programming Re-Imagined in Lisp


Ken Iverson's masterpiece reflected in the medium of Lisp.

April compiles a subset of the APL programming language into Common Lisp. Leveraging Lisp's powerful macros and numeric processing faculties, it brings APL's expressive potential to bear for Lisp developers. Replace hundreds of lines of number-crunching code with a single line of APL.

Why April?

APL veritably hums with algorithmic power. As a handful of characters run past the lexer, vast fields of data grow, morph and distil to reveal their secrets. However, APL has hitherto dwelt in an ivory tower, secluded inside monolithic runtime environments. If you have a store of data you'd like to process with APL, getting it there can be an ordeal akin to hauling tons of cargo on donkeys' backs through a narrow mountain pass. The original APL interpreters ran on mainframes whose only input was the keyboard and only output was the printer and the legacy of that implementation approach has persisted to this day, limiting the reach of the language.

But no longer. Lisp is the great connector of the software world, digesting and transforming semantic patterns in much the same way that APL works upon numeric patterns. With APL inside of Lisp, databases, streams, binary files and other media are just a few lines of code away from processing with APL.

Discussion

For the time being, discussion of April and its development is happening on the ##phantomics channel on irc.libera.chat.

Support April's Development

If you'd like to help provide for continuing work on April, you can contribute through Patreon. April is making fast and steady progress toward the goal of being a powerful, full-featured and professional APL implementation, whose ease of extension and integration with other software offers game-changing advantages in the vector language field. Did you know that April's code at the beginning of December 2021 was over 95% different from its code at the beginning of September 2020?

April Media and Publications

PLDI 2023 presentation

Presentation of paper on April's deferred evaluation features

ArrayCast interview

Developer interview exploring April's history and features

APL Seeds Presentation

Presentation of April at Dyalog's APL Seeds '22 conference

LispNYC April compiler presentation

Video outline and panel discussion showing early April design

European Lisp Symposium 2022 paper

April: APL Compiling to Common Lisp

Compatibility with Common Lisp Implementations

April puts the numeric and array processing capabilities of Common Lisp to the test. It has been verified to work with SBCL, CCL, ECL, ABCL, Clasp, Allegro CL and LispWorks. SBCL and CCL are considered fully compatible with no special provisions made in order to function. ECL and Clasp pass all tests with the help of some specific provisions addressing their differences from other CL implementations. ABCL passes all main tests with the help of some special provisions and all dfn tests except those in the tree library, which cannot be loaded due to limitations of the underlying Java virtual machine. Allegro CL and LispWorks both have a few compatibility issues causing failures of dfn tests. See this document for a list of all differences in functionality between implementations.

Automatic Installation

April is supplied by the Quicklisp library manager, so the easiest way to install April is through Quicklisp. To install April with Quicklisp, evaluate:

(ql:quickload 'april)

Manual Installation

If you'd like to install April manually from this repository, you can follow these instructions.

Cloning the Repository

First, clone the repository to a location on your system. For this example, let's say you cloned it to the directory ~/mystuff/april.

Preparing Quicklisp

Enter your Quicklisp local-projects directory (usually ~/quicklisp/local-projects) and create a symbolic link to the directory where you cloned the April repository. For example, if you cloned the repo to ~/mystuff/april and your Quicklisp directory is ~/quicklisp/, enter:

cd ~/quicklisp/local-projects
ln -s ~/mystuff/april

Installing Dependencies

To complete the installation, just start a Common Lisp REPL and enter:

(ql:quickload 'april)

This will download and install April's dependencies, and with that the package will be built and ready.

APL Functions and Operators

The APL language uses single characters to represent its primitive functions and operators. Most of these symbols are not part of the standard ASCII character set but are unique to APL. To see a list of the glyphs that are supported by April, visit the link below.

See the complete April APL lexicon here.

Some APL functions and operators won't be added to April since they don't make sense for April's design as a compiler from APL to Lisp. Others may be added in the future. See the list of features not implemented here.

Getting to Know APL

A full guide to the APL language is far beyond the scope of this file, but here are links to some good sources.

A high-level introduction to APL.

This is a detailed language tutorial covering most of the functions and operators in April.

The original paper by Ken Iverson, creator of APL, detailing the language's underlying philosophy.

If you would like a quick tour of the language, April includes a function that will print demos of all the commands and many APL syntax features. To see the demos, enter:

* (april (demo))

The * indicates a REPL prompt. Prepare for a long read. The demo content that gets printed will tell you the name(s) of the operations that correspond to each symbol and will hopefully give you some idea of what each one does.

How to Enter APL Characters

In order to write APL programs you'll need a way to use the language's special character set.

Click here for information on enabling APL input within Emacs.

Click here for information on enabling APL input within Vim.

Click here for information on enabling APL input universally within GNU/Linux.

Basic Evaluation: (april) and (april-f)

Evaluating an APL expression is as simple as:

* (april-f "1+2 3 4")
3 4 5
#(3 4 5)

As above, the * indicates a REPL prompt and the text below is the expression's output.

The macro (april-f) (short for april-format) will evaluate any APL string passed to it as the sole argument, returning the final result. Using (april-f) will also produce a printout of the output in APL's traditional array printing style, which appears before the actual output value. You can see above how the 3 4 5 is printed out before the value #(3 4 5). APL-style printed arrays are easier to read than Lisp's style of printing arrays; APL can use a simpler style to express its output because it doesn't have as many different data types and structures as Lisp.

If you don't need to see the printout, you can use the plain (april) macro. Like this:

* (april "1+2 3 4")
#(3 4 5)

You should use (april) if you're using April to do calculations inside of a larger program and don't need the printout. Otherwise, especially if you're working with large data sets, the system may consume significant resources printing out the results of calculations.

Setting state properties for the APL instance can be done like this:

* (april-f (with (:state :count-from 0)) "⍳9")
0 1 2 3 4 5 6 7 8
#(0 1 2 3 4 5 6 7 8)

Instead of an APL string, the first argument to (april) or (april-f) may be a list of parameters for the APL environment. The APL expression is then passed in the second argument.

For example, you can use the :count-from parameter to determine whether functions in the evaluated APL code will start counting from 0 or 1. We'll get into more detail on how these parameters work later.

* (april-f (with (:state :count-from 1)) "⍳9")
1 2 3 4 5 6 7 8 9
#(1 2 3 4 5 6 7 8 9)

* (april-f (with (:state :count-from 0)) "⍳9")
0 1 2 3 4 5 6 7 8
#(0 1 2 3 4 5 6 7 8)

More APL expressions:

* (april-f "⍳12")
1 2 3 4 5 6 7 8 9 10 11 12
#(1 2 3 4 5 6 7 8 9 10 11 12)

* (april-f "3 4⍴⍳12")
1  2  3  4
5  6  7  8
9 10 11 12
#2A((1 2 3 4) (5 6 7 8) (9 10 11 12))

* (april-f "+/3 4⍴⍳12")
10 26 42
#(10 26 42)

* (april-f "+⌿3 4⍴⍳12")
15 18 21 24
#(15 18 21 24)

* (april-f "+/[1]3 4⍴⍳12")
15 18 21 24
#(15 18 21 24)

* (april-f "⌽3 4⍴⍳12")
 4  3  2 1
 8  7  6 5
12 11 10 9
#2A((4 3 2 1) (8 7 6 5) (12 11 10 9))

* (april-f "1⌽3 4⍴⍳12")
 2  3  4 1
 6  7  8 5
10 11 12 9
#2A((2 3 4 1) (6 7 8 5) (10 11 12 9))

A note on escaping characters

April uses the backslash character \ to implement the expand function and the scan operator. Because of the way Lisp strings work, this character must be escaped with a second \ before it in order to enter APL code containing backslashes. For example:

* (april-f "+\\⍳5")
1 3 6 10 15
#(1 3 6 10 15)

Inside the "string", the two backslashes evaluate to a single backslash. If you forget about this, you can run into confusing errors.

Unique Language Features in April

For the most part, April's syntax and functions follow standard APL conventions. But there are a few areas where April differs from typical APL implementations along with some unique language features. Most notably:

;; k-style if-statements
* (april "x←5 ⋄ $[x>3;8;12]")
8

;; k-style functions with any number of named arguments
* (april "monthlyPayment←{[amt;int;len] (len÷⍨amt×int×0.1)+amt÷len} ⋄ monthlyPayment[5000;0.8;12]")
450.0d0

;; numbered branch points instantiated with →⎕ syntax
* (april "x←1 ⋄ →1+1 ⋄ x×←11 ⋄ 1→⎕ ⋄ x×←3 ⋄ 2→⎕ ⋄ x×←5 ⋄ 3→⎕ ⋄ x×←7")
35

;; symbol-referenced branch points and a branch function with expression-determined branch symbol choice
* (april "x←1 ⋄ (5-3)→two three ⋄ x×←11 ⋄ one→⎕ ⋄ x×←3 ⋄ two→⎕ ⋄ x×←5 ⋄ three→⎕ ⋄ x×←7")
7

;; ⍶ and ⍹ are used to reference values passed as operands to a user-defined operator
* (april "'*' {⍶,⍵} ' b c d'")
"* b c d"

;; if the operands passed to a user-defined operator are to be functions,
;; ⍺⍺ and ⍵⍵ are used as in other APLs
* (april "(2∘|) {(⍺⍺¨⍵)/⍵} ⍳20")
#(1 3 5 7 9 11 13 15 17 19)

The biggest difference between April and other APLs lies in its implementation of the → branch function, as shown in the third and fourth examples above. April also allows you to use if-statements and functions with any number of named arguments in the style of Arthur Whitney's k programming language.

Because of April's nature as a compiler, user-defined operators use different symbols to refer to operands depending whether the operands are values or functions. The underlined characters and are used to refer to the operands as values, while the doubled characters ⍺⍺ and ⍵⍵ refer to the operands as functions. The presence of both and ⍺⍺ or both and ⍵⍵ in a defined operator will cause an error.

Using rational numbers

April is one of a few APL implementations to include rational numbers. They are printed with a lowercase r separating the numerator and denominator. Examples:

* (april-f "÷⍳5")
1 1r2 1r3 1r4 1r5
#(1 1/2 1/3 1/4 1/5)

* (april-f "2r3×⍳4")
2r3 4r3 2 8r3
#(2/3 4/3 2 8/3)

Rational numbers can also be used as parts of complex numbers:

* (april-f "3r4J9r5×⍳4")
3r4J9r5 3r2J18r5 9r4J27r5 3J36r5
#(#C(3/4 9/5) #C(3/2 18/5) #C(9/4 27/5) #C(3 36/5))

Underscores within numbers

In April, you can use underscores to separate parts of a number:

* (april "1_000_000+5")
1000005

* (april "1__000_000__000_000+5")
1000000000005

* (april "1_00___0_0__00_0+5")
10000005

As shown above, you can use any number of underscores anywhere within a number, they are simply ignored by the reader. Underscores are also used by the printer when printing columns of mixed complex floats and rationals:

* (april-f "⍪12.2J99.11 3J8 19r13J5r2")
12.20J99.11
 3___J_8   
19r13J_5r_2
#2A((#C(12.2 99.11)) (#C(3 8)) (#C(19/13 5/2)))

Using the underscores as filler keeps the decimal points, rs and Js properly aligned for printing.

Strings and escaped quotes

In April, either single or double quotes can be used to enclose character strings:

* (april "'abc','def'")
"abcdef"

* (april "\"ghi\",\"jkl\"")
"ghijkl"

Note that you must use backslashes to escape double quotes used within Lisp strings, making double quotes a less desirable choice unless you're loading April code from files using (april-load). In order to escape quote characters within an April string, enter the quote character twice. For example:

* (april "'\'abc'\'")
"'abc'"

* (april "'''abc'''")
"'abc'"

Compact Function Calls: The (april-c) Macro

Want to invoke April functions on some variables with less code? You can use the (april-c) macro. For example:

* (april-c "{⍺×⍵}" 2 8)
16

* (april-c "×" 2 8) ;; the same result as above by passing a string containing just '×'
16

* (april-c "{[a;b;c;d] d↑c⍴a+b}" 3 5 6 10)
#(8 8 8 8 8 8 0 0 0 0)

After the string where the April function is written, pass the variables that will be input to the function and you'll receive the result with no need for a long (with (:state ...)) clause. If you wish to pass parameters in a (with) clause, you can still do it with (april-c).

* (april-c (with (:state :count-from 0)) "{⍳⍵}" 7)
#(0 1 2 3 4 5 6)

The arguments passed to (april-c) are in the order ⍵ ⍺. This may seem counterintuitive:

* (april-c "{⍵-⍺}" 10 5)
5

But keep in mind that a function must always have a right argument, but may or may not have a left argument. Therefore it's easier to remember that the first argument after the function string is always the right argument, and the second argument, if present, is the left argument.

The (april-c) macro can also be used to compose inline operators with functions. For example:

* (april-c "{⍺⍺/⍵}" #'+ #(1 2 3 4 5))
15

* (april-c "{⍵⍵ ⍺⍺/⍵}" #'+ #'- #(1 2 3 4 5))
-15

* (april-c "{⍵⍵ ⍺ ⍺⍺/⍵}" #'+ (scalar-function -) #(1 2 3 4 5) 3)
#(-6 -9 -12) 

Note that in operators where a right operand is expected (i.e. those that contain a ⍵⍵ or symbol), two operands are expected following the code string. In an operator taking only a left operand, whose code doesn't include ⍵⍵ or , one operand is expected following the code string. The arguments to (april-c) for an operator are in the order ⍺⍺/⍶ (⍵⍵/⍹ if present) ⍵ (⍺ if present). The left operand comes first in the arguments because all operators must have a left operand, but they may or may not have a right operand.

Keep in mind that standard Common Lisp functions like #'+ do not operate on entire arrays like APL functions do. In order to pass scalar functions into April via (april-c) that can be composed with operators and work as you expect scalar functions to when doing operations like - 3 +/⍳5, you must pass those functions' symbols through the (scalar-function) macro as seen above with (scalar-function -).

Parameter reference

When (april) or (april-f) is called, you may pass it either a single text string:

* (april-f "1+1 2 3")

Or a parameter object followed by a text string:

* (april-f (with (:state :count-from 0)) "⍳9")

This section details the parameters you can pass to April.

(test)

To run April's test suite, just enter:

* (april (test))

(demo)

As mentioned before, you can see demos of April's functions with:

* (april (demo))

(with)

(with) is the workhorse of April parameters, allowing you to specify many options for an April invocation. The most common sub-parameter passed via (with) is (:state). To wit:

* (april (with (:state :count-from 0
                       :in ((a 3) (b 5))
                       :out (a c)))
         "c←a+⍳b")
3
#(3 4 5 6 7)

(:state) sub-parameters

Let's learn some more about what's going on in that code. The sub-parameters of (:state) are:

:count-from

Sets the index from which April counts. Almost always set to 0 or 1. The default value is 1. In the code above, ⍳b with b equal to 5 counts from 0 to 4, whereas with the default :count-from value of 1, ⍳b would count from 1 to 5. When you set :count-from, it only affects the APL code evaluated in the expression to which the :count-from option is passed. For example:


* (april (with (:state :count-from 0)) "⍳9")
#(0 1 2 3 4 5 6 7 8)

* (april "⍳9")
#(1 2 3 4 5 6 7 8 9)

:in

Passes variables into the April instance that may be used when evaluating the subsequent expressions. In the example above, the variables a and b are set in the code, with values 1 and 2 respectively. You can use :in to pass values from Lisp into the April instance.

* (april-f (with (:state :in ((a 5) (b 10))))
           "1+2+a×b")
53

Please note that April variables follow a stricter naming convention than Lisp variables. When naming the input variables, only alphanumeric characters, underscores and dashes may be used. In keeping with APL tradition, the delta/triangle characters ∆ and ⍙ can be used in variable names as well. Punctuation marks like ?, >, . and ! may not be used as they have separate meanings in April.

These characters are allowed in variable names within April:

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_∆⍙

These variable names are ok for use with the :in parameter:

a var my_var another-var

These are not ok:

true! this->that pass/fail? var.name

If you use dashes in the names of Lisp variables you pass into April, note that inside April they will be converted to camel case. For example:

* (april-f (with (:state :in ((one-var 2)
                              (other-var 5))))
           "oneVar×otherVar+5")
20

The dash character - is used to denote the subtraction function inside April, so you may not use dashes in variable names within the language.

One more caveat: it's best to avoid using input variable names with a dash before a number or other non-letter symbol. The dash will be removed and the character following it will cannot be capitalized so information will have been lost. For example:

my-var-2 → myVar2
my-var-∆ → myVar∆

An important note about modifying arrays passed to April

When passing array values into April via :in or direct function calls using (april-c), be advised that assinging new values to their elements may or may not destructively modify the original arrays. Whether destructive modification happens depends on whether the values assigned are compatible with the element type of the original arrays. Here is an example:

(let ((a #(1 2 3)) (b #(2 3 4))
      (c (make-array 3 :element-type '(unsigned-byte 4)
                       :initial-contents '(7 8 9)))
      (d (make-array 3 :element-type '(unsigned-byte 4)
                       :initial-contents '(10 11 12))))
  (april (with (:state :in ((a a) (b b) (c c) (d d))))
         "a[1]←20 ⋄ (⊃b)←30 ⋄ c[1]←40 ⋄ (⊃d)←50")
  (vector a b c d))

#(#(20 2 3) #(30 3 4) #(7 8 9) #(10 11 12))

The first two T-type vectors are destructively modified since they are compatible with the elements being assigned. The second two vectors of 4-bit integers are copied and reassigned within the April invocation because they are not compatible with the values being assigned, and those the original arrays are left intact.

:out

Lists variables to be output when the code has finished evaluating. By default, the value of the last evaluated expression is passed back after an April evaluation is finished. For example:

* (april-f "1+2
            2+3
            3+4")
7

The last value calculated is displayed. The :out sub-parameter allows you to list a set of variables that whose values will be returned once evaluation is complete. For example:

* (april-f (with (:state :out (a b c)))
           "a←9+2
            b←5+3
            c←2×9")
11
8
18

:index-origin

This is another, more technical name for the :count-from sub-parameter. You can use it instead of :count-from:

* (april-f (with (:state :index-origin 0)) "⍳9")
0 1 2 3 4 5 6 7 8
#(0 1 2 3 4 5 6 7 8)

:print-precision

This controls the maximal precision at which April prints floating point numbers. Its default value is 10. For example:

* (april-f "○1 2 3")
3.141592654 6.283185307 9.424777961	
#(3.141592653589793d0 6.283185307179586d0 9.42477796076938d0)

* (april-f (with (:state :print-precision 6)) "○1 2 3")
3.14159 6.28319 9.42478
#(3.141592653589793d0 6.283185307179586d0 9.42477796076938d0)

* (april-f (with (:state :print-precision 3)) "○1 2 3")
3.14 6.28 9.42
#(3.141592653589793d0 6.283185307179586d0 9.42477796076938d0)

Note that :print-precision doesn't affect the Lisp values output by April, only the printed output.

:print-to

When using (april-f), the formatted array content is output to the *standard-output* stream. When using (april), no formatted output is printed. You can change this using the :print-to sub-parameter. For example:

* (april (with (:state :print-to *standard-output*)) "2 3⍴⍳9")
1 2 3
4 5 6
#2A((1 2 3) (4 5 6))

* (april-f (with (:state :print-to nil)) "2 3⍴⍳9")
#2A((1 2 3) (4 5 6))

Using the :print-to parameter effectively erases the distinction between (april) and (april-f). The two different macros are provided as a courtesy so you don't need to pass a :print-to parameter to get printed output. You can also pass a different stream than *standard-output* to :print-to to have the printed output directed there.

:output-printed

When the :output-printed sub-parameter is passed, the string of APL-formatted data that gets printed will also be returned as the last output value by the April invocation. For example:

* (april (with (:state :output-printed t)) "2 3⍴⍳9")
#2A((1 2 3) (4 5 6))
"1 2 3
4 5 6
"

If you don't want to receive the Lisp value output by April and only want the formatted string as output, you can pass the :only option to :output-printed, like this:

* (april (with (:state :output-printed :only)) "2 3⍴⍳9")
"1 2 3
4 5 6
"

This way, the formatted string will be the only returned value.

(:space) parameter

If you want to create a persistent workspace where the functions and variables you've created are stored and can be used in multiple calls to April, you must first create the workspace. Here's how:

* (april-create-workspace space1)
"Successfully created workspace 「SPACE1」."

Then, to evaluate April code within use the (:space) parameter. For example:

* (april-f (with (:space space1)) "a←5+2 ⋄ b←3×9")
27

* (april-f (with (:space space1)) "c←{⍵+2}")
#<FUNCTION ... >

* (april-f (with (:space space1)) "c a+b")
36

In the above example, a workspace called space1 is created, two variables and a function are stored within it, and then the function is called on the sum of the variables.

When you invoke (april) without naming a workspace, a workspace called common is used.

* (april (with (:space common)) "a←5")
5

* (april "a+5")
10

(:store-val) and (:store-fun) parameters

If you'd like to add values and functions from the Lisp instance into an April workspace, you can use the (:store-val) and (:store-fun) parameters. Here is an example:

* (april (with (:store-val (a 12) (b 45))) "a+b+10")
67

* (april "3×a")
36

* (april (with (:store-fun (add-ten (lambda (x) (+ x 10))))) "")
NIL ;; the result of not running code after the function is stored

* (april "addTen 20")
30

As shown above, dash-separated variable and function names are converted to camel case, just as when passing input values with the :in sub-parameter of (:state). Note also that functions passed into April this way are not adapted for use with arrays the way that functions created within April are. For instance, if you enter:

* (april (with (:store-fun (add-ten (lambda (x) (+ x 10))))) "")
NIL

* (april "addTen 1 2 3 4 5")

You will get an error stating something like The value #(1 2 3 4 5) is not of type NUMBER .... That's because in Lisp, you can't add 10 to the vector #(1 2 3 4 5), which is what the function is attempting to do. Use caution when adding arbitrary Lisp functions into an April workspace.

If you want to store functions or variables with names that are read literally rather than being converted to camel case, you can do this by passing strings as the variable names. If a dash is found in such a string-expressed variable name it will cause an error. For example:

(april (with (:store-fun ("aBcDe" (lambda (x) (+ x 10))))) "aBcDe 5")
15

;; the presence of a dash causes an error
(april (with (:store-fun ("ab-cd" (lambda (x) (+ x 10))))) "abcd 6")
Error: Invalid characters present in symbol aBC-deF passed to :STORE-FUN.

(:compile-only) parameter

If you just want to compile the code you enter into April without running it, use this option. For example:

* (april (with (:compile-only)) "1+1 2 3")
(IN-APRIL-WORKSPACE COMMON
  (LET ((OUTPUT-STREAM *STANDARD-OUTPUT*))
    (DECLARE (IGNORABLE OUTPUT-STREAM))
    (SYMBOL-MACROLET ((INDEX-ORIGIN ⊑*INDEX-ORIGIN*)
                      (PRINT-PRECISION ⊑*PRINT-PRECISION*)
                      (COMPARISON-TOLERANCE ⊑*COMPARISON-TOLERANCE*)
                      (DIVISION-METHOD ⊑*DIVISION-METHOD*)
                      (RNGS ⊑*RNGS*))
      (A-OUT (A-CALL (APL-FN-S +) (AVEC 1 2 3) 1) :PRINT-PRECISION
             PRINT-PRECISION))))

(:print-tokens) parameter

April compiles code in two stages: first, the lexer converts the string of characters into lists of tokens, then the compiler takes those tokens and uses them to generate Common Lisp code based on grammar elements and patterns. You can see the implementation of April's grammar in the grammar source file.

If you'd like to see the lists of tokens the lexer generates for a given APL expression, you can see them by passing the (:print-tokens) parameter.

* (april (with (:print-tokens)) "(⍳4)+⍤1⊢3 4⍴⍳9")
(9 (:FN #\APL_FUNCTIONAL_SYMBOL_IOTA) (:FN #\APL_FUNCTIONAL_SYMBOL_RHO) 4 3
 (:FN #\RIGHT_TACK) 1 (:OP :PIVOTAL #\APL_FUNCTIONAL_SYMBOL_JOT_DIAERESIS)
 (:FN #\+) (4 (:FN #\APL_FUNCTIONAL_SYMBOL_IOTA)))
#2A((2 4 6 8) (6 8 10 12) (10 3 5 7))

Clearing Workspaces: The (april-clear-workspace) Macro

You can use this macro to clear a workspace, removing all user-created variables within it and returning it to its default state. For example, to clear a workspace called space1, enter:

* (april-clear-workspace space1)
"The workspace 「SPACE1」 has been cleared."

References to April Workspace Contents: The (april-ref) Function

If you'd like to easily fetch a value or function from an April workspace within Common Lisp code, you can use the (april-ref) function. It works like this:

* (april "v←⍳3")
#(1 2 3)

* (length (april-ref '|v|))
3

* (april "fn←{⍵+5}")
#<FUNCTION ...>

* (funcall (april-ref "fn") 10)
15

The arguments to (april-ref) can be strings or symbols (which will be converted to strings). Since (april-ref) is a function and not a macro you can construct strings to pass to it as arguments like so:

* (april "abcdef←×")
#<FUNCTION ...>

* (funcall (april-ref (format nil "~a~a" "abc" :|def|)) 2 5)
10

By default (april-ref) fetches items from the default common workspace. By passing two arguments you can choose the workspace to use:

* (april-create-workspace space1)
"Successfully created workspace 「SPACE1」."

* (april (with (:space space1)) "v←25")
25

* (* 3 (april-ref (format nil "SPACE~a" 1) :|v|))
75

Sharing Scope: The (with-april-context) Macro

Perhaps you'd like to make multiple calls to April using the same workspace and other parameters and you don't want to have to enter the same parameters over and over again. The (with-april-context) macro can help. For example:

* (april-create-workspace space1)
"Successfully created workspace 「SPACE1」."

* (with-april-context ((:space space1) (:state :index-origin 0))
    (april "g←5")
    (april "g×3+⍳9"))
#(15 20 25 30 35 40 45 50 55)

Inside the body of the (with-april-context) macro, each of the (april) invocations act as if they were passed the options (with (:space space1) (:state :index-origin 0)).

* (april-create-workspace space1)
"Successfully created workspace 「SPACE1」."

* (with-april-context ((:space space1) (:state :index-origin 0))
    (april "x←⍳3")
    (april (with (:state :index-origin 1)) "x,⍳5"))
#(0 1 2 1 2 3 4 5)

Options passed for one of the (april) invocations inside the context will override the options for the context. Here, the second (april) invocation has its index origin set to 1 which overrides the context's 0 value.

Console Output Using the Quad Character

The (april-f) macro is one way to view the printed output of APL expressions. What if you want to see the result of an evaluation that occurs in the middle of your code instead of the end, or if you want to print the contents of multiple arrays within a single expression? At times like these, you can use the character, also called "quad." In APL, console output can be produced by "assigning" values to like this:

* (april "a←1 2 3 ⋄ b←3+⎕←2+a ⋄ ⎕←c←4+b ⋄ c+5")
3 4 5
10 11 12
#(15 16 17)

Both of the values assigned to are printed in order before the expression's final result is output. Because (april) is used instead of (april-f), no formatted values are printed by default; only the values assigned to are printed. Using , it's easy to debug complex functions.

Loading Code Directly From Files

Perhaps you'd like to write files containing pure APL code rather than passing strings to (april) within Lisp code. The (april-load) macro has you covered. For example:

⍝ contents of file test.apl

v   9
fn  {+10}
   fn v
* (april-load #P"/path/to/test.apl")
11 12 13 14 15 16 17 18 19
#(11 12 13 14 15 16 17 18 19)

* (april "fn 3 4 5")
#(13 14 15)

The variable v and the function fn have been loaded into the default workspace.

Note that the argument to (april-load) must be a pathname, not merely a string. The argument to (april-load) may also be an expression that evaluates to a pathname. For instance:

* (april-load (pathname (format nil "~a/test.apl" "/test/directory")))
11 12 13 14 15 16 17 18 19
#(11 12 13 14 15 16 17 18 19)

The (april-load) macro may take a first argument containing the same parameters that can be passed to (april). For example:

* (april-create-workspace space1)
"Successfully created workspace 「SPACE1」."

* (april-load (with (:space space1) (:state :index-origin 0)) #P"/path/to/test.apl")
10 11 12 13 14 15 16 17 18
#(10 11 12 13 14 15 16 17 18)

* (april (with (:space space1)) "fn 9 8 7")
#(19 18 17)

Source code from files can thus be loaded into any workspace.

APL System Variables and Functions in April

April makes available the following APL system variables, constants and functions:

⎕IO ⎕CT ⎕PP ⎕DIV ⎕RL ⎕A ⎕D ⎕TS ⎕NS ⎕CS ⎕UCS ⎕FMT

Additionally, April exposes these system variables and functions not found in other APL implementations:

⎕OST ⎕TY ⎕XWV ⎕XWF ⎕XWO

Click here to read the names and descriptions of these symbols.

About Workspace Variables: Index Origin and Print Precision

Above, you learned how to use the :count-from/:index-origin and :print-precision sub-parameters to control how April counts and prints. Using these parameters with an April invocation will affect only the code passed to that particular April invocation. What if you want to create a change in these parameters that will persist in a given workspace until it's changed again?

Traditional APL dialects use the ⎕IO and ⎕PP system variables to set the index origin and print precision in a workspace, and using them in April will make a change that persists in the workspace. For example:

* (april-create-workspace space1)
"Successfully created workspace 「SPACE1」."

* (april-create-workspace space2)
"Successfully created workspace 「SPACE2」."

* (april-f (with (:space space1)) "⎕IO←0 ⋄ ⍳9")
0 1 2 3 4 5 6 7 8
#(0 1 2 3 4 5 6 7 8)

* (april-f (with (:space space1)) "⍳9")
0 1 2 3 4 5 6 7 8
#(0 1 2 3 4 5 6 7 8)

* (april-f (with (:space space2)) "⍳9")
1 2 3 4 5 6 7 8 9
#(1 2 3 4 5 6 7 8 9)

Switching to the workspace space2, the default index origin of 1 is used again.

If you pass an :index-origin, :count-from or :print-precision sub-parameter to an APL invocation, it will override whatever value is present in the active workspace. However, it will only affect the code passed to the individual (april) invocation that has the sub-parameter(s) passed.

* (april-create-workspace space1)
"Successfully created workspace 「SPACE1」."

* (april-f (with (:space space1)) "⎕IO←0 ⋄ ⍳9")
0 1 2 3 4 5 6 7 8
#(0 1 2 3 4 5 6 7 8)

* (april-f (with (:state :count-from 1)) "⍳9")
1 2 3 4 5 6 7 8 9
#(1 2 3 4 5 6 7 8 9)

* (april-f (with (:space space1)) "⍳9")
0 1 2 3 4 5 6 7 8
#(0 1 2 3 4 5 6 7 8)

Setting a Custom Output Stream

April has a system variable called ⎕ost that you can use to set a custom destination for printed output. Normally, data output using (april-f) or values assigned to the quad character like ⎕←1 2 3 are sent to the *standard-output* stream. You can change this as follows:

* (let* ((out-str (make-string-output-stream))
	 (vector (april-f "a←1 2 3 ⋄ ⎕ost←'OUT-STR' ⋄ ⎕←a+5 ⋄ ⎕←3 4 5 ⋄ ⎕ost←'*STANDARD-OUTPUT*' ⋄ 3+a")))
    (princ (get-output-stream-string out-str))
    vector)
4 5 6
6 7 8
3 4 5
#(4 5 6)

Within the APL expression, the output stream is set to OUT-STR, two vectors are output to that stream, and then the stream is reset to *STANDARD-OUTPUT* before the expression ends and prints its final output. When the code runs, first the APL-formatted output from the (april-f) expression is printed. Then, the two APL-formatted strings output to the out-str stream are printed. Finally, the Lisp vector that resulted from the (april-f) expression is printed.

Remember to use all caps when setting the ⎕ost variable, unless your desired output stream is referenced by a literal lowercase symbol like |output-stream|.

The syntax above assumes that the symbol representing the output stream is internal to the current package. For instance:

(in-package #:pkg-one)

(defvar out-str (make-string-output-stream))

(april-f "⎕ost←'OUT-STR' ⋄ 5+10")

In this code, the OUT-STR output stream is interned in the package PKG-ONE. What if you want to use an output stream whose symbol belongs to a package other than the current one?

(in-package #:pkg-one)

(defvar out-str (make-string-output-stream))

(in-package #:pkg-two)

(april-f "⎕ost←('PKG-ONE' 'OUT-STR') ⋄ 5+10")

If you assign to ⎕ost a vector of two strings, the first string is the name of a package and the second string is the name of a symbol belonging to that package. In this way, you can reference an output stream whose symbol is interned in a package other than the current one.

Referencing Variables, Functions and Operators in Another Workspace

A difference between April and other APLs is the way in which workspaces are handled. Because April workspaces are modeled as Lisp software packages, it's possible to include things from one workspace in another in much the same way you do with Common Lisp's (defpackage) macro. In other APLs, a workspace is more akin to the complete application state of an APL interpreter, so sharing items between them is more complicated to implement.

Below is an example of items shared between workspaces.

* (april (with (:space first-space))
         "V1←10 ⋄ V2←20 ⋄ F←{10×⍺÷⍵} ⋄ G←⍴∘,")
...

* (april (with (:space second-space))
         "A B ← 'FIRST-SPACE' ⎕XWV 'V1' 'V2'
          H I ← 'FIRST-SPACE' ⎕XWF 'F' 'G'
          A B H I A B")
#(5 5)

The V1 and V2 variables from first-space are referenced as A and B while the Fn1 and Fn2 variables are referenced as H and I respectively. Operators can also be referenced with ⎕XWO.

What's Not Planned for Implementation

Functions:

⍇ File read
⍈ File write
⍐ File hold
⍗ File drop
⎕ Evaluated input
⎕ Output with newline
⍞ Character input
⍞ Bare output

Operators:

& Spawn
⌶ I-Beam

(Click here to see the functions and operators that have been implemented.)

See a pattern? The functions not planned for implentation are all those that manifest low-level interactions between the APL instance and the underlying computer system. Common Lisp already has powerful tools for system interaction, so it's presumed that developers will do things like this outside of April.

Also Not Implemented

APL's function editor system and control statements are not implemented; this type of functionality is also readily accessible through standard Common Lisp. Also, rather than using control statements like :If, April approaches the need for such structures with lexical statements like $[].

April's Lexicon Compared to Other APLs

APL has multiple implementations and there are subtle but significant variations between the lexical functions they offer. April's set of functions is closest to those offered by Dyalog APL in its default mode. For instance, in April dyadic implements the partitioned enclose function while dyadic implements the partition function, as in Dyalog. In IBM APL2, however, there is no partitioned enclose function and dyadic implements the partition function. The same is true in GNU APL, whose design primarily follows APL2.

The other major lexical difference between APL2-family languages and April is that in April, monadic implements the disclose function and monadic implements the mix function; the converse is true in APL2.

Dyalog APL offers users the option of using multiple lexical modes, some of which are more similar to APL2. The variable controlling the active mode is referred to as the "migration level." The implementation of migration levels in April is not planned at this time.

Tests, Demo and the Extended Demo Suite

If you missed it earlier, you can run tests for the implemented APL functions and operators by entering:

* (april (test))

And you can see a demonstration of April language features by entering:

* (april (demo))

April comes with a set of standard libraries and demo packages implementing useful APL functions. The libraries are located in this repository's /libraries folder, and the demo packages are located in the /demos folder, and each library package has its own set of tests. You can load the libraries by evaluating (load-libs) and run the tests for each demo by evaluating (run-lib-tests) within the april package. The library tests contain many complex functions that generate large arrays, giving the computer a workout. On slower systems these tests may take some time to complete.

Enabling APL Input in Emacs

Most Lisp developers interact with the language through Emacs, so Emacs is also the most convenient tool to write April programs. John Thingstad has created a dedicated Emacs APL input mode for April, you can download it and read instructions at its repository.

Another way to input APL characters in Emacs is using the gnu-apl-mode Emacs plugin. You can get it from the repository here or install it directly via the MELPA Emacs package repository.

Click here for information on using MELPA.

Once gnu-apl-mode is installed, you can switch to the APL input mode by typing M-x toggle-input-method or C-\. You will be prompted to enter the input mode to use, so enter APL-Z and then you'll be able to toggle APL input on and off by typing C-\. While in APL-Z input mode, you can enter APL characters by prefixing the key with a . period character.

Enabling APL Input in Vim

For Lisp developers who interact with the language through Vim, a plugin called "vim-apl" allows one to input APL characters. You can get it from this git repository. Using a Vim plugin manager called Vundle it is easy to add this plugin by adding the single line Plugin 'justin2004/vim-apl' to your .vimrc and following the Vundle instructions. With vim-apl installed, while editing an .apl file you can enter the iota character by typing `i (backtick and i), enter the rho character by typing `r, and so on.

Enabling APL Input Universally in GNU/Linux

For GNU/Linux users who'd like use APL characters outside of a customized editor, refer to this page on the APL Wiki. After following the instructions there you'll be able to use your keyboard's right Alt key as a modifier to enter APL characters. For instance, you enter can the iota character by pressing the right Alt + i, the rho character by pressing the right Alt + r and so on.

Another option for GNU/Linux APL input is to enter setxkbmap us,apl -option grp:win_switch in a terminal. The keyboard's win keys will become modifiers that allow you to enter APL characters when held down. Typing Win + r will produce , for example, and typing Win + % will produce .

Thanks to:

Tamas K. Papp, creator of array-operations, the basis for some of April's early function implementations.

Max Rottenkolber, creator of MaxPC, the heart of April's parsing engine.

justin2004 and Nikolai Matiushev, contributors of many bug reports and suggestions.

Dependencies (13)

  • alexandria
  • array-operations
  • cl-ppcre
  • cl-unicode
  • lisp-binary
  • lparallel
  • parse-number
  • prove
  • random-state
  • serapeum
  • simple-date-time
  • symbol-munger
  • trivia

Dependents (0)

    • GitHub
    • Quicklisp