Nevertheless this simple (in modern terminology “0-order”) language was rich enough to make the point that iterative algorithms could usefully be expressed in terms of equations.

However Ed Ashcroft and I keenly felt the absence of subroutines/procedures/functions (or whatever you want to call them) and agonized over the form they should take and the semantics they should have. Finally it dawned on us that in their most general form they denoted functions from streams to streams – what are now called “filters”, after UNIX.

For example, the square function maps e.g. the stream

1, 2, 3, 4, 5, …

into the stream

1, 4, 9. 16, 25, …

But filters like these – which correspond to data functions – are only a tiny fraction of the possibilities. More interesting is the running sum filter which transforms, say,

1, 2, 0, 3, 1, 5, …

into

1, 3, 3, 4, 9, …

In terms of computations, it acts like an OO object that has an internal memory which stores the sum so far.

All this alarmed us because the question was, how were we going to implement these filters? Implementing OO is a big job. Will we need coroutines and concurrency? It didn’t look very hopeful.

We’d already run into a problem with the 0-order language. We thought we could translate programs into dataflow nets but the standard dataflow model is eager (data-push). It is inconsistent with the interpretation of programs as sets of equations.

**Eduction**

Eventually David May (at Warwick) and Tom Cargill (at Waterloo) (both grad students at the time) came up with the solution (separately), namely time stamped data-pull. Their interpreters worked by demanding values of variables at specified times, these demands usually generating demands for possibly different variables at possibly different times. The whole is driven by demands for output at times 0, 1, 2, 3, …

This works without any memory but is hopelessly inefficient. Acceptable performance requires a cache of *variable,timetag→result* entries in what we called the “warehouse”. We named the whole process “eduction“.

Eduction solves the problem of implementing the 0-order language, but not that of “UDFs” (User Defined Filters/Functions). What does it mean to demand the 5th component of *foo(X,Y)*? This must produce demands for elements of *X *and *Y*; but how?

**The Ostrum Interpreter**

The solution showed up one day in the form of a large C program written by Calvin Ostrum, then an undergraduate student at Waterloo. It was an interpreter for what Ostrum called “Luthid”, a version of Lucid based on Lisp datatypes. It had limited error handling, no storage management of the warehouse, and the source was incomprehensible. But it supported UDFs.

At that time my first student, Azio (Tony) Faustini was a Warwick postdoc. We studied the Luthid interpreter very carefully and discovered that Ostrum had worked out a coordinate system for the calling trees of programs with UDFs. These coordinates constituted a second dimension (which we called the “place” dimension) and the place tag together with the time tag and variable name form the demand. A user defined function *foo(p,q)* becomes an ordinary variable that varies over place and time, as do its formals *p* and *q*. In other words, a first order program is compiled into a 0-order program with extra operators. Ostrum did not use the OO object interpretation of filters.

**The pLucid Interpreter**

Faustini cleaned up the Luthid interpreter, added excellent error handling and reporting and implemented our “retirement scheme” cache management system. We also converted it to use, not Lisp, but the data types of POP-2, an AI language invented at Edinburgh. POP-2 had basically the same data types and operations as Lisp but used a more user-friendly infix notation. The resulting language we called “pLucid” (POP-2 based Lucid) and was widely used and was the language discussed in the “Lucid the Dataflow Language” book. (Incidentally POP-2 is itself dead but lives on as POP-11.)

**Yaghi Code**

At about the same time my PhD student Ali A. G. Yaghi formalized and extended Ostrum’s place coordinates. He described them algebraically in terms of intensional logic, the logic of context sensitive expressions.

I’ll describe the simplest case. Suppose we have the program

```
fib(a+b)
where
a = 1 fby a+1;
b = 5;
fib(n) = if n<2 then 1 else fib(n-1)+fib(n-2) fi;
end
```

To transform this into Yaghi code we number the occurrences of calls to *fib*: call 0 is the call with argument (actual parameter) *a+b *and calls 1 and 2 have actuals parameters *n-1* and *n-2 *respectively. We replace the *i*th call with the operator *calli* applied to *fib* and define *n* to be the operator *actuals* applied to the actuals listed in order. The program becomes

```
call0(fib)
where
a = 1 fby a+1;
b = 5;
fib = if n<2 then 1 else call1(fib)+call2(fib) fi;
n = actuals(a+b,n-1,n-2)
end
```

and the magic is done – we have transformed an order 1 program into an order 0 program (*calli* and *actuals* are built-in operators, not UDFs).

The transformed program is evaluated in the context of a place code, a finite sequence of small natural numbers (in this case 0, 1, or 2) that can be thought of as coding a position in the calling tree of *fib*. The basic rules are that

*calli(f) @ <p0, p1, p2, ….> = f @ <i, p0, p1, p2, …>**actuals(a0, a1, a2, …) @ <p0, p1, p2, ….> = a(p0) @ <p1, p2, p3, ….>*

Recursive programs like those above can require arbitrarily long place codes. However the interpreter does not have to drag around arbitrarily long sequences. We use a technique called hash-consing that gives every sequence a unique natural number code and only this code is used, say, as part of the warehouse tag.

**pLucid resurrected**

The language pLucid fell out of use in the 1980s and was succeeded by GLU (granular Lucid), a Lucid dialect designed and implemented at SRI in California. GLU’s most novel feature was unlimited user-declared dimensions, in addition to place and time. (pLucid had multiple space dimensions but this was undocumented). GLU was developed by Faustini, Jagannathan, and Ashcroft.

Unfortunately GLU also fell out of use when its inventors all left SRI. That was also the end of the available Lucid interpreters. The GLU interpreter was privately funded and proprietary. Even I have no access to it. It will likely never see the light of day again.

As for the pLucid interpreter, it was written in a day when C compilers were much more forgiving. Contemporary attempts to compile it produce warnings or errors approximately every 10 lines. A lost cause.

Thus began a dark age of about two decades in which there was no Lucid interpreter available. I’d started a project based on what I called the “pop shop” written in C (a lot of C) but it was seriously over-engineered and in the end came to nothing.

Flash forward to 2019. Having recently retired I had time on my hands and decided (foolishly?) to redo the popshop but in Python and minus the over engineering. What made me choose Python was simplicity, popularity, and dictionaries (which incorporate hash consing).

It all went quicker than I had any right to hope for and by early 2020 (otherwise the *anus horribilis*) I had a full-featured pLucid interpreter which, of course, I had no alternative but to call PyLucid. PyLucid extended pLucid by the tiny step of having a single space dimension. (I’m still not sure how to implement pLucid’s (unofficial) infinitely many space dimensions.)

**Dimensionality Analysis**

One problem remained namely that of determining the dimensionality of variables and expressions – the set of dimensions actually needed to compute a value, leaving out those that are irrelevant. This is important because to store a value in the warehouse the tag (label) must not contain irrelevant dimensions because then the same result my be stored multiple times with tags differing only in irrelevant dimensions.

My current student Monem Shennat has been working on this problem for 0-order programs. The basic idea is that you iteratively refine approximations to the dimensionalities by recomputing them following rules such as that the next approximation to the dimensionality of *A fby B* is the union of the approximations of the dimensionalities of *A* and *B* and *{t}.* Recomputation continues until the approximations settle down, i.e. there is no change anywhere from one computation to the next.

We both got stuck, however, on programs with UDFs. How does the dimensionality of *foo(A,B)* depend on the dimensionalities of *A* and *B*? That depends on the definition of *foo*, but how? One of the ideas was to abstract some sort of matrix that captures the dimensionality behaviour of *foo*, but how?

