Session 29
The Next Big Thing?

Where Are We?

You are implementing the third version of a small language for programming colors, Huey. For Homework 9 and Homework 10, you produced tools for a language consisting of colors, operators for manipulating them, and local variables. For Homework 11, you are adding sequences of statements and mutable data.

We are closing the semester by considering other programming language issues you hear about in modern software development. Last time, we learned about a technique for optimizing an interpreter, using an odd little language for our laboratory. Today we consider an even odder little language that enables us look to the future.

A Quick Puzzle: An Odd Little Language

There's this language that operates on a stack...

Read the top of this handout, which describes a Turing-complete stack-based language, and then trace the three programs at the bottom of the handout:

302 1000 500.0 / -.



4.95 5.0 0.1 rollup - abs >=.



10 6 swap dup * swap dup * - sqrt.

Some of these may look familiar... if you squint hard enough.

The Future, Then and Now

Strangely, the future of programming may look and sound like 1940s jazz scat. Hey to the great Ella Fitzgerald.

A Different Road

In addition to stretching your mind with Racket's prefix notation, we've occasionally talked about postfix notation this semester. Consider this little postfix interpreter that supports binary operators:

> (postfix 2)
'(2)

> (postfix 2 3 +)
'(5)

The expressions in green are programs in what is sometimes called a stack-based language. Postfix notation, also called Reverse Polish notation, is the first thing that many conventional programmers notice when working in a stack-based language. It corresponds to a postfix traversal of a program tree.

We can write longer programs, too:

> (postfix 2 3 + 5 *)
'(25)

This program is equivalent to 2 + 3 * 5 . Er, make that (2 + 3) * 5. As long as we know the arity of each procedure, postfix notation requires no rules for the precedence of operators.

My little interpreter is returning the state of its data stack. The parentheses expose that I've implemented my stack using a Racket list. Our stack could finish with more than one value on it:

> (postfix 2 3 + 5 * 6 3 /)
'(2 25)

Adding an operator to the end of our program can return us to a single-value stack:

> (postfix 2 3 + 5 * 6 3 / -)
'(23)

This points out a more general feature of stack programs: we can compose programs simply by concatenating their source code:

> (postfix 2 3 +)
'(5)

> (postfix 2 3 + 2 *)
'(10)

2 * is like a new program — "double my argument" — though, in this style of programming, there are no arguments. All programs read from the stack and leave their results there. The * operator requires two arguments, but the program pushes only one. So this program requires the stack to contain at least one value.

We can place this program immediately after any other program (*) and create a new program. For this reason, another name for this programming style is concatenative programming. It turns out that the stack is really just an implementation detail — and an especially convenient one.

(*) that leaves at least one value on the stack!

The Joy Language

The language you played with in our opening exercise is Joy. It is a Turing-complete stack-based language in which programs are written postfix.

Joy style is concatenative. As we saw above, if we have two sub-programs that compute partial results, we concatenate two sub-programs to compute a compound result. For example, two plus (two times the square root of 16) is:

16 sqrt 2 *   2 +
-----------   ---
^             ^
|             |-- second program
|
|-- first program

If we look at a program written in postfix notation, it seems that data flows from right to left, and the application of operators flows from left to right.

Look at the source file.

You traced three programs on the opening exercise. All were problems on Homework 2:

  1. the candy temperature problem
  2. the in-range? function
  3. the ladder-height problem

Solutions to these problems are included in a file of Joy programs in today's code. Things to note:

What Makes Joy Different?

The same file of Joy programs also includes implementations for two modular arithmetic functions, times and minus, which are sometimes assigned as homework problems in this course. These problems are complicated enough that we start to see some of the things that make languages like Joy different.

This style is very different. Learning it is a much bigger change than learning Racket, or even BF.

What are those bracketed bits of code? Brackets denote a list. A list can contain operators. There are higher-order functions that treat lists as programs!

[1 2 3] [4 5 6 7] concat.

[1 2 3 4] [dup *] map.

DEFINE square ==  dup  *.
[1 2 3 4] [square] map.

5  [1]  [*]  primrec.

The last example shows us that

DEFINE factorial == [1]  [*]  primrec.

This language may look impossible to you after only a few minutes. Even so, we can write amazing programs in Joy:

  1. This file contains a function that solves the Towers of Hanoi.
    "jp-nestrec.joy" include.
    []  3  r-hamilhyp.
    
  2. This file contains a Joy interpreter written in Joy.

Yes, those programs look weird — really weird. They may even look hard or scary. You probably felt the same way about Racket fourteen weeks ago. Take a look at the code you are writing for Homework 11 now. Python and Java programs look weird and scary to many beginners — until they get used to them!

A Joy Interpreter

Now that you have begun to write your own interpreter for a small language, you can appreciate what it takes to implement Joy. Knowing how interpreters work can also help you understand the language better! Take a look at this Joy interpreter, written in Racket.

I wrote a simple string parser, so that I don't have to rely on Racket to read Racket-friendly expressions. This means I don't need to wrap parentheses around my sentences.

In the interpreter itself, we see the same sort of recursive evaluator we saw in the postfix interpreter early, with a dose of your own language interpreter style...

