Synopsis of the language Joy

The language Joy is a purely functional programming language. Whereas all other functional programming languages are based on the application of functions to arguments, Joy is based on the composition of functions. All such functions take a stack as argument and produce a stack as value. Consequently much of Joy looks like ordinary postfix notation. However, in Joy a function can consume any number of parameters from the stack and leave any number of results on the stack. The concatenation of appropriate programs denotes the composition of the functions which the programs denote. One of the datatypes of Joy is that of quoted programs, of which lists are a special case. Some functions expect quoted programs on top of the stack and execute them in many different ways, effectively by dequoting. So, where other functional languages use abstraction and application, Joy uses quotation and combinators -- functions which perform dequotation. As a result, there are no named formal parameters, no substitution of actual for formal parameters, and no environment of name-value pairs. Combinators in Joy behave much like functionals or higher order functions in other languages, they minimise the need for recursive and non-recursive definitions. Because there is no need for an environment, Joy has an exceptionally simple algebra, and its programs are easily manipulated by hand and by other programs. Many programs first construct another program which is then executed.

This paper is intended as an introduction for Forth programmers.

Introduction

2 3 +

To compute the square of an integer, it has to be multiplied by itself. To compute the square of the sum of two integers, the sum has to be multiplied by itself. Preferably this should be done without computing the sum twice. The following is a program to compute the square of the sum of 2 and 3:

2 3 + dup *

dup

dup

pop

swap

Unlike Forth, Joy has a data type of lists. A list of integers is written inside square brackets. Just as integers can be added and otherwise manipulated, so lists can be manipulated in various ways. The following concat enates two lists:

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

concat

[1 2 3 4 5 6 7]

Joy makes extensive use of combinators, even more so than most functional languges. In mainstream functional languages combinators take as arguments (lambda abstractions of) functions. In Joy combinators are like operators in that they expect something specific on top of the stack. Unlike operators they execute what they find on top of the stack, and this has to be the quotation of a program, enclosed in square brackets. One of these is a combinator for map ping elements of one list via a function to another list. Consider the program

[1 2 3 4] [dup *] map

map

[1 4 9 16]

In definitions of new functions, as in Forth, no formal parameters are used, and hence there is no substitution of actual parameters for formal parameters. After the following definition

square == dup *

square

dup *

Data types of Joy

dup

pop

swap

Integers and floats are written in decimal notation. The usual binary operations and relations are provided. Operators are written after their operands. Binary operators remove two values from the top of the stack and replace them by the result. Unary operators are similar, except that they only remove one value. In addition to the integer type there are also characters and truth values (Boolean), both with the usual operations.

The aggregate types are the unordered type of sets and the ordered types of strings and lists. Aggregates can be built up, combined, taken apart and tested for membership. A set is an unordered collection of zero or more small integers, 0.. 31. Literals of type set are written inside curly braces, like this {3 7 21} . The usual set operations are available.

A string is an ordered sequence of zero or more characters. Literals of this type string are written inside double quotes, like this: "Hello world".

A list is an ordered sequence of zero or more values of any type. Literals of type list are written inside square brackets, like this: [peter paul mary] or [42 true {2 5}] . Lists can contain mixtures of values of any types. In particular they can contain lists as members, so the type of lists is a recursive data type. The usual list operations are provided. Matrices are represented as lists of lists, and there is a small library for the usual matrix operations. To a large extent the operators on the structured types are identical. Lists are implemented using linked structures, and the stack itself is just a list. This makes it possible to treat the stack as a list and vice versa.

Quotations and Combinators

[ + 20 * 10 4 - ]

6

20

*