Then it occurred to me that we could evaluate the Yaghi coded version of the program over the (small) domain of dimensionalities (*{{}, {t}, {s}, {s,t}}* with subset ordering). There was a big snag with this, namely that for programs with recursion the evaluation does not terminate. The reason is that *if-then-else-fi* uses a worst case analysis and doesn’t handle actual Booleans.

Finally it dawned on me that we could instrument the interpreter with counts that measure how far the (nonterminating) evaluation has proceeded. These counts could include the number of evaluations and the length of the longest place code (the recursion depth). Then we could run the interpreter with small bounds on these measures, re run it with larger bounds, until it settles down.

I adapted the evaluator to a version that ran over dimensionalities. It worked better than I expected and settled down for very small values, e.g. place codes of length less than 4. When I ran it on the example given above, it produced output *{t}* meaning that the program varies in time, correct. Notice that *fib* has no temporal operators. The alternate evaluator correctly detected that *fib* passes on the time sensitivity of its actual *a+b* (+ in turn passes on the time sensitivity of *a*).

Of course the alternate evaluator also works fine on 0-order programs without Yaghi operators. This saved Shennat the trouble of implementing his 0-order rules.

**One more thing**

With Yaghi code 40 year old technology saved the day. But in spite of its age it still has a lot to offer. For example, in the 1990s P. Rondogiannis and I discovered that a simple extension of Yaghi code could allow us to compile some arbitrarily higher order programs.

The idea is simple enough: use Yaghi operators to eliminate 0-order arguments turning a (say) order 5 program into an order 4 program. Then use a *duplicate set* of Yaghi operators to eliminate what were order 1 arguments. In this way we can compile an order 5 program into a 0-order program with 5 sets of Yaghi operators using 5 isomorphic place dimensions.

Unfortunately this doesn’t work for all programs, even all typed higher order programs. It can’t handle partial application, for example, a function which returns another function as it value. Nevertheless it will handle many useful programs, for example one with a second order function which takes a first order function as its argument and returns an approximation to the integral of this function over a given range. Typical, most numerical analysis programs are at most second order and amenable to this extended Yaghi treatment.

So why doesn’t PyLucid have such higher order functions? The problem is the form such programs must take. We all worked hard to make PyLucid dynamic: type errors are caught at runtime and programmers do not have to provide cumbersome complex type declarations. If we could find a way to write higher order programs without them, we would waste no time adding higher order functions to PyLucid. One idea is to have the interpreter infer the necessary types – we’re looking into this.

**Almost Forgot …**

… that I don’t know how to handle the place dimension in computing dimensionalities. We need to know whether or not the place dimension *p* is relevant to a variable to avoid warehouse duplication etc. Obviously this means using the set-inclusion partial order over the domain

*{{}, {s}, {t}, {p}, {s, t}, {s, p}, {t, p}, {s, t, p}}*

but it’s not clear what the rules are. Definitely *actuals(x,y,z,…) *is sensitive to p but *calli(f)* may not be (the example program’s output is *call0(fib)* and is not sensitive to p). The interpreter should compute the dimensionality of the program as *{t}*, not *{t, p}*.

This is related to the problem with infinite dimensions. Faustini’s pLucid allowed arrays of arrays of arrays … and this implied an indefinite series

*s0, s1, s2, …*

of space dimensions. How do you evaluate over an infinite domain? Perhaps by incrementally restricting it to finite subsets ?

I’m looking into it …

]]>To make things simple, in the form of a zip file – I’ll put it on GitHub if there’s enough interest.

Just read README.txt and follow the instructions. As it says you need Python 3, 3.10.1 being the latest stable version. . It all should work straight out of the box.

**A quick summary of PyFL**

It”s all implemented on top of Basic PyFL, a stripped down lazy functional language. Basic PyFL has the data types and infix operators of POP-11, lambda expressions, and *valof* clauses (basically *where* clauses), and function application. Function application uses conventional mathematical notation, e.g. f(a,b) instead of (f a b) as in Haskell.

Basic PyFL was implemented from scratch using function closures and similar standard techniques. It is dynamically typed – no type declarations and no constraints on application (self application in particular is supported).

Once Basic PyFL was implemented I proceeded to add more and more extensions, some quite novel.

**Conventional function definitions**

Mathematicians don’t normally use lambda expressions to define functions. Instead of writing

`sumsq = lambda (x,y) x*x+y*y;`

they are more likely to write

`sumsq(x,y) = x*x+y*y;`

This notation is implemented by simple translation – definitions using conventional notation are replaced by the version using lambda. Naturally this involves a complete ‘tree walk’ of the entire program because conventional definitions may occur in values deeply nested in other expressions.

The next feature added is *where* clauses. These are stylistically more convenient but cause parsing problems and so are not a basic feature. They, too, are implemented by a simple translation. A *where* clause with subject (head) *s* and set *body* of definitions is rewritten as a *valof* with the same body except that the definition *result=s* is added.

With *where* and *lambda* we can write a non recursive factorial program:

```
fac(7)
where
fac = Y(A);
A(f) = lambda (n) if n==0 then 1 else n*f(n-1) fi;
Y(a) = g(g) where g(x) = a(x(x)); end;
end
```

Notice that no variable is defined directly or indirectly in terms of itself. This definition of the fixed point combinator Y clearly depends on self-application, which Haskell’s type system won’t allow. I was pretty happy when the PyFL interpreter produced 5040, the correct answer.

Another feature added was multiple returns, so that you can write e.g.

min, max = bounds(x)

This is implemented with a simple translation but I won’t go in to it.

The next feature added was VBOs – variable binding operators, like the existential quantifier ∃ or the sigma (summation) operator ∑. They work exactly like their math counterparts except the ranges are lists and they are expressed in ASCII notation. Thus we can compute the primes less than 100 as

```
res = [2 3 5 7] <>
those n in rg(2,100):
forall p in [2 3 5 7] :
n mod p != 0
end
end
where
rg(l,u) =
if l>u then []
else l :: rg(l+1,u)
fi;
end;
```

These VBOs are disguised loops, even though PyFL is a functional language. They are implemented by translating into expressions using map, filter, and reduce.

My next step was to jump right in and add genuine *while* loops. We can use a *while* clause to compute the primes less than 100 as follows

```
res =
while n <100
n = 3 fby n+2;
nprime = not exist p in primes: n mod p == 0 end;
primes = [2] fby if nprime then n :: primes else primes fi;
result = primes;
end
```

There are three loop variables, `n, primes`

, and `nprime`

. The first is a counter that enumerates the odd numbers starting at 3, the second is a (reverse) list of the primes we have found so far, and *prime* is true or false depending on whether or not the current value of *n* is a prime (not divisible by any prime we’ve found so far).

It looks like Lucid (as intended) but is not Lucid; `fby`

is just a syntax word that separates the initial expression from the next value expression of the recurrence relation. It’s translated into a tail recursion with the loop variables becoming formal parameters of the tail recursively defined function.

Next I took a break from innovating and produced a REPL program – REPL stands for Read, Evaluate, Print, Loop. The REPL is invoked by the command `repl`

and when run it prints a welcome message and a list of real commands. It declares the current input file, if any, then gives a prompt consisting of a % proceeded by the name of the current import, if any. For example you might see

`exp%`

which means the current input file is `exp.`

The various commands allow you to manipulate the current import, whose source is stored in the file `buffer`

and whose name is in the file `current`

