Re: [stack] stackless fixed-arity concatenative languages

John Nowak <[email protected]>
Newsgroups gmane.comp.lang.concatenative
Message-ID <[email protected]>
After doing some thinking, I've come up with an example of where an n- 
ary 'dip' combinator makes a complete mess of things. The example is  
Factor's 'bi@' combinator. The 'bi@' combinator takes some x, some y,  
and a quotation which it applies to both. As an example, '4 5 [ dup  
* ] bi@' yields '16 25'.

Here's one way we might think to implement 'bi@' in Factor:

    : bi@ ( x y quot -- x' y' ) dup dip dip ;

The thinking here is that we duplicate the quotation, apply it below  
the original quotation to 'y', then apply the original quotation to  
'x'. Just to be clear, I'll trace the execution of an expression as if  
Factor used term rewriting (which I find is the easiest way to show  
this sort of thing):

    4 5 [ dup * ] bi@
    4 5 [ dup * ] dup dip dip
    4 5 [ dup * ] [ dup * ] dip dip
    4 5 dup * [ dup * ] dip
    4 5 5 * [ dup * ] dip
    4 25 [ dup * ] dip
    4 dup * 25
    4 4 * 25
    16 25

Great. It worked as we wanted it to. There is, however, a bug. The bug  
is that the first call to the quotation has both 4 and 5 on the stack,  
while the second call only has 4 on the stack. In other words, the  
quotations are exposed to stacks of different sizes. If the quotation  
supplied uses two elements on the stack rather than one, very weird  
things will happen.

Surely a type system like Cat's would catch this, yes? Well, sort of.  
Unfortunately, it does it in the worst way possible; it gives bi@ the  
type 'A [A -> A b] -> A b b'. What this means is that the quotation  
must keep the type of the stack the same and add another element. This  
means doing '4 5 [dup *] bi@' would be a type error since '[dup *]' is  
not an instance of '[A -> A b]'; it's an instance of '[A b -> A b]'.  
Furthermore, it doesn't even indicate that two elements must be below  
the quotation on the stack! As a result, all you can do are things  
like '4 5 [6] bi@' to yield '4 5 6 6' which makes the function  
completely useless.

One way to solve this problem is to use a version of 'dip' that calls  
a quotation with only one element and requires the quotation to yield  
one element. Cat's type system cannot enforce this, but Fifth's type  
system can. Below are the types of the unrestricted 'dip' combinator  
and the restricted version:

    dip    :: A b [A -> C] -> C b
    dip1-1 :: a b [a -> c] -> c b

As you can see, the types are basically the same, except the quotation  
can only take one element 'a' and transform it to one element 'c'  
rather than the stack 'A' to the stack 'C'. Using the restricted  
combinator, our function would look like this:

    bi@ = dup dip1-1 dip1-1

We could then assign it the following type:

    bi@ :: a a [a -> b] -> b b

This type is much more like what we wanted. Indeed, it'll let us do '4  
5 [dup *] bi@'. It does have the restriction however that both  
elements below the quotation must be of the same type. To solve this  
we need higher rank types which is another discussion altogether.

Now the nasty bit: 'bi@ = dup dip dip' can be assigned the type above  
in Fifth provided that you give it a signature. The signature 'a a [a - 
 > b] -> b b', however, is not an instance of the type that is  
inferred (which is the same as Cat's: 'A [A -> A b] -> A b b'). This  
is very unfortunate as it means Fifth does not have principal types in  
the presence of n-ary combinators! This problem would hold for Cat as  
well if it had a way of enforcing similar arity restrictions (which it  
will need to be able to eventually for things like callbacks if  
nothing else).

This is rather damning, and it may mean that n-ary combinators have to  
be completely eliminated unless a more expressive system can be found.  
One way to do this would be fixed arity versions of 'dip' and 'i',  
along with built-in mechanisms for pattern matching and deconstruction  
(a la Haskell, et al) rather than simply using normal functions like  
'unlist' as I've talked about previously. There would still need to be  
some way to address the need to allow the user to write new  
combinators like '2dip' that apply a quotation below the top two  
elements of the stack rather than just one. Introducing something like  
lambda expressions would be one way to do this (similar to how  
Backus's Formal FP allowed lambda expressions for creating new  
combinators).

It's important to remember that 'dup dip dip' works fine for our  
expected use case, but has some very strange behaviors lurking about!  
The new type system I'm working on does capture this by giving 'dup  
dip dip' the type '[$A] => $A [$A] dip'. Essentially, it types 'dup  
dip', realizes there's no most general way to proceed, and stops and  
waits for more information about $A (the quotation passed in).  
Unfortunately, this type system is undecidable, expensive, and yields  
ugly types. What fun!

So what's the correct way to write 'bi@' so that Cat gives it the  
correct type without placing an arity restriction on the quotation? It  
turns out there isn't one. I thought maybe something like 'under apply  
slip' would work, but I get the rather insane type 'A b (A self b -> C  
(C -> D) e) -> D e'. Amusing. Another attempt was 'under [[apply dip]  
dip apply', but this gets the type 'A b b -> (A b -> A) -> A' which  
means you can only do things like '3 4 [pop] bi@' and hence it's  
rather useless. If you think about it, there's really no way it could  
possibly give a useful type without an arity restriction. Well,  
actually it could if you had first-class stacks. That's something else  
I have to consider as a possible solution...

Hey, I thought concatenative languages were supposed to be simpler!

- 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.