What Really Makes Joy Different?

Joy really looks different now. What are the big ideas?

Here's one: No names for data.

Functional programming gave up state and assignments, but it still used names: parameters and local variables. Joy uses the stack for all of its data values, so we don't need to name arguments or parameters.

We don't apply functions. We compose them.

We say that Racket and languages like it (every language you use?) are applicative. Think about a Racket expression: on (+ 2 3), the evaluator applies the + procedure to its arguments 2 and 3. The + is treated differently from the 2 and the 3. We can pass functions as arguments, but they are treated differently when used.

To find the square root of the square root of a number in Racket, we would say (sqrt (sqrt num)). In Python and Java, we would say sqrt(sqrt(num)). These apply the square root function twice.

Concatenative programming uses function composition rather than function application. This is the defining difference between languages such as Joy and the the languages most of us use on a daily basis.

Here is the square root of the square root in Joy:

sqrt sqrt               # computes (sqrt (sqrt arg))

In practical terms, writing code in a concatenative language is not all that different from programming in a functional language, except that there is less nesting of functions. Rather than writing:

(f0 (f1 (f2  ... (fn x) ...)))

we could write:

x fn ... f2 f1 f0

But under the hood, there is a big difference.

Racket is based on the lambda calculus, as are most functional programming languages. The lambda calculus is simple, yet it requires three kinds of term: variables, lambdas, and applications. It also requires several rules for replacing variable names with their values, as well as the concepts of with bindings, closures, and scope. This is quite a bit of complexity.

Concatenative languages have a much simpler core. They require only functions and compositions. We don't even need an evaluation rule, because evaluation is just the composition of functions. It never has to deal with named state, so there are no variables. Without variables, there is no mutation. This means that concatenative languages are in a certain sense more functional than the languages we usually call "functional"!

I just said, "There are only functions and compositions". But wait. Recall this program from above:

2   16 sqrt 2 *   +
-   -----------   -

The + is easy enough to fit into the system. But what about the 2? Or the 16 sqrt 2 * part?

All three parts are programs. Everything is a function, being composed by concatenation. Even 2 is a function: a constant function that takes no arguments from the stack:

          2 === (lambda () 2)                  # I'm mixing Racket
16 sqrt 2 * === (lambda () (* (sqrt 16) 2))    # and Joy syntax...

This approach has a different sort of unity of representation that leads to a need for a new kind of data type for functions.

Why Might This Be The Future?

New section — still just an outline....

From the machine's perspective

Many computer architectures are stack-based. The virtual machines for Python, Java, and JavaScript all use assembly language with a stack.

For example, the JavaScript function function(x, y) { return (x + y) == 1; } compiles to:

LocalGet 0
LocalGet 1
Add
Const 1
Eq
Return
From the programmer's perspective

Stack-based languages operate at a higher level of abstraction. We can manipulate functions without worrying about lower-level details. There are no names for args or parameters. All types are compositions of other types.

Programs are sequences

It is easy to manipulate Joy programs. They are simply sequences of tokens. This supports refactoring, combining, and packaging code.

As a result, they are amenable to a particular sort of machine learning: genetic programming.

The idea behind genetic programming is enticing: Programs are "encoded" as genes that can be modified using an evolutionary algorithm. A population of programs can evolve toward a program most fit for solving a program.

Joy programs already look like genes!

This file contains two operators for genetic programming. Try:

(crossover '(16 sqrt 2 * 2 +)
           '(2 3 + 5 * 6 3 / -))

(mutate '(16 sqrt 2 * 2 +))

Stack-based languages offer a simple test bed for machine learning. We can find new solutions to problems, optimal solutions, etc.

Demo Alex's program.

Concatenative Programming in the World

Unix

Unix operating systems such as Linux have many connections to concatenative programming. They come with many built-in tools, including dc:

dc is the oldest language on Unix; it was written on the PDP-7 and ported to the PDP-11 before Unix [itself] was ported.
Ken Thompson

dc is probably in your Unix:

4 7 + p

(Many high school students learned to program by hacking on their Hewlett-Packard HP-28 and HP-48 calculators back in the '70s and '80s, using its stack-based language, "RPL".)

But the most common example of concatenative programming in the wild is Unix pipes. They operate on string values, but their mode of operation matches what Joy does. Instead of stack operators like dup and swap, they have various operators for manipulating the data stream:

Programming Languages

As noted above, concatenative languages lie at the heart of several systems used by traditional programmers every day including the Java virtual machine, the CPython engine, and JavaScript running in the browser.

The Forth programming language is used to program embedded microcontrollers, especially in the medical industry.

The language that drives many computer printers, Postscript, is a dynamically typed, concatenative programming language. Check out the examples on the Wikipedia page. After having seen some Joy, Postscript will look familiar. It's actually a very readable language.

Mainstream Programnming?

Will Joy be the story of 2034 or 2049? I doubt it, but concatenative programming may be. For a more likely candidate language, check out Factor. It is a concatenative language that aspires to be a more complete tool for sytems programming and has some interesting features, including static type checking and modules. Though it is still young, Factor has good cross-platform support, an IDE with a modern feel, and a growing open-source community.

Wrap Up