import (don’t touch these files directly). For example, to see the source use the command *b* or better yet,* f*, which invokes the pretty printer to format it. If you want to edit the file in the buffer use *v*, which invokes *vi.*

The file in the buffer is a set of definitions, not a program, so it doesn’t make sense to ‘run’ it. Instead, you can evaluate a PyFL expression and the evaluator will take the definitions into account if need be. What I usually do, as in the program given above, is equate the variable *res* to the expression of interest and use the command

`primes% e res`

The evaluator evaluates the expression following the *e* and prints the result, followed by some statistics.You can enter any PyFL expression, it doesn’t have to use the definitions in the buffer.

There’s more but you can discover it for yourself. PyFL is well documented by README.txt file, the REPL, and my numerous blog posts (my blog can now be found at `billwadge.com`

)

A few notes. If you pay attention to the statistics, it’s obvious that for some programs the evaluator is ridiculously inefficient – huge numbers of list operations. This will be fixed in a future release. Also, I overlooked some vital opportunities for caching. I discovered, to my disgust, that evaluating res*res takes twice as long as evaluating res or res**2. This will also be fixed.

I put a lot of work into catching and handling errors, but not enough. It’s still easy to crash the software (generate a traceback). This will get harder in future releases.

So please download the zip file and try it out. Write your own PyFL programs, and, if you’re feeling adventurous, modify the source of the interpreter for e.g. better error handling or new features. Let me know of any interesting results and I’ll discuss them in my blog and maybe incorporate them in a future release (with appropriate credit given).

]]>(PYFL = Python based functional language; henceforth PyFL)

Of course writing a tictactoe player is hardly a major challenge, but I wanted to see how it turned out in PyFL. It worked out rather well, as you’ll see. It made good use of most of PyFL”s new features, especially variable binding operators.

Let’s start with boards. I number the cells 1-9 in the usual way, so that e.g. 5 is the center square and 9 is the bottom right corner. The players are the words “X” and “O” and an empty cell is “.”.

A board is represented as a function that assigns one of these three values to every cell. Thus we have

```
cells = [1 2 3 4 5 6 7 8 9];
startingboard(c) = ".":
```

(I’ll put the complete program at the end as an appendix.)

The main part of the program is a big *while* loop (*while* loops are translated into tail recursion).

The loop variables are *board*, *player* and a counter *ix* and their recurrence definitions are

```
board = startingboard fby MakeMove(move, player);
player = X fby opponent(player);
ix = 1 fby ix+1;
```

The variable *move* is the move made by the current player. If the current player is X, you (who are playing X against the computer) are prompted to input your move. Otherwise the move (for O, who is being played by the program) is calculated by a formula.

The program does not use a min/max algorithm. Instead, it uses a half dozen prioritized heuristics which I believe make it unbeatable. These heuristics needed twiddling because on two occasions I managed to beat the program I thought was unbeatable. The heuristics are, in order of priority:

- – Make a winning move (for O) if there is one
- – Block a win for X, if X has a winning move
- – Take the center, if it’s open
- – Take a corner, if it’s empty and surrounded by X’s
- – Take the middle of a side or bottom or top, if one is free
- – Take a corner, if one is free
- – Take one of whatever’s left

When there is more than one move which satisfies the heuristic, the program chooses one at random.

The rest of the program codes up these heuristics and displays the sequence of moves and board images.

First, making a move: the result is a function identical to the current function except for its value at the move being made:

`MakeMove(p,m) = b where b(c) = if c==m then p else board(c) fi;`

(this uses a *where* clause, which is often clearer than a *valof*);

Next we have the crucial definition of a winning move for a player. It’s one which is the open cell in a line (vertical, horizontal or diagonal) in which the other two cells are occupied by the player.

```
WinningMoves(p) =
(% HasWinningMove, WinningMove %)
where
lines = Openlines(p);
HasWinningMove = lines != [];
l = hd(lines);
WinnningMove = firstof c in l:
board(c) == "."
end;
end;
OpenLines(p) = those l in alllines: WinFor(p,l) end;
WinFor(p,l) = not exist c in l: board(c) == opponent(p) end and
1 = sumfor c in l: board(c) == "." all 1 end;
```

The definition of *move* is an *if* that checks for the preconditions of each heuristic and returns a corresponding move if the precondition is satisfied.

```
move = if
player == X then
InputXMove
HasWinX then outputln('win X') &&
WinX
HasWinO then outputln('win O') &&
WinO
CenterFree then outputln('center') &&
Center
ExistThreatenedCorner then outputln('thrd corner') &&
ThreatenedCorner
FreeSide then outputln('side') &&
Side
ExistOpenCorner then outputln('open corner') &&
OpenCorner
else outputln('random') &&
RandomMove
fi where
```

(The *Outputln* calls report which heuristic is used for each computer move.) These variables, like *ThreatenedCorners*, all have similar definitions. For example,

```
ExistThreatenedCorner, ThreatenedCorner = ThreatenedCorners;
ThreatenedCorners = (% htc, tc %)
where
htc = tcorners != [];
tcorners = those cr in corners:
board(el1(cr)) = "." and
board(el2(cr)) = X and
board(el3(cr)) = X
end;
tc = el1(tcorners);
end;
Corners = [[1 2 4][3 2 6][7 4 8][9 8 6]];
```

In each the first value is a Boolean which, if true, confirms that the second is available. The (% %) tuples are lazy so that evaluating the first component does not necessarily trigger evaluation of the second, which could produce errors.

There remains the code to display the board. *DisplayBoard* produces the characters (including newlines) that produce an image of the current board.

```
DisplayBoard=
concatfor r in rows all
DisplayRow(r)^'\n'
end^'\n'
where
DisplayRow(r) =
concatfor c in r
all Icon(board(c))
end ;
Icon(m) = if
m== X then 'X '
m== O then 'O '
else '. '
fi;
end;
```

The output is produced as a side effect of the loop *while* condition:

```
outputs(DisplayBoard) &&
not(HasWonX or HasWonO or IsTie) and ix<10
```

The expression *outputs(DisplayBoard)* has the side effect of sending the string *DisplayBoard* to the terminal (yes, a shameless side effect – but no monads). The *&&* operator evaluates its first argument, ignores the result, and returns the result of its second.

The result of the *while* loop announces the outcome of the game:

```
result =
if
HasWon(X) then 'Congratulations, you win!'
HasWon(O) then 'Yay, I win!'
else 'A tie!'
fi;
```

I’ll be releasing PyFL as a zip file once I clean it up a bit. I’ll include the tictactoe program so you can try your luck against it. I claim it’s unbeatable but it could be that the heuristics still need tweaking, Good luck!

**APPENDIX – THE COMPLETE PROGRAM**

