[stack] impure concatenativity: let without translation
John Nowak <[email protected]>
| Newsgroups | gmane.comp.lang.concatenative |
|---|---|
| Message-ID | <[email protected]> |
Just moments ago, I gave this example of the quadratic formula:
(define (quad a b c)
(let ((d b 4 a c * * -)
(r d 0 >= [d sqrt] ["quad: sqrt of negative" error] if)
(x 2 a *)
(p b r - x /)
(n b r + x /))
p n))
How it works should be obvious after you get used to looking at this
sort of prefix/postfix syntax. It's a very nice way to write this
function; arguably nicer than what you can do in Scheme as the
multiple values returned can be easily consumed (or ignored) by
another function. For example, if we only cared about the positive
root, we could do 'quad pop'.
Now here's the quadratic formula in Joy:
quad ==
[[[pop pop 2 *]
[pop 0 swap -]
[swap dup * rollup * 4 * - sqrt]]
[i] map]
ternary i
[[[+ swap /]
[- swap /]]
[i] map]
ternary.
Clearly we want to be able to write the first version of 'quad' as
this version is truly awful (both in terms of readability and likely
execution speed).
Let's go back and examine how the first version works. I'd suggest
that each variable introduced via 'let' must bind the result of a
function that requires *no* arguments on the stack. This is a
necessary restriction as otherwise the order in which you evaluate the
functions matters and a bug in one can screw up the other. (Well, you
could save and restore the stack for each binding, but that requires
you represent the stack as a linked list.)
I argue that translating the 'let' expression into point-free code is
a *bad* idea. Why? Let's say we wrote this buggy version instead:
(define (quad a b c)
(let ((d b 4 a c * * * -)
(r d 0 >= [d sqrt] ["quad: sqrt of negative" error] if)
(x 2 a *)
(p b r - x /)
(n b r + x /))
p n))
See it? The 'd' function has an extra call to '*'. If we don't
translate the 'let' away, we are able to give an intelligent error
that implicates the call to '*' in the expression 'b 4 a c * * *' in
the binding of 'd'. If we translate the let away however, who knows
what the error will be. It depends how the compiler does the
translation of course. The likely result will be an unhelpful type
error that requires the programmer to tediously go over the entire
function to try and find the problem.
Worse yet, the translation might actually yield code that type checks
by requiring an additional argument on the stack, but produces the
wrong result and has the wrong type! (I'd guess that would be the case
here actually.) You'd not catch this until much later when actually
using 'quad' somewhere and getting confusing error messages. This sort
of problem can take a long time to track down. If the reason we're
introducing types is to get rid of these scenarios, then it seems
translating everything to a point-free form is a bad idea. Such a
translation necessarily discards the very information that allowed the
programmer to write the function more easily in the first place!
What I'm suggesting is that dropping any pretense of being purely
concatenative offers some clear usability benefits with no loss in
expressiveness. It would likely offer efficiency advantages as well;
splicing quotes together to embed variables into the middle of
anonymous functions is less than fun. Additionally, you can define
primitives like 'swap' within the language itself:
(define (swap a b) b a)
Are there any other proposals for an impure (or, more nicely,
"hybrid") concatenative language out there? What are the negative
practical implications of being impure?
- John