From:
phimvt@... [mailto:
phimvt@...]
>There was a long thread (72 items) on the subject "joy applications".
>(Some of it drifted a bit, of course.) I'll comment on some ideas.
Thanks. It's good to have you commenting in here.
>2: "Stack/concatenative/combinative languages"
> It would be terrific if some people could spell out principal
>differences between the languages that have been discussed here.
> Forth
Forth is one of the earliest concatenative languages, and certainly the
earliest one to become mainstream. Forth emphasises the process of
programming a virtual machine; you are supposed to know all of the details
of the machine and code for it. Another major emphasis is simplicity: not
only is the VM kept as simple as possible, but your own definitions are
supposed to be kept simple. A good programmer will write definitions
averaging 40 characters long. This makes unit testing suprisingly
effective.
Forth is also notable for not having any language-managed syntax aside from
number parsing. Everything, including comments, is implemented as a word.
The major modern dialects of Forth are:
- ANSI Forth, used in OpenBoot and OTA (both ISO standards) as well as most
applications; ANSI Forth is notable in that the standard doesn't even
mention the VM, leaving it up the implementor.
- Machine Forth, a vastly simplified VM together with some rethought
primitives: Machine Forth is much closer to the capabilities of modern
machines.
- Color Forth, a more recent descendant of Machine Forth which adds some
syntax to replace and simplify some of the old rules of Forth.
The rules of ANSI Forth permit a number of different types of threading.
Machine Forth requires native code.
> J (am I right that it is a successor to Iverson's APL?)
> K (successor of J?)
Correct. J is a rationalised APL: it's also pure ASCII. (Almost) every
symbol has a dual and triple which do related things (for example, "*" has a
dual "*." and a triple "*:"). Most symbols also have different meanings
when called with one argument or two.
K is greatly simplified from J; in addition, the focus is shifted from
arrays and matrices to ragged arrays (lists). There's much better and more
consistant support for other datatypes.
These are not at all concatenative languages; however, they're very well
developed combinative languages, and many of us believe that concatenative
languages have plenty to learn from them.
> Joy (parthogenesis!)
I see the goal of Joy as being demonstration that a stack-oriented language
can be purely functional, and in fact has some advantages over other ways of
building a purely functional language.
>3: "absorbing the syntax of other languages"
> I should like to see how syntax of other languages is absorbed
>in Forth, and how it changes the readability of programs. If it
>really is a good idea, since Joy-program = Joy-data, it can be done
>in Joy. I want to know whether it should be done.
Syntax absorbtion is almost never done in real Forth. It's too much work,
and the result is too complicated.
However, the tools which make syntax absorbtion possible are used in Forth
-- but they're used only when absolutely necessary. Often, the initial
version of a tool needs to use parsing, but a later version changes some
details and manages to work without the parsing. It's considered far better
to not parse, because we know that
>4: "Arbitrarily complex expressions"
> This is a perennial problem. In languages in which one has
>named formal parameters, the names can be used to refer to the
>actual parameters. Joy can't do this, although one might extend
>Joy in that direction. (Question: am I right in believing that
>it is done quite often in Forth?)
Not while I'm watching. Many Forth programmers avoid that; however, some
(whom I believe are C programmers at heart and should go home to C ;-)
prefer to use "local variables".
The advantage of working without local variables is very clear: the
programmer can split the expression at any point.
>Consider a definition in a
>fantasy functional language:
> f(x,y,z) =
> g(h(x,j(y,x,z),k(z,l(z,y)),x) (* I hope the
>parens are OK *)
>The point being that in the second line each of x,y,z occurs several
>times. The second line need not occur in a definition, but might be
>part of a larger (again!) expression. Question: how to do the same
>sort of thing when x,y,z are not available as names, and the actual
>parameters have to come from the stack.
The way to get a direct translation is, I believe, obvious (although the
translation itself is certainly not easy to extract). No doubt you
recognise that in its current form, this expression is an abomination from
the lowest pits of hell, whether it's written in its current form or
translated to SWAP-laden concatenative code.
A good programmer will attempt to refactor that expression into more easily
manageable blocks. This is utterly trivial once the program's written in
its concatenative form; it's worth the effort even if you have to do it in
the applicative form.
Hmm, your parens were indeed wrong; I'm going to assume that g needs that x
parameter, because otherwise h is getting x twice.
f(x,y,z) = g(m(x,y,z),x)
m(x,y,z) = h(x,j(y,x,z),n(y,z))
n(y,z) = k(z,l(z,y))
To make things simpler for me, I'm going to use stack picture combinator
names rather than the conventional names whenever that makes the word
shorter. I think you'll understand what's happening: for example, "ab--ba"
is the same as "SWAP".
f (z y x -- result) == abc--cabc m g;
m (z y x -- result) == 2dup n abcd--dacb j h;
n (z y --result) == over l swap k.
Next, a good Forth programmer would then do a minor rewrite of some of the
words to reduce the need for juggling -- some of the functions could
probably return some of their inputs as well as their outputs, and that
might greatly reduce the juggling; also, the order of inputs might be
changable (if "k" could be changed I could get rid of that swap, for
example). For that matter, the factorization might be better if I adjusted
it a bit -- for example, perhaps if I slid the "j h" at the end of "m" into
"f" instead things might work nicer. Or perhaps I should slide the 2dup at
the beginning of "m" into "f", and then merge it with the stack picture
combinator.
Let's do both, just for fun.
f (z y x -- result) == abc--cabcbc m j h g;
m (z y x -- result) == n abcd--dacb;
n (z y --result) == over l swap k.
That stack picture is getting too complicated, and the chain of reasoning is
getting too long -- I've done too much. Backtrack and try a different
refactoring.
f (z y x -- result) == abc--cabc m j h g;
m (z y x -- n[x,y] z y x) == 2dup over n k abcd--dacb;
n (z y z -- result z) == l swap.
...and so on. The interesting thing is that all of the refactorings I've
done are utterly simple; there's no need for machine help, or even strenuous
testing (although I'm not taking enough care, so I've probably messed
something up).
> I doubt whether there is a really clean solution in Joy or
>(that fragment of) Forth without named formals.
There's no really clean solution, period. It's a messy problem, and it was
even in its original applicative form -- so messy that you wrote it down
wrong.
>And yet, often
>the pattern with x,y,z will be more readable than the name.
>I do not know an answer to all this - except to hope that
>perhaps in _real_ problems you don't get such complex patterns.
You do. In traditional Forth the response is to "borrow" the return stack;
you shove values on it, and then you work as usual with the data stack. The
system I use above is one I've implemented before in Forth; it allows you to
make arbitrary pictures of combinators. I found myself using pictures for
any pair of combinators, or for any combinator as complex as 'rot'. All the
advantages of a lambda function, and none of the facoring disadvantages.
-Billy