```
res =
while outputs(DisplayBoard) &&
not(HasWon(X) or HasWon(O) or IsTie) and
ix != 10
board = startboard fby MakeMove(player,move);
O = "O";
X = "X";
ix = 1 fby ix+1;
player = "X" fby opponent(player);
opponent(p) =
if
p == X then O
p == O then X
else "."
fi;
move = if
player== X then InputXMove
HasWinX then outputln('win X') && WinX
HasWinO then outputln('win O') && WinO
CenterFree then outputln('center') && Center
ExistThreatenedCorner then outputln('thrd corner') && ThreatenedCorner
FreeSide then outputln('side') && Side
ExistOpenCorner then outputln('open corner') && OpenCorner
else outputln('random') && RandomMove
fi where
InputXMove = input(str(ix)+' - choose an empty cell ');
HasWinX, WinX = WinningMove(X);
HasWinO, WinO = WinningMove(O);
ExistOpposingCorner, OpposingCorner =
OpposingCorners;
ExistThreatenedCorner, ThreatenedCorner =
ThreatenedCorners;
ExistOpenCorner, OpenCorner = OpenCorners;
FreeSide, Side = FreeSides;
CenterFree = board(5) == ".";
Center = 5;
end;
MakeMove(p0,c0) = if
board(c0) != "."
then error('overplay '+str(p0)+str(c0))
else b
fi
where
b(c) =
if
c0 == c then p0
else board(c)
fi;
end;
HasWon(p) = // p has already won
exist l in lines:
forall c in l:
board(c) == p
end
end;
IsTie = // board is tied - game over no winner
forall c in cells : board(c) != "." end;
OpenLines(p) = // lines where p has a winning move
those l in lines:
WinFor(p,l)
end
;
WinFor(p,l) = //p can win playing in l
not exist c in l: board(c) == opponent(p) end;
and 1 ==
sumfor c in l: board(c) == "." all 1
end;
WinningMove(p) = // a winning move for p
(% haswin, winningmove %)
where
ls = OpenLines(p);
haswin = ls != [];
l = hd(ls);
winningmove =
firstof c in l:board(c) == "." end;
end;
FreeSides =
(% fs, s %)
where
fss =
those c in Sides:
board(c) == "."
end;
fs = fss != [] and not(board(5) == "X");
s = randomelement(fss);
end;
ThreatenedCorners =
(% etc, tc %)
where
tcs =
those r in Corners:
board(el1(r)) == "." and
board(el2(r)) == X and board(el3(r)) == X
end;
etc = tcs != [];
tc = el1(el1(tcs));
end;
OpposingCorners
= (%eoc,oc %)
where
dcs =
those l in DiagonalCorners:
board(el1(l)) == "." and
board(el2(l)) == X
end;
eoc = dcs != [];
oc = el1(el1(dcs));
end;
OpenCorners =
(% opcok, opc %)
where
opcs = those r in Corners:
board(el1(r)) == "."
end;
opcok = opcs != [];
ropc = randomelement(opcs);
opc = el1(ropc);
end;
RandomMove =
m where
poss = those c in cells: board(c) == "." end;
m = randomelement(poss);
end;
DisplayBoard=
concatfor r in rows all
DisplayRow(r)^'\n'
end^'\n'
where
DisplayRow(r) =
concatfor c in r
all Icon(board(c))
end ;
Icon(m) = if
m== X then 'X '
m== O then 'O '
else '. '
fi;
end;
result =
if
HasWon(O) then 'Yay, I Win!'
HasWon(X) then 'Congrats, you win!'
IsTie then 'Tie!'
else error('inconclusive outcome')
fi;
end;
cells = [1 2 3 4 5 6 7 8 9];
startboard(c) = ".";
lines = rows<>columns<>diagonals;
rows = [[1 2 3] [4 5 6] [7 8 9]];
columns = [[1 4 7] [2 5 8] [3 6 9]];
diagonals = [[1 5 9][3 5 7]];
Corners = [[1 2 4][3 6 2][7 4 8][9 8 6]];
DiagonalCorners = [[1 9][9 1][7 3][3 7]];
el1(l) = hd(l);
el2(l) = el1(tl(l));
el3(l) = el2(tl(l));
Sides = [2 4 6 8];
```

]]>I fixed the flaw … and in so doing discovered a promising new equational programming paradigm.

The new paradigm allows a purely equational approach to something like OO and in particular allows an OO extension of Lucid that is simple and natural. But parametric programming (PP) seems to go beyond OO. It can be used in conjunction with other declarative languages like Haskell.

**Valof clauses**

First we need to avoid *where* clauses, which complicate the issues. When I was at Warwick in the old days there was a band of enthusiastic devotees of the pioneering system language BCPL. BCPL was a descendant of the monster (and unimplemented) language CPL, and an ancestor (via B) of C itself.

Anyway BCPL had many nice features including the VALOF construct. A VALOF was like a begin-end block except that you use a command RESULTIS to return a value. For example

VALOF $(
DISC := B*B - 4*A*C
IF DISC < 0 RESULTIS ERR
RESULTIS (-B + SQRT(DISC))/(2*A)
$)

BCPL’s VALOF is easily adapted to an equational approach. We simply replace the commands with an (unordered) set of equations including exactly one for the special variable *result*:

valof
disc = b*b - 4*a*c;
root = (-b + sqrt(disc))/(2*a);
result = if disc<0 then ERR else root fi;
end

(Note that the Lucid version, in which variables are evaluated on demand, will not attempt to take a negative square root. The equations are not assignment statements.)

**Output parameters**

At one point an interesting thought occurred to me, why not have output parameters? That return results? For example, this would allow an equivalent of *if-then-else-fi *without the nested structure:

valof
test = n<1;
choicet = 1;
choicef = n*(n-1);
result = choice;
end

The variable* result* is special in that it cannot be renamed – unlike *disc* and *root*. I call it a “parameter”, like the parameters *columns, rows *and* numformat* pyLucid uses to format output.

Here it’s understood that

`choice = if test then choicet else choicef fi`

but you don’t write this equation, it’s automatically included.

So that was my idea. I showed that with parameters you can formalize other “constructs” like multiple exit loops.

And that was it. Not an earth-shaking idea. And one with a few problems. For example, it ties up identifiers like “choice” or “test” that the programmer might want to use. So I dropped it, for many decades.

**A simple modification**

The other day I decided to write a blog post about parameters and as I reflected on the problem of tied-up identifiers a simple solution occurred to me: preface the parameter names with the names of the construct in question. Thus for example

valof
if.test = n < 1;
if.true = 1;
if.false = n*(n-1);
result = if.result
end

Well will you look at that. Familiar? Yes, it looks like the invocation of an OO object called “if”. The equations defining the ‘input’ parameters like “*if.test*” are setting properties (in this case *test*) and the expression *if.result* is calling a method *result*.

And yet there’s nothing going on other than invoking an implicit equation, namely

if.result = if if.test then if.true else if.false fi;

No storage allocation and no parameter initialization, none of the overhead associated with creating an object in traditional imperative OO. At most copying a string at compile time, namely the implicit equation.

**Schemes**

Clearly the *if* object is built-in. What about other objects we might want? Suppose we wanted to solve a quadratic equation. We can imagine invoking an object quad

```
valof
import quad;
quad.a = 3;
quad.b = 9;
quad.c = -8;
result = [%quad.root1,quad.root2%];
end
```

to calculate the two roots of the quadratic/;

3x^{2} + 9x - 8

Notice the line

`import quad;`

I put that there because I imagined *quad* would not be built-in and we’d have to invoke something to bring in the required implicit equations. And what would these equations be? Something like

```
disc = b*b - 4*a*c;
droot = sqrt(disc);
root1 = (-b + droot)/2/a
root2 = (-b - droot)/2/a
```

The obvious solution is

class quad
droot = sqrt(disc);
root1 = (-b + droot)/2/a
root2 = (-b - droot)/2/a
end

except there’s not enough information here. We need to specify that *a, b, *and* c *are input parameters (basically properties) and that *root1* and *root2* (but not *disc* and *droot*) are output parameters (basically methods). Also, as we will find, these packages can’t always be thought of as OO objects. So I’ve decided to call these sets of equations “schemes” as in

scheme quad
disc = $b*$b - 4*$a*$c;
droot = sqrt(disc);
$root1 = (-$b + droot)/2/$a
$root2 = (-$b - droot)/2/$a
end

