An Introduction to Ix
For years, I have had a dream. This dream has been a waking dream, a fantasy that preoccupies my waking thoughts. It is a mundane dream, in a sense: I dream of my ideal tool, the tool I use to practice my trade. Mundane in the sense that a carpenter dreaming of the ideal hammer is mundane.
The tool I use is more abstract. It’s the tool man uses to control machines, a programming language. Unlike natural languages, such as English or Spanish, programming languages are invented by men deliberately to serve their purposes. There are many of these languages, thousands. Why so many?
I started thinking about developing my own language in my first college programming languages course. “Wow, this is easy,” I thought after implementing my first interpreter. It’s not easy to make it run fast, but it’s relatively easy to make it exist.
My design of Ix has been influenced by the best of all the programming languages I’ve used. In a sense I’ve been most impressed by Lisp, where code is data, and programs can rewrite themselves. However, in order to achieve the code is data objective, Lisp had to give up a lot of readability and conciseness of which other languages are capable.
I felt that Lisp came very close to hitting the mark, the mark for which I myself am aiming (“the perfect programming language”), but ultimately missed. One of its failings is its choice of primitive data structure, the cons cell or the pair. During the development of Ix, I’ve made it a priority to select the ideal fundamental structure.
The language’s fundamental construct should be one with which we can build any data structure known to computer science. The cons cell fails at this. It can’t, for example, be used to implement arrays. Although Lisp’s syntax was designed so that “there is only one sort of stuff” (the cons cell), and all syntax is expressed with it, I think it’s the wrong primitive construct with which to approach computer science, and the wrong fundamental construct even for language syntax. You can see it break down when Lisp needs to represent arrays or associative structures. (‘a 1 ‘b 2) is not a clear way to represent that ‘a maps to 1 and ‘b to 2.
Having decided the ideal primitive construct can’t be cons, I set out to find it. Since in the language I am building data is code, that also means that the primitive with which I represent language syntax will also be the primitive with which to build data structures. The first structure I considered was the array. Indeed, an array of length 2 is a cons, so surely arrays are at least as good a choice.
I came very close to deciding upon the array as the basic structure, but what held me back was the feeling that the language should be able to support named parameters. Yes, that’s all. I hacked together a clunky implementation where the basic syntactical unit had both array- and map-like properties before I finally put things together after reading Steve Yegge’s blog post called The Universal Design Pattern. I don’t necessarily think he was meaning to convey the idea I took away from it. Steve intended to convey that (property) maps are a useful data structure with which to build programs; what I ended up realizing is that they are actually the ideal fundamental data structure for programming language syntax, and are the most fundamental structure in computer science.
To make this connection it helps to think of data structures in an abstract mathematical sense. Consider arrays and linked lists. What are the most basic properties of these structures?
I would argue that fundamentally, these data structures are associations, specifically functions. They associate position (index) to an element value. A linked list with a section of the Fibonacci sequence {5, 8, 13, 21, 34} is a function mapping the indexes {1, 2, 3, 4, 5} to the values, so:
| Index | Value |
| 1 | 5 |
| 2 | 8 |
| 3 | 13 |
| 4 | 21 |
| 5 | 34 |
An array with the same contents represents the same association. Looking at the situation with the greatest degree of abstraction, arrays and linked lists are two implementations of a function; being a function is their first property. Only when we move down a rung on the abstraction ladder does the performance of the two structures come into play, such as the lookup or insertion time.
Lisp-style functional programming has taken a rather different approach on the situation, looking at the cons cell as a general recursive type, which it is. But without the language having a generalized concept of collections, it’s hard to get there.
So, Ix is Lisp-like but starts with the premise that the basic unit of syntax is the map. The key is taking the realizations from earlier, and noting that elements in a map don’t have to be “named” in any sense. The key might just be an integer which is the element’s position.
Ix’s syntax starts out like Lisp. (f a b) represents calling the function f with arguments a and b. In many contexts, like at the beginning of a statement, a function call is implied, and so the parentheses may be omitted. Assuming I can properly avoid ambiguity, C-style function call notation may be used: f(a b).
For clarity, the symbols “;” or “,” can be used to delimit arguments, so our recurring example could be written (among other ways):
· f(a,b)
· f(a;b)
· (f a; b)
Function call syntax is a map. A named argument can be specified with the maps-to operator, which is the symbol “:”. Example: open-socket(host : “example.com”; port : 22).
Aside from the function call syntax “( )”, Ix has two other primitive notations: (explicit) maps, and blocks.
Collections
Maps, primarily known as collections, are created by placing an expression inside “[ ]”. Elements in the maps may be identified by name (explicitly) or by position (implicitly). For example, a map associating the symbol A with 1 and B with 2 can be written explicitly with the aforementioned maps-to operator:
[ a : 1; b : 2]
Elements which are not explicitly assigned a name are implicitly assigned a position. For example, the portion of the Fibonacci sequence mentioned earlier can be written:
· [ 5 8 13 21 34 ]
· [ 5, 8, 13, 21, 34 ]
· [ 5; 8; 13; 21; 34 ]
Ix presents this variety of syntax meaning the same thing to allow the expression of connotations – generally, for the programmer to use the syntax that “looks right”.
The syntax of function calls and the syntax of collections (maps) is thus the same; the only difference is that function calls indicate function-call evaluation, while the collection syntax indicates the creation of a collection.
Blocks
The remaining type of “object group” notation is the block, which uses “{ }”. Block syntax indicates the creation of an anonymous function; the contents of a block is a collection of expressions. For example, (+ a b) represents the sum of a and b (evaluated presently), while {+ a b} represents an anonymous function taking parameters a, b and returning their sum. When a block is evaluated, its constituent expressions are evaluated sequentially.
By default, parameters are not positional, so one would have to call such a function with named parameters. For example:
({+ a b} a : 1, b : 2)
The final primitive piece of Ix notation is the function binding operator, written “=>”. The block notation creates an anonymous function, and any contained variables are free variables. In order to evaluate the function, the variables must be assigned values. This can be done by calling the function with named arguments, or can be done by using the binding operator, which assigns function arguments positions. For example:
[a b] => {+ a b}
The result of the previous example expression is a function with two positional arguments.
Macros
So far, we’ve seen how to call functions, how to create collections of values, and how to create anonymous functions. We haven’t yet seen how to create variables or anything else.
A design goal of Ix is for the syntax to be unified, following the Lisp “code is data” mantra. In Ix, code is indeed data, and the creation of variables and other standard constructs is through special function calls.
An Ix macro is a function which takes as its arguments program fragments, called expressions. It may choose to evaluate these expressions, or may not, or may transform them to some other meaning. One important Ix macro is def, which creates named variables. The following example creates a variable n with value 3:
def n 3
How does it make sense for “n” to be an argument to a function call if the variable doesn’t exist? The answer lies in the concept of the macro: the macro isn’t a function of values, but rather program expressions. Thus, the def macro actually takes in the expression “n” in an abstract sense, and by inspecting it creates a variable binding.
Another macro is if, which takes in a value and two blocks, and acts as one would expect. Assuming some basic comparison operators, we now have enough tools to implement the Fibonacci function:
def fib [n] => {
if (> n 2) {
+ fib(- n 1) fib(- n 2);
} {
n;
}
}
What we have now is a syntax very similar to a high-level language, one that is much friendlier to humans, yet every component is formed from the simple primitives explained herein.
I haven’t yet fully designed the syntax and semantics for higher constructs like classes and interfaces, but I do have some ideas how they’d work. First, consider an additional operator :: which acts like Haskell’s “$”, which right-associates the remaining expression. In other words, assume the following expressions are congruent:
· g (f a)
· (g (f a))
· g :: f a
I'm hesitating t to post this publicly, just because I know the language is likely to undergo rapid evolution as I apply increasing scrutiny to the syntax, and begin implementing samples in the language. The precise syntax needn't be decided with finality this early on, partially because I'm designing the interpreter in a very abstract manner (working only on "expression tree" objects) so as to function as an API exposing the syntax for other consumers, but also because one of my goals is to allow the language to be "skinned" in the sense that translation to/from various syntaxes should be pluggable.

