Syntax-less Programming

2011 August 06

Niue is a programming language without syntax. It is easy to learn and makes an ideal extension language. Despite its simplicity, Niue can do anything a conventional programming language can do.

Programs written in Niue consist of literals and words. Anything that falls between brackets are treated as comments and are ignored by the interpreter. That is all there is. You have learned Niue. A little more explanation with examples might still be useful.

Following are examples of literals:

( numbers )
56
3.14159

( strings )
"hello, world"
'joe

A string without quotes will be treated as a word which maps to a value. This value could be a literal, another word or even a block of code. Such a code block can consume values that precede it and use them to compute new values. For example, we can input two numbers followed by the + word to find their sum:

3 4 +

The dot (.) word is used to print the top value on Niue's data stack:

.
=> 7

The following sections compare a few basic syntactic forms in the C language with their equivalent "syntax-less" expressions in Niue.

Variables

Following are some variable definitions in C:

char *name = "nemo";
int age = 2;

The same definitions in Niue will be:

'nemo 'name ;
2 'age ; 

The define (;) word, creates a new word and binds it to a value. Let us inspect the words that we just defined:

name .
=> nemo
age .
=> 2

A word can be re-bound to a new value. The following code increments the value of age and assigns the new value back to age itself:

age 1 + 'age ;
age .
=> 3

Functions

A C function to increment age:

int age;

void increment_age ()
{
    age = age + 1;
}

age = 2;
increment_age ();
increment_age ();
printf ("%d\n", age); // => 4

The Niue equivalent of this function will be the following word definition:

[ age 1 + 'age ; ] 'increment-age ;

The opening square bracket ([) is a word that asks Niue not to execute the instructions that follow. Instead, they should be compiled and saved. Niue stays in this "compilation" mode until a corresponding ] word is encountered. This sequence of compiled instructions makes up a code block. A code block is also a literal like a number or a string. So it can be assigned to a word. When Niue encounters a word that is bound to a code block, it is replaced by the value obtained by executing that code block:

2 'age ;
increment-age
increment-age
age .
=> 4

Let us re-write the C function so that it do not modify a global variable. Instead, it increments its argument - in place:

void increment (int* p)
{
   *p = *p + 1;
}

int age = 3;
increment (&age); 
printf ("%d\n", age); // => 4

It is evident from the function signature that it will work properly for only one numeric type - int. Even the range of integer values it can deal with depends on the host platform:

double tax_rate = 5.7829;
increment (&tax_rate); // tax_rate => ?
int worlds_population = 6953738051;
increment (&worlds_population); // worlds_population => ?

The Niue version of increment might look a bit complicated. But it is more versatile:

[ dup eval 1 + swap ; ] 'increment ;

3 'age ;
'age increment
age .
=> 4

Let us figure out what the increment word is doing. Fist it duplicates the string literal:

'age 'age

eval interprets a string literal as Niue code. As age denotes a word bound to an integer, it is replaced by its value:

'age 3

The next instruction increments this value by 1:

'age 4

swap transforms the above sequence to:

4 'age

As the last step, ; will rebind age to 4.

The increment word do not discriminate between various numeric types:

5.7829 'tax_rate ;
'tax_rate increment
tax_rate .
=> 6.7829

6953738051 'worlds-population ; ( http://www.census.gov/main/www/popclock.html, 
                                  10:31 UTC (EST+5) Aug 06, 2011 )
'worlds-population increment
worlds-population .
=> 6953738052

The increment function that we wrote in C is still not really a "function" because it modifies its argument. Here is a more functional increment :

int increment (int a)
{
    return a + 1;
}

int x = 100;
printf ("%d\n", increment (x)); // => 101
printf ("%d\n", x); // => 100

The same functional increment in Niue is just too simple:

[ 1 + ] 'increment ;

100 'x ;
x increment . 
=> 101
x .
=> 100