The fact that *$root1 *and *$root2* have definitions means *root1* and *root2* are output parameters.

**Function Parameters**

So far so good. Then something hit me: both input and output parameters could be functions:

```
scheme integrate
m = ($a+$b)/2
$area = ($b-$a)($f($a)+4$f(m)+$f($b)
```

and could be used as follows:

```
valof
import integrate
integrate.a = 1;
integrate.b = 2;
integrate.f(x) = 1/x;
log2 = integrate.area;
end
```

This was a big surprise! pyLucid (for example) is strictly first order, you can’t define a function that takes another function as an argument. We figured out how to do it in principal but there are syntactical and implementation complications.

And now we have it almost for free, all done with little more than compile time string copying. We can also have functions returned as output parameters, as in this scheme which computes a derivative numerically:

```
scheme deriv
$df(x) = ($f(x+eps)-$f(x))/eps;
end
```

```
valof
import deriv;
deriv.f(x) = x*x;
result = deriv.df(4);
end
```

**Enter PYFL**

At this point it occurred to me that this idea has nothing inherently to do with Lucid, it works with any equational language. Furthermore as an equational language Lucid has irrelevant baggage such as being first order and requiring dimensional analysis to make eduction work.

So I decided to specify and implement a super simple equational language as a platform for demonstrating parameters. Thus was born PYFL, the PYthon based Functional Language.

What followed was ironic on two grounds. First, PYFL was not super simple for long. I discovered that it was relatively easy to extend PYFL with interesting features like VBOs or (paradoxical) functional while loops. At the same time, I got distracted from parameters and didn’t implement them till quite late.

And when I did implement them, I discovered that they aren’t quite as simple as they seem. You can’t just have the (say) equation for *if.result* at the topmost level. It has to be included in every *valof* clause that imports the *if* scheme.

User defined schemes are complex and haven’t been done yet.

So parameters aren’t quite vaporware but they haven’t really been tried out. The vapor hasn’t condensed yet.

]]>

A *while* loop? In a functional language? “Impossible!” you snort.

Well you’re wrong. Let me explain.

Anyone who has tried programming the Fibonacci function has got a big surprise. In PYFL, for example, you can naively write

`fib(n) = if n<2 then 1 else fib(n-1)+fib(n-2) fi;`

On my Intel Macbook Air evaluating fib(20) eventually produces 10946 but fib(30) hangs up. A little thought explains why: each call to fib generates two calls to fib … an exponential explosion, because the calls are independent, don’t share results unless you augment the simple program with some memoization scheme.

However in PYFL you have an alternative, namely a while loop:

```
fib(n) =
while i < n
i = 1 fby i+1;
f = 1 fby f+pf;
pf = 1 fby f;
result = f;
end;
```

Now when I evaluate fib(20) I get 10946 *immediately* and fib(30) produces 1346269, also immediately. Given fib(1000), after a barely detectable pause, I get

70330367711422815821835254877183549770181269836358732742604905087154537118196933579742249494562611733487750449241765991088186363265450223647106012053374121273867339111198139373125598767690091902245245323403501

The secret, of course, is that the while loop uses an iterative algorithm that carries forward both the current value (f) of fib(i) as well as the previous value (pf) of fib(i).

Isn’t this cheating? PYFL is supposed to be functional and use recursion not iteration … We’ll see.

Another example, summing up 22 terms of the series for e**x:

```
e(x) =
while i<22
i = 1 fby i+1;
term = 1 fby term*x/i;
sum = 0 fby term+sum;
result = sum;
end;
```

evaluating e(1) yields 2.7182818284590455, exact. Of course you can do this recursively but avoiding the calculation of x**i and i! at each stage requires auxiliary functions with extra arguments.

As for the charges of cheating – not guilty. PYFL while loops use plain old respectable tail recursion.

Suppose we want a function that removes duplicates in its list argument while preserving order. We soon realize we need an auxiliary function with an extra parameter that accumulates one copy of everything we’ve seen so far:

```
nodups(l) = g([],l);
g(m,k) = if k == [] then m else if hd(k) isin m
then g(m,tl(k))
else g(m<>[%hd(k)%],tl(k))
fi
fi;
```

Now consider how the recursion unfolds when l is [1 3 5 3]. We have

```
nodups([1 3 5 3]) => g([],[1 3 5 3]) => g([1],[3 5 3]) => g([1 3],[5 3]) =>
g([1 3 5],[3]) => g([1 3 5],[]) => [1 3 5]
```

And now notice that the variables m and k take on *sequences* of values; for example, m takes on the sequence

`[], [1], [1 3], [1 3 5], [1 3 5]`

And what are the *rules* that generate the sequences? For m the rules are that m is initially [], and thereafter is m again if the head of k is in m, otherwise m<>[%hd(k)%] (the result of appending the head of l on the end of k).

The rules for k are simpler: k is initially l, and thereafter tl(k), until k is empty.

This tail recursion corresponds closely to the (conventional imperative) while loop:

```
m := []
k := l
while k != []
m := if hd(k) isin m then m else m<>[%hd(k)%] fi
k := tl(k)
end
return m
```

Thus tail recursion and recurrence rules are basically different specifications of the same computation. The idea of PYFL’s paradoxical *while* loops is to let the programmer specify the recurrence rules for the iterative version of the computation then have the PYFL interpreter translate them into (functionally respectable) tail recursions.

Here is *nodups* as a PYFL while clause

```
nodups(l) =
while k != []
k = l fby tl(l);
m = [] fby if hd(k) isin m then m else m<>[%hd(k)%] fi;
result = m;
end;
```

The PYFL *while* is similar to the imperative one given above except that the statements are recurrence rules, not commands. They are unordered. The word *fby* is borrowed from Lucid but is just a syntax word, not an operator. It all gets translated into something very close to the tail recursion given above.

Ordinary tail recursions have a particularly simple formulation as while loops. Here is the definition of a function which sums the squares of the elements its list argument

```
sumsq(l) =
while m != []
m = l fby tl(m);
sum = 0 fby sum + hd(m)**2;
result = sum;
end;
```

The function *sumsq* can be defined using map-reduce:

```
sumsq(l) = reduce(map(lambda (h) h**2 end,l),add,0);
reduce(m,A,b) = if m == [] then b else A(hd(l),reduce(tl(m),A,b) fi;
```

which is nowhere near as clear.

Slightly more generally, the while loop

```
while p
a = fa fby na;
b = fb fby nb;
c = fc fby nc;
result = r;
end
```

gets translated to

```
valof
result = f(fa,fb,fc);
f(a,b,c) = if p then r else f(na,nb,nc) fi;
end
```

The rules in the body don’t have to be recurrences; you can define a variable directly as a given expression. For example we could rewrite our PYFL *while* as

```
while k != []
k = l fby tl(k);
h = hd(k);
m = [] fby if h isin m then m else m<>[%h%] fi;
result = m;
end
```

We need a slightly more sophisticated translation which I won’t go into.

Input and output calls may appear in a while loop, with the expected results.

Here is a simple loop for factoring numbers, which prints the partial results as the factoring proceeds

```
factors(n) =
while p*p <=
outputf('{:>20s}{:10d}{:>6} \n',str(f),m,p)
&& m
p = 2 fby if divides then p else p+1 fi;
m = n fby if divides then m div p else m fi;
f = [] fby if divides then p :: f else f fi;
divides = m mod p == 0;
result = outputf('{:>20s}{:10d} \n',str(m :: f),1) && "";
end
;
n = input('Number to be factored: ');
r = factors(n);
```