If the above quotation occurs in a program, then it results in the quotation being pushed onto the stack - just as a list would be pushed. There are many other ways in which that quotation could end up on top of the stack, by being concatenated from its parts, by extraction from a larger quotation, or by being read from the input. No matter how it got to be on top of the stack, it can now be treated in two ways: passively as a data structure, or actively as a program. The square brackets prevented it from being treated actively. Without them the program would have been executed: it would expect two integers which it would add, then multiply the result by 20, and finally push 6, the difference between 10 and 4. A combinator expects a quotation on the stack and executes it in a way that is different for each combinator. One of the simplest is the i combinator. Its effect is to execute a single program on top of the stack, and nothing else. Syntactically speaking, its effect is to remove the quoting square brackets and thus to expose the quoted program for execution. Consequently the following two programs are equivalent:

[ + 20 * 10 4 - ] i + 20 * 10 4 -

i

Some combinators require that the stack contains values of certain types. Many are analogues of higher order functions familiar from other programming languages: map , filter and fold . The map has already been explained.

Another combinator that expects an aggregate is the filter combinator. The quoted program has to yield a truth value. The result is a new aggregate of the same type containing those elements of the original for which the quoted program yields true . For example, the quoted program ['Z >] will yield truth for characters whose numeric values is greater than that of Z . Hence it can be used to remove upper case letters and blanks from a string. So the following evaluates to "ohnmith" :

"John Smith" ['Z >] filter

Sometimes it is necessary to add or multiply or otherwise combine all elements of an aggregate value. The fold combinator can do just that. It requires three parameters: the aggregate to be folded, the quoted value to be returned when the aggregate is empty, and the quoted binary operation to be used to combine the elements. In some languages the combinator is called reduce (because it turns the aggregate into a single value), or insert (because it looks as though the binary operation has been inserted between any two members). The following two programs compute the sum of the members of a list and the sum of the squares of the members of a list. They evaluate to 10 and 38, respectively.

[2 5 3] 0 [+] fold [2 5 3] 0 [dup * +] fold

Intermezzo: Lambda abstraction and application

square(x) == x * x

square == Lx : x * x

Such a definition would be used as in the following evaluation, where square is being applied (here using an explicit @ infix operator) to the argument 2:

square @ 2 (Lx : x * x) @ 2 2 * 2 4

square

x = 2

The two operations of lambda abstraction and application are complementary. They form the heart of the lambda calculus which underlies all the mainstream functional and imperative languages. Joy eliminates abstraction and application and replaces them by program quotation and function composition.

Doing without abstraction and environments

square == Lx : x x * square == Lx : x dup *

x

x dup *

x square == x x * x square == x dup *

In the last of these both sides start with the formal paramater x, and otherwise there are no further occurrences of x on either side. Would it be possible for the two occurrences on the left and right to "cancel out", so to speak? Yes indeed, and now the definition looks loke this:

square == dup *

The mainstream imperative languages have a state of associations between assignable variables and their current values. The values are changed by assignments during the run of the program. These languages also have an environment of formal / actual parameter associations that are set up by calls of defined functions or procedures. Purely functional languages have no state. But the mainstream functional languages are based on the lambda caclculus, and hence they have an environment. The purely functional language Joy has no state and no environment. The imperative language Forth has both.

As in other languages, definitions can be recursive in Joy. In the first line below is a recursive definition of the factorial function in one of many variants of conventional notation. In the second line is a recursive definition in Joy.

factorial(x) = if x = 0 then 1 else x * factorial(x - 1) factorial == [0 =] [pop 1] [dup 1 - factorial *] ifte

x

ifte

Recursive Combinators

[ factorial ] map

A high proportion of recursively defined functions exhibit a very simple pattern: There is some test, the if-part, which determines whether the ground case obtains. If it does, then the non-recursive branch is executed, the basis case of recursion. If it does not, then the recursive part is executed, including one or more recursive calls.

Joy has a useful device, the linrec combinator, which allows computation of anonymous functions that might have been defined recursively using a linear recursive pattern. Whereas the ifte combinator requires three quoted parameters, the linrec combinator requires four: an if-part, a then-part, a rec1-part and a rec2-part. Recursion occurs between the two rec-parts. For example, the factorial function could be computed by

[null] [succ] [dup pred] [*] linrec

[ [null] [succ] [dup pred] [*] linrec ] map

In many recursive definitions there are two recursive calls of the function being defined. This is the pattern of binary recursion, and it is used in the usual definitions of quicksort and of the Fibonacci function. In analogy with the linrec combinator for linear recursion, Joy has a binrec combinator for binary recursion. The following will quicksort a list whose members can be a mixture of anything except lists.

[small] [] [uncons [>] split] [swapd cons concat] binrec

Mathmatical Foundations of Joy

Concatenation is a binary constructor, and since it is associative it is best written in infix notation and hence no parentheses are required. Since concatenation is the only binary constructor of its kind, in Joy it is best written without an explicit symbol.

Quotation is a unary constructor which takes as its operand a program. In Joy the quotation of a program is written by enclosing it in square brackets. Ultimately all programs are built from atomic programs which do not have any parts.

The semantics of Joy has to explain what the atomic programs mean, how the meaning of a concatenated program depends on the meaning of its parts, and what the meaning of a quoted program is. Moreover, it has to explain under what conditions it is possible to replace a part by an equivalent part while retaining the meaning of the whole program.

Joy programs denote functions which take one argument and yield one value. The argument and the value consist of several components. The principal component is a stack, and the other components are not needed here. Much of the detail of the semantics of Joy depends on specific properties of programs.

However, central to the semantics of Joy is the following, which also holds for the purely functional fragment of Forth:

The concatenation of two programs denotes the composition of the functions denoted by the two programs.

One part of a concatenation may be replaced by another part denoting the same function while retaining the denotation of the whole concatenation.

One quoted program may be replaced by another denoting the same function only in a context where the quoted program will be dequoted by being executed. Such contexts are provided by the combinators of Joy. These denote functions which behave like higher order functions in other languages. There is no equivalent mechanism in Forth.

The above may be summarised as follows: Let P , Q1 , Q2 and R be programs, and let C be a combinator. Then this principle holds:

IF Q1 == Q2 THEN P Q1 R == P Q2 R AND [Q1] C == [Q2] C

Joy Algebra

Q1

Q2

P

R

P Q1 R

P Q2 R

Q1

Q2

As premises one needs axioms such as in the first three lines below, and definitions such as in the fourth line:

(+) 2 3 + == 5 (dup) 5 dup == 5 5 (*) 5 5 * == 25 (def square) square == dup *

2 3 + square == 5 square (+) == 5 dup * (def square) == 5 5 * (dup) == 25 (*)

Consider the following equations in infix notation: The first says that multiplying a number x by 2 gives the same result as adding it to itself. The second says that the size of a reverse d list is the same as the size of the original list.

2 * x = x + x size(reverse(x)) = size(x)

2 * == dup + reverse size == size

Other equivalences express algebraic properties of various operations. For example, the predecessor pred of the successor succ of a number is just the number itself. The conjunction and of a truth value with itself gives just the truth value. The less than relation < is the converse of the greater than relation > . Inserting a number with cons into a list of numbers and then taking the sum of that gives the same result as first taking the sum of the list and then adding the other number.

In conventional notation these are expressed by

pred(succ(x)) = x x and x = x x < y = y > x sum(cons(x,y)) = x + sum(y)

id

succ pred == id dup and == id < == swap > cons sum == sum +

dip

In the first example below, the dip combinator is used to express the associativity of addition. Another combinator is the app2 combinator which expects a program on top of the stack and below that two values. It applies the program to the two values. In the second example below it expresses one of the De Morgan laws. In the third example it expresses that the size of two lists concat enated is the sum of the size s of the two concatenands. The last example uses both combinators to express that multiplication distributes (from the right) over addition. (Note that the program parameter for app2 is first constructed from the multiplicand and * .)

[+] dip + == + + and not == [not] app2 or concat size == [size] app2 + [+] dip * == [*] cons app2 +

Joy and Forth

The similarities and differences between Joy and Forth are striking and profound. They have been discussed in the mailing group which can be found at

http://groups.yahoo.com/group/concatenative . The main Joy page is on

http://www.latrobe.edu.au/philosophy/phimvt/joy.html .