What if we need a function to increment and return two numbers instead of one? In C we have to define a data structure to hold those values:

struct point
{
    int x;
    int y;
};

struct point increment_point (struct point p)
{
    struct point new_p = p;
    ++new_p.x;
    ++new_p.y;
    return new_p;
}

struct point p;
p.x = 10;
p.y = 20;
p = increment_point (p);
printf ("%d:%d\n", p.x, p.y); // => 11:21

A Niue word that does the same job is just a one-liner:

[ 1 + swap 1 + ] 'increment-point ;

10 20 increment-point . .
=> 11 21

No special data structure needs to be defined as no restriction is imposed on the number of arguments and return values. This peculiar nature of Niue makes it easy to compose functions together. To understand what that means, let us contrast a few more C functions with their equivalent Niue words:

/* Doubles a value. */

int dbl (int x)
{
    return 2 * x;
}

/* Uses `dbl` to double a point. */

struct point dbl_point (struct point p)
{
    struct point new_p;
    new_p.x = dbl (p.x);
    new_p.y = dbl (p.y);
    return new_p;
}

struct point p;
p.x = 10;
p.y = 20;

/* First increment and then double a point. */

p = dbl_point (increment_point (p));
printf ("%d:%d\n", p.x, p.y); // => 22:42

Here the result of a function call becomes the argument of another. In Niue, the above sequence of function calls could be written in a more natural order:

[ 2 * ] 'double ;
[ double swap double swap ] 'double-twice ;

 
10 20 increment-point double-twice 
. .
=> 22 42

The above code demonstrates a common style of programming in Niue: We first input some data and compose together a sequence of words to transform that data into the desired result.

Note: It is possible to create complex data structures in Niue. In fact, a user-defined object in Niue is more powerful than an instance of a C structure because it can keep track of its own private state and respond to messages. It can even live in its own 'tiny' parallel process.

Sometimes we like to identify arguments by name. The following word that calculates the factorial of a number demonstrates how to do this. It also introduces the if and else words that are used to conditionally execute code blocks.

[ 'n ;
   n 1 = [ 1 ] if 
   [ n n 1 - factorial * ] else 
] 'factorial ;

4 factorial .
=> 24
10 factorial .
=> 3628800

Next is an iterative version of factorial. It makes use of the times word that executes a code block a specific number of times. For each execution the code block is passed the current value of the counter:

[ 'n ;
 L1 'r ; ( The L before 1 marks it as a "Large Integer". )
 [ 1 + r * 'r ; ] n times r ] 'factorial ;

10 factorial .
=> 3628800 

100 factorial .
=> 93326215443944152681699238856266700490715968264381621468592963895217599993229915608
941463976156518286253697920827223758251185210916864000000000000000000000000 

What if you want to pass values to a code block without caring about their order? You can define code blocks that emulate keyword arguments. Here is a word that finds the force of gravity between two objects, given their masses and the distance between the masses in meters:

( The Gravitational Constant. )
10 -11 ^ 6.673 *  'G ;

( Uses the formula: F = Gm1m2/d^2 )
[ 'm1 get-r 'm1 ;
  'm2 get-r 'm2 ;
  'd get-r 'd ;

  m1 m2 * d 2 ^ / G * ] 'F ;

( Values to be used by the code block can be specified in any order,
  just preceed each value by string to identify it. )

'm1 34.5 'm2 43.6 'd 100 F .
=> 1.0037526600000002E-11

'm2 43.6 'd 100 'm1 34.5 F .
=> 1.0037526600000002E-11

Links

  1. ACM Turing Lecture by Marvin Minsky has some favorable comments on 'Syntax-Weak' languages. Here is a quote from the talk: "Using BNF to describe the formation of expressions may be retarding development of new languages that smoothly incorporate quotation, self-modification, and symbolic manipulation into a traditional algorithmic framework."
  2. Famous Syntax-Weak languages: Lisp, Smalltalk, Forth.