It works by enumerating possible divisors starting at 2, accumulating divisors found in the list f. If, for example, when it asks for an input number, you give it 1968, you get

```
[] 1968 2
[2] 984 2
[2 2] 492 2
[2 2 2] 246 2
[2 2 2 2] 123 2
[2 2 2 2] 123 3
[3 2 2 2 2] 41 3
[3 2 2 2 2] 41 4
[3 2 2 2 2] 41 5
[3 2 2 2 2] 41 6
[3 2 2 2 2] 41 7
[41 3 2 2 2 2] 1
```

There is another while-like construct, called *until*. It runs until a condition is true, in itself a trivial difference. But until has *two* conditions and *two* result expressions, the result returned depending on which condition is true first.

Here is an *until* clause used to calculate cube (for a change) roots using Newton’s method, exiting if it doesn’t converge in 8 steps.

```
root(N) =
until A == pA, steps>8
steps = 1 fby steps+1;
A = 1 fby (2*A+N/A**2)/3;
pA = 0 fby A;
result = A, "noroot";
end;
```

For example root(27) evaluates to 3.0 but root(100) produces *noroot*.

So why am I telling you all this? In the hope that you will switch from Haskell to PYFL?

Sorry, but I’m not delusional. PYFL is an experimental toy language but is far from practical for serious projects. I’ll make it available and some of you may find it interesting to write little programs with while loops, VBOs, interactive IO etc. Or experiment with your own ideas.

The real point is that serious functional languages, like Haskell, could benefit from these features – they are trivial to implement. The whole implementation of PYFL is less than 5000 lines of Python. For example, Haskell could have while loops using the source translation given above. The entire translation process, including parsing, required less than 100 lines of code.

Haskell is great but it shouldn’t be the last word in functional programming. There is no last word in functional programming, and the PYFL experiment makes that clear.

]]>For example, PyFL has a full set of Variable Binding Operators (VBOs) that use a linear ASCII form to represent the constructs like sigma notation of conventional mathematics. For example the following adds up the first 20 squares

`sumfor i in range(1,20) all i*i end`

and the following tests if N is prime

`forall d in range(2,sqrt(N)): N mod d != 0 end`

In the previous post I described the PyFL approach to input, which is (arguably) purely functional, drastically simpler, and trivial to implement, as compared to Haskell’s cumbersome IO monad. (Btw I have nothing against monads or Haskell in general, I’m just not impressed by the Haskell IO monad).

This time I’ll talk about PyFL’s output scheme. It ticks two of the three boxes just mentioned – it’s simple and easily implemented. Here’s some output (yep, Pascal’s triangle). I’ll show the program at the end of this post.

Unfortunately I can’t claim that PyFL output is functional. It shamelessly causes a side effect. I just don’t know how to do better.

A simple interactive program will illustrate the point. In BC in Canada at one stage you had to be a certain (varying) age to book an appointment for a Covid shot. This PyFL program asks your name and age and tells you whether you’re eligible.

```
query = if age>40
then outputs(name^', you are eligible!')
else outputs(name^', sorry, you are not eligible.')
fi;
name = input('Your name, please: ');
age = input(name^', your age please: ')
```

When *query* is evaluated the program will prompt for your name, then, addressing you by name, prompt you for your age, then tell you whether or not you’re eligible. (The carat symbol stands in PyFL for string concatenation.)

The ‘function’ *outputs* when evaluated prints its argument, which is normally a string. This is a blatant side effect. We can debate whether *input* invokes a side effect, too, but the important point is that they are easy to use and implement. And since invocations take the form of function calls they can be embedded in PyFL constructs like if-then-else-fi and VBOs.

The arguments to *input* and *outputs* can be any expressions. They don’t have to be string constants. In particular they can involve previous input values, as above.

There is also a more primitive output ‘function’ namely *output*, but *outputs* is usually preferred because it strips the the string quotes off its string argument, as does *input*.

The *outputs* pseudo function can be called embedded in PyFL constructs like VBOs, lambda expressions, if-then-else-fi’s and so on. Here is a program that prints a table of the first 10 squares and cubes:

```
res = concatfor i in range(1,10) all
outputs(i) && outputs(' ') &&
outputs(i*i) && outputs(' ') &&
outputs(i*i*i) && outputs('\n') &&
''
end;
```

(The operator && evaluates the first argument for its side effects, and discards the result.)

The result isn’t that great. We don’t have columns lined up. Fortunately we can do better. One of the advantage of using Python as the implementation language is that we have access to all its features in one way or another. In particular Python has a rather sophisticated output formatting system based on format strings with ‘holes’ for data. In PyFL you use another output pseudo function *outputf*. The first argument is a format string with holes, and the remaining arguments are data items that will be slotted into the holes. Format strings are well documented and what works for us is the string ‘{:5d} {:5d} {:5d}\n’.

```
res = concatfor i in range(1,10)) all
outputf('{:5d} {:5d} {:5d}\n',i,i*i,i*i*i) && ''
end;
```

The result is much better. Now the numbers are lined up in columns five characters wide.

The Python formatting strings can naturally handle floating point numbers as well. Suppose we want to print a table of square and cube roots of the first ten whole numbers but only want to display four significant digits. Here is the complete program

```
res = concatfor i in range(1,10) all
outputf('{:4d} {:8.4f} {:8.4f}\n',
i,sqrt(i),i**(1/3)) && ''
end;
```

and here is its output (I’ll deal with the empty string at the bottom later). There’s no need here to go into the details of Python format strings because they are well documented elsewhere.

Printing numbers in columns is one thing but producing a triangle is a bit harder – there’s nothing built-in to the Python formatting strings that would cover that. Instead we have to use the computational power of PyFL to generate appropriate side effects.

Here is the complete program

```
scc(L) =
while tl(M) != []
M = L <> [0] fby tl(M);
k = hd(M);
pk = 0 fby k;
n = k+pk;
N = [] fby n :: N;
result = n :: N;
end
;
tri =
while i<18
L = [1] fby scc(L);
i = 1 fby outputs(sp(35-( width(L) div 2))) &&
outputs(strlist(L)) &&
outputs('\n') &&
i+1;
result = L && '';
end
;
sp(j) = concatfor i in range(1,j) all ' ' end; // j spaces
width(L) = sumfor h in L all w(h)+1 end -1;
strlist(L) = concatfor h in L all str(h)^' ' end;
w(h) = 1 + (log10(h) div 1);
```

The first definition, of the function *scc*, defines a computation that generates the line of the triangle that succeeds a given line, both represented as lists. Thus *scc([1])* is *[1 1], scc([1 1]*) is *[1 2 1],* *scc([1 2 1])* is *[1 3 3 1]*, and so on. Yes, that’s a while loop – I’ll explain in the next post, but let me assure you in the mean time that’s all legit, no side effects.

The next definition, of *tri*, is another while loop. It repeatedly applies *scc* starting with *[1]* and generates 18 lines of the triangle. It converts these lists into strings and calculates their width. It reduces the left hand spacing as the lines grow wider.

I’m sure that by now you believe that as far as the terminal goes, PyFL can do anything reasonable (output strings can contain control characters and escape sequences).

However I’ve acquired a didactic debt, namely *while* (and other!) loops. I have some ‘splaining to do. Next post.

The basic language itself is pretty standard, except that it uses infix operators and conventional mathematical notation for function calling; e.g. f(h,a+2*y). It also has a wide range of variable binding operators; as in the program given at the end of this post

VBOs are great but the real elephant in the FP room is I/O. I have a few ideas.

