[Nickle]"purely" functional nickle (was: Recursive datatypes)
Carl Worth
nickle@nickle.org
Thu, 25 Jul 2002 10:57:16 +0000
On Jul 24, Keith Packard wrote:
> The way around this type problem is to use a type which can be represented
> with more than one kind of value.
>
> To avoid pure polymorphism, you can use a union type instead:
>
> if (l.ref == listend) printf ("yes\n");
I suppose that was intended to be:
if (l.ref == (listref.end) <>) printf ("yes\n");
which is indeed a rather awkward syntax.
Other than the syntax, I do like the other characteristics of using a
union for this purpose.
I ended up just using poly. I made a global struct value named nil as
a placeholder for list termination. When the new poly zero is in
place, I can get rid of my nil.
Using poly is actually the right thing for what I'm doing, (which
involves implementing a purely polymorphic language in nickle). I've
been trying is a little experiment in "pure functional" programming in
nickle. I've been going through a 1984 paper by John Hughes, "Why
Functional Programming Matters"[1], implementing each example.
With a small module defining cons, nil, and functional versions of all
the operators it was a simple matter to directly implement the first
set of examples:
/* fp.5c */
namespace FP {
public typedef struct {
poly car;
poly cdr;
} cons_cell;
public global cons_cell nil = { car = 0, cdr = 0 };
public cons_cell cons(car, cdr) {
return (cons_cell){car = car, cdr = cdr};
}
public poly add(a, b) {
return a + b;
}
public poly mult(a, b) {
return a * b;
}
/* etc. for all other nickle operators */
}
/* fptest.5c */
/* load "fp.5c"; *** Commented out for your cut-and-paste pleasure */
import FP;
poly reduce(poly(poly,poly) binop, poly basis, cons_cell list)
{
if (list == nil) {
return basis;
} else {
return binop(list.car, reduce(binop, basis, list.cdr));
}
}
poly sum(cons_cell list) {
return reduce(add, 0, list);
}
poly product(cons_cell list) {
return reduce(mult, 1, list);
}
cons_cell append(cons_cell a, cons_cell b) {
return reduce(cons, b, a);
}
l1 = cons(1, cons(2, nil));
l2 = cons(3, cons(4, nil));
list = append(l1, l2);
printf("sum is %d\n", sum(list));
printf("product is %d\n", product(list));
That's all very straightforward.
The next example revealed something interesting. He starts with a
single function to double each element in a list, then progressively
decomposes it in order to arrive at a definition for map. I was able
to implement the version with map like so:
poly(poly,poly) compose(poly(poly,poly) f, poly(poly) g) {
poly composite(car, cdr) {
return f(g(car), cdr);
}
return composite;
}
poly map(poly(poly) f, cons_cell list) {
return reduce(compose(cons, f), nil, list);
}
poly double(arg) {
return mult(2, arg);
}
cons_cell doubleall(list) {
return map(double, list);
}
list2 = doubleall(list);
This works, but the definition of compose isn't very satisfying. This
compose is restricted to accepting one function with two arguments and
a second with one argument. A more powerful compose would be able to
accept functions with any number of arguments and just do the Right
Thing. That is, if f accepts M arguments and g accepts N arguments,
then compose would return a function accepting N + (M-1) arguments
that would return f(g(arg1, ..., argN), argN+1, ..., argN+M-1).
I don't suppose there would be any way to write such a beast in
nickle?
Oh, and in a similar vein, is there any way to call one function with
a variable length argument list from another that also has a variable
length argument list. Something like:
int foo(int args ...)
{
/* ... */
return 0;
}
int bar(int args ...)
{
return foo(args); /* How to make something like this work? */
}
Would we want some new bit of syntax that turns an array into a list
of arguments for a function call?
By the way, thanks for nickle. I'm having a lot of fun poking and
prodding at it like this.
-Carl
[1] http://www.math.chalmers.se/~rjmh/Papers/whyfp.html
--
Carl Worth
USC Information Sciences Institute cworth@east.isi.edu
3811 N. Fairfax Dr. #200, Arlington VA 22203 703-812-3725