Axis oriented programming

I was reading some APL when I realized I had invented a new style of programming language. It hasn’t been implemented as far as I know. The syntax is just any old syntax. I am trying to be non-cryptic, but if you are planning to implement this, you probably want better, more consistent syntax. This is supposed to be a cool new idea, some bits haven’t been fully thought through. If you are looking for inspiration in designing a programming language, this might be it.

Suppose you are dealing in numpy, and some of your arrays have like 10 or 20 dimensions. And you are finding it really hard to keep track and remember that axis 4 of array foo represents the same thing as axis 7 of array bar. Then array oriented programming is for you.

Conceptually, there are axis and there are data. If you see something like

Axis foo=Axis([5])
Int a=1
Axis bar=Axis([6])
Float b=bar.index.as_type(float)

Every variable like a and b can be imagined as actually being an array, with a dimension for every axis. There is no such thing as a constant, just an array that happens to be constant. [5] represents the type of numbers that are non-negative integers < 5. Ie 0,1,2,3,4.

bar.index is an array of type [6], which can be typecast to float.

A naive compiler/​ interpreter for this language would soon find every object becomes an exponentially large array as soon as more than a few Axis are created. Its the compilers job not to be that naive. It shouldn’t store fast grids of the same number over and over. A good compiler might use all sorts of tricks, like lazy evaluation, and https://​​en.wikipedia.org/​​wiki/​​Matrix_chain_multiplication to reorder operations. These are implementation details.

Simple functions using axis

Lets look at a few functions.

Sum:[Axis] -> Float -> Float

Takes the sum of the inputted array along the Axis. Places this value in every position in the array. The axes used must implement the summable characteristic. (Which pretty much means they must be finite)

Sort:Axis -> [Axis] -> Float -> Float

Takes an axis to sort along. It also takes a list of axis to take lexical priority over.

Sort foo [] is the standard sorting. Sort foo [bar] means to slice the array up into lists of length 6. Consult the first item in the list, if thats a tie, consult the second item.

All axes used here (oh and the type) must implement a sortable characteristic. (Axis are sortable if their underlying type is) For example an ennumeration axis Axis({Red, Green, Blue}) wouldn’t be sortable, but a date axis would.

I am not sure if you should be able to call sort on an axis, and implicitly sort all data along that axis eg

Axis student=Axis([20])
Int age= get_ages()
String name=get_names()
student.sort(age) 

And have the students names get automatically sorted by age.

Combining axis.

Int a=1
Axis foo=Axis([5])
Axis bar=Axis([5]) 
\\ do stuff
assert_combine(foo,bar)

The first 2 lines create a 5 by 5 array out of a. (and any other variables in use)

The assert_combine(foo,bar) step will assert that these axis have the same underlying types, and combine them, deleting any elements off the diagonal. This makes a and b alieses of the same axis.

If you are using a rust like ownership model.foo.assert_combine(bar) will gain ownership of and use up bar.

Functions like matrix inverse take in a tuple of axis and assert that they are the same.

If you are using floats to index your axis, you can’t handle every float. But using lazy evaluation, you can still evaluate at a point, or numerically integrate over a line.

Complicated Axis

The complex numbers behave kind of like an axis (Just add an Axis([2])and sum works fine, real parts at index 0, imag parts at index 1) , except that the rules for projecting a real numbers into complex is different. And multiplication is different. The Haskellish thing to do, is to let programmers define their own projection functions and own operations. Insist on rules like where is a programmer defined function that turns a number lacking this axis into one with this axis. And is a programmer defined function for things with the axis. (ie and complex multiplication. Don’t attempt to enforce these constraints programmatically. If someone attempts to multiply complex numbers without having defined the complex multiplication, raise a compile time error.) Also throw an error if anyone tries to make a complex bool, string or date. (Unless the programmer wrote a function that worked with dates.)

Maybe you also want axis representing all sorts of odd things. Like polynomials, which can be added and multiplied pointwise (like the float axis), but can also be added elementwise.

Any of this sort of stuff gives up on the idea of all axis types being compatible with all data types.

Variable Axis

Consider this code

Axis foo, Int b=from_list([5,6,8])
\\creates an axis of type Axis([3]), and an int b which is 5,6,8 along that axis.
Axis bar=Axis([b])
Int c=1

This creates a new axis with width 5,6,8. The resulting c resembles the python [[1]*5,[1]*6,[1]*8]. It consists of a total of 19 ones. Of course its still stored internally as a single 1, because of that compiler optimization for arrays that repeat themselves along an axis.

Note that now assert_combine has to compare lots of numbers to see if they are all equal. (In the worst case. If the user uses b twice to make axis, then that can be seen just by seeing the pointers are the same.) This isn’t a note about performance, the overhead of this should still be small compared with dealing with that many numbers.

Changing b after bar is created shouldn’t make c bigger. b could be immutable, or consumed in the creation of bar.

Note that the size of each axis can only depend on previously defined axis, making this a DAG structure that, in the worst case resembles nested lists.

(If you don’t like this, you can introduce special values that are actually constant to use as array sizes)