**Monads schmonads**

Haskell uses the seriously complex machinery of monads to do I/O, supposedly without side effects (I don’t accept this). And you end up writing stuff like

`main = do {` ` ` `putStrLn "Hello, World!" ;` ` ` `return ()` ` ` `}` |

which to me looks like C. There must be a more functional approach.

I started from the PyLucid approach, that the output of a program is its value (as an expression) and its input is the value of a special variable *input*. Simple enough.

The problem with adopting this is that pyLucid has streams and PYFL doesn’t. Infinite lists could serve as a substitute but PyFL has only finite lists (to avoid the complications of list closures)

But there is no harm in having an *input* variable and this gives us a simple arrangement: the input becomes the value of *input*, and the corresponding value of the program is the corresponding output. Thus the expression

*input*input*

is a program to compute the square of its input (note that both occurrences of *input* denote the same value – no side effects, this program is equivalent to *input**2*).

What if we do want two inputs, and output their product? If one input variable is kosher, we can have two, *input* and *input2*. Our program is *input*input2.*

Generalizing, we could have *inputn* for every number n. Even better, a function *input(n)* of one number argument. When the computation (which is demand driven) needs the value of, say, *input(3)*, it could prompt for it. In this simplest form the scheme is impractical because in general there is no guarantee that these inputs will be demanded in numerical order; the computation may need *input(4)* before i*nput(3)*.

The solution (following Lucid) is to demand the inputs in order and cache them, so they will be available when the computation needs them.

**Input prompting**

PyFL goes one better. It has an input function whose argument can be an arbitrary string (not necessarily a numeral). This string is used as a prompt. Thus a program to compute the roots of a quadratic equation could be (and in PyFL is)

*valof** a = input(‘coefficient of x squared’);** b = input(‘coefficient of x’);** c = input(‘constant coefficient’)** rdisc = sqrt(b*b-4*a*c);** root1 = (-b + rdisc)/(2*a);** root2 = (-b – rdisc)/(2*a);** result = [% root1, root2 %];**end*

As it happens the computation will need the value of b before that of a. If this is a problem, we can write the definition of root1 as

*root1 = (1/(2*a))*(-b + rdisc);*

(later I’ll explain a more systematic approach). Then the demand for *root* generates a demand for *root1* which generates a demand for *1/(2*a)* and finally a demand for a. Demands for b and c follow shortly, in that order.

Input values are cached in a dictionary using the prompt itself as a key. Thus the implementation won’t ask you again for the coefficient of x squared.

This works well if there is a small finite set of inputs but what if, say, we wanted to sum the values of twenty inputs?

The answer is to generate prompts and embed the a call to the input function in a VBO that runs over the required range. For example

*sumfor i in range(1,20) all ** input(‘a number please(‘+str(i)+’)’)**end*

where *str* converts numbers to strings.

What if the input is being produced by another program? Why would we print the prompts? Because the program can read them digitally, and produce output as required. In other words the producer program is feeding our consumer program by producing its output on demand. It receives a prompt and computes or looks up the corresponding output.

We can save a lot of trouble if the producer knows ahead of time the order in which the input items will be required. Then we can eliminate the prompting dialogue.

The input scheme is not only conceptually simple (none of those pesky monads) but is trivial to implement – about a dozen lines of Python.

Now for output. The idea is … wait, take a look at the word count … this is already too long, the output story will have to wait till next time. Prompt me.

Here’s the promised program

*valof** subsets(s,i) = // the set of all i-element subsets of set s** if i==0 then {{}}** i==1 then collectfor x in s all {% x %} end** else** unionfor x in s all** valof** sminusx = s – {% x %}; // {% x %} is singleton x** withoutx = subsets(sminusx,i); // subsets without x** withx = collectfor u in subsets(sminusx,i-1) all //those with x** {% x %} ++ u** end;** result = withx ++ withoutx; // disjoint union** end** end** fi;** result = subsets({tom dick harry chris pat},3);** end*

which computes all the three element subsets of the set {tom,dick,harry,chris,pat} (PyFL has proper sets).

]]>A blessing because it has allowed many thousands to experience FP firsthand – by writing functional programs. They’ve discovered that it’s not that hard, that you can do useful projects in FP, and (thanks to the cleverness stuffed into Glasgow Haskell) the performance is comparable if not superior to that of conventional languages like Java,

So what is the curse? The curse is that Haskell dominates FP like Tyrannosaurus Rex dominated the dinosaurs (and like LISP used to dominate FP). Any discussion of FP becomes a discussion of Haskell, any proposed feature becomes a proposal for extending Haskell, and any problems with Haskell seem inherent in FP. The FP ecosystem seriously lacks diversity

I’m going to fix that with PyFL (PYthon based Functional Language), designed and implemented from scratch, as you watch

Take currying. In school we learned that addition, denoted by “+”, was a friendly little operator that takes two numeric values and returns their sum. Studying Haskell, we learn that it is actually a complex object that takes a number and returns a function that takes another number and returns the result of adding the first number to it.

Higher order functions that return other functions? Pretty complex stuff just to understand why 2+2 = 4.

Then there’s the monads. I’m more in favor of monads since I discovered that Lucid can be explained in terms of a simple stream monad. But I’m baffled by the IO monad. What causes the side effects? And are we really required to write imperative-looking code to read and write? Here is “Hello World” in Haskell according to online sources:

```
main :: IO ()
main = putStrLn "Hello, World!"
```

Yes, “putStrLn’, which, is clearly a command. The same source also offers

```
main = do {
putStrLn "Hello, World!" ;
return ()
}
```

which to me looks like C. Apparently the “do” is shorthand for invoking monads … who knows.

**Enough is enough**

I don’t believe functional programming should look like C and I’m going to put my money where my mouth is. I’m going to increase the diversity of the FP ecosystem by inventing a new FP language – starting now. As the title says, I aim to put the fun into – and take the C out of – functional programming. Perhaps I can overcome some of Haskell’s limitations and draw backs.

The new language is called PyFL : Python based Functional Language. It’s based on Python in the sense that Python is the implementation language.

Succeeding posts will take you through the development. You can think of it as live blogging except that I’ve already done much of the work.

I’m cutting only two corners. First, PyFL is dynamically typed. A static type system is too much work. And they can be constraining and cumbersome, so I think I can do without one for the time being.

The other corner is performance. PyFL is relatively slow given two levels interpretation. It would take a whole team to match the amazing work done on the GHC. But modern computers are powerful and PYFL performance is good enough that the language is usable on small projects.

**The basic principles**

The first principle is that PyFL is math. Out is any feature (like putStrLn) that can’t be explained *as* math. (And not just *through* math because any precisely defined notion can be described using math).

The second principle is that as far as possible PyFL uses conventional mathematical notation. This means prefix and infix operators, parentheses, and operator precedence. But most importantly, conventional mathematical notation adapted to linear format using the ASCII alphabet.

Thus a function to compute the root of the sum of the squares of its arguments can be defined by the equation

sumsq(x,y) = sqrt(x**2+y**2)

The function *sumsq* is a simple, humble first order function of two arguments (it has *arity* 2), not a disguised second order unary function. It is invoked like in your calculus text, e.g.

sumsq(3,4)

Of course we can define higher order functions as required, using function formal parameters or (linear ascii) lambda notation. Thus

simpson(f,a,b) = (b-a)*(f(a)+4*f((a+b)/2)+f(b))/6

Here *simpson(f,a,b)* is the simple Simpson’s rule approximation to the integral of function f from a to b. Thus

