[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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.