simpson(lambda (x) 1/x end,1,2)

gives an approximation to log 2.

**Valof clauses**

The only remaing feature of basic PyFL is a construct for auxiliary definitions. Haskell adopted Landin’s *where* clause, so that the definition of *simpson* could be written

simpson(f,a,b) = l*avgf

where l = b-a; m = (a+b)/2; avgf = (f(a)+4*f(m)+f(b)/6; end;

However *where* clauses cause problems with top-down parsing so instead we adapt BCPL’s *valof* construct, and write

*simpson(f,a,b) =** valof ** result = l*avgf; ** l = b-a; ** m = (a+b)/2; ** avgf = (f(a)+4*f(m)+f(b)/6; ** end*

The *valof* clause is equivalent to the *where* clause except for the definition of the special variable *result*, which is equated to the expression at the head of the corresponding *where* clause.

The equations in the body of a *valof* are unordered. There must be a definition of *result*, and every variable defined must have a unique definition. Definitions inside the body override definitions of the same variable outside the body. This is all standard. Thus

valof factorial(n) = if n<1 then 1 else n*factorial(n-1) fi; result = factorial(5); end

has value 120.

**VBOs**

PYFL fully implements Variable Binding Operators (VBOs), as described in a previous post. For example you can compute the set of all subsets of s as

*unionfor x in s all** valof** r = s – {% x %};** sr = subsets(r);** wix = collectfor u in sr all // subsets with x** {% x %} ++ u** end ;** wox = sr; // subsets without** result = {%s%} + wix ++ wox + {{}};** end**end*

A current list of VBOs is: sumfor, prodfor, consfor, appendfor, concatfor, unionfor, intersectfor, collectfor, those, exist, forall.

Notice PyFL has sets, implemented properly, with higher order primitives instead of a random member operator.

**The fixed point operator**

One intriguing property of basic PYFL (which is really just applied lambda calculus) is that in principal recursion is not necessary. The following *valof* also has value 120

*valof** alpha(f) = lambda (n) if n<1 then 1 else n*f(n-1) fi end;** Y(gamma) = ** valof ** result = h(h); ** h(k) = gamma(k(k)); ** end;** factorial = Y(alpha);** result = factorial(5)**end*

Examine the code carefully; you can verify that no variable is defined directly or indirectly in terms of itself.

Notice the simple (untypable) definition of the fixed point operator Y; there are advantages to skipping static type checking.

To see how Y works, look at the definition of h:

*h(k) = gamma(k(k))*

Now substitute h for k, giving

*h(h) = gamma(h(h))*

In other words, h(h) is a fixed point of *gamma*!

The PyFL interpreter handles this correctly; the *valof* above evaluates to 120. In fact all these examples have been tested by my PyFL interpreter.

There’s a lot more to PyFL but that’s for next time.

]]>In a previous post I showed how to find identify variables in a classic (time only) Lucid program that are constant – do not change with time. This information proves vital for output and for efficient caching.

Now we consider two dimensional Lucid, like pyLucid, in which variables can vary in two independent dimensions, time t and space s.

The situation is more complex because a variable can be constant, sensitive to time only, sensitive to space only, or sensitive to both. If we define the dimensionality of a variable as the set of all dimensions to which it is sensitive, the four possibilities are {}, {t}, {s}, and {s,t}. Our dimensionality analysis assigns one of these to every program variable, including the output. Note that these assignments are *upper bounds*. For example, we may assign {s,t} to X even though, when we actually run the program, it turns out that the space parameter doesn’t affect the results. Upper bounds are good enough to avoid caching duplicate values.

Here is a two dimensional program that produces the stream of prime numbers:

N2 = 2 sby N2+1;

S = N2 fby (S wherever (S mod init S != 0));

output = init S;

The first step, as before, is to convert it into a set of atomic equations:

N2 = 2 sby T00;

T00 = N2+1;

S = N2 fby T01;

T01 = S wherever T02;

T02 = S mod T03;

T03 = init S;

T04 = T02 != 0;

output = T03;

Next we set everything to dimensionality {} (constant). On the second iteration, we set N2 to {s} and S to {t}.

On the third iteration T00 gets {s}, S gets {t,s}, T01 gets {t}, T02 gets {t}, T03 gets {t}, T04 gets {t}, output gets {t}.

Finally on the fourth iteration N2 gets {s}, T00 gets {s}, S gets {t,s}, T01 gets {t,s}, T02 gets {t,s}, T03 gets {t}, T04 gets {t,s}, and output gets {t}.

The next stage gives the same result so the iteration is complete. We conclude in particular that N2 has dimensionality {s} (a pure vector), S has dimensionality {t,s} (a time varying array), and output has dimensionality {t} (a pure stream). When N2 is cached one space tag is enough, caching S requires a time tag and a space tag, and the output is a stream of scalars.

At each stage the new dimensionality of each variable is calculated from the previous dimensionalities according to rules for the operators. These rules are similar to the rules for time sensitivity but more elaborate. Here are some of them

if V = X sby Y then V is space sensitive, and also time sensitive if either X or Y are

if V = init X then V is space insensitive but time sensitive if X is

if V = X wherever P then X is space sensitive and time sensitive if either X or P is

if V = X + Y then V is space sensitive if either X or Y is and time sensitive if X or Y is

Each rule with operator o can be understood as applying a corresponding operator o* to the dimensionalities of the operands. For example {} sby* {t} is {t,s} and +* is union (of dimensionalities). These *-operators are all monotonic in the subset ordering of dimensionalities so that if d_{i} is the approximate dimensionality on the i^{th} iteration, d_{i} ⊆ d_{i+1}. This guarantees that the iterations eventually settle down.

**Multiple Dimensions**

What if we have more than two dimensions – say vertical v, horizontal h, forward and backward z? The algorithm should be evident by now. A dimensionality is a set (e.g.{t,h,v}) of dimensions, to which the intension in question is possibly sensitive.

Of course we don’t invent dozens of new operators like fby and sby. Instead we parameterize them with a dimension’s name attached with a period. Thus fby.h is fby in the vertical direction, and first.z is first in the forward/backward dimension. Ordinary fby becomes fby.t.

The algorithm proceeds as above, except that the values being combined are subsets of the set {t,s,v,h,z,…} of all dimensions. (We can assume, since any particular program is finite, that this set itself is finite.)

The general rules are obvious generalizations of those given above for {s,t}:

if V = X + Y then V is assigned the (set) union of the current values of X and Y

if V = next.d X then V is assigned the current value of X

if V = first.d X then V is assigned the current value of X, minus d if it is in this set

if V = A fby.d Y then X is assigned the union of the values of X and Y, plus d

if V = X whenever.d Y then X is assigned the union of the values of X and Y, plus d

**Future Work**

This analysis is by no means the last word for GLU, the advanced dialect of Lucid developed at SRI as a coordination language. GLU had many more powerful dimensional features. GLU had where clauses (blocks) with temporary declarations of local dimensions. It also allowed user-defined functions. These could be data functions like cube but could be nonpointwise, for example computing an running average. The definitions could be recursive and even dimensionally abstract, e.g. having a dimensional parameter that specifies the dimension in which the running average is performed.

At present we don’t know how to analyze such programs; research is continuing.

How did GLU do it? We don’t know because the GLU implementation is proprietary software and is currently locked up in a corporate vault. It is all but certain that it will never see the light of day. We do know, for example, from discussions with former GLU researchers, that they used crude approximations. In particular, for user defined functions they took the union of the approximate dimensionalities of actual parameters yielding a worst-case result.

]]>