Suppose I have two procedures p1
and p2
on the stack and I want to consume them and leave on the stack a new procedure that combines their effects. I would like a procedure to do this. This is easy enough if I am always willing to do a little dictionary bookkeeping. But can I do it without introducing any names? (Note that I want the resulting procedure, not just to execute the combined effects on the current stack.)
Consider for example
/compose {<< /f1 4 2 roll /f2 exch >>begin {f1 f2} end}bind def
This won't work of course because f1 and f2 will be unknown after end
. But this broken code should illustrate what I'm after.
It's totally possible, and not terribly difficult. You make a new array with each procedure object followed by the executable name exec
. Then make that array executable.
/combine { % {a} {b} . {{a} exec {b} exec}
/exec cvx exch % {a} exec {b}
/exec cvx % {a} exec {b} exec
4 array astore % [{a} exec {b} exec]
cvx % {{a} exec {b} exec}
} def
For a style closer to your original, with named arguments, I would write it like this:
% fun1 fun2 compose proc
/compose { 2 dict begin % f1 f2
{f2 f1}{exch def} forall %
({ //f1 exec //f2 exec }) % ({ //f1 exec //f2 exec })
cvx exec % { <f1> exec <f2> exec }
end } def
The //immediate-name
syntax is very powerful. Here I use a code template in a string. When the string is executed cvx exec
it invokes the scanner upon the contents and it is then that it automatically load
s all tokens prefixed with double-slash //
. The comment <f1>
indicates the contents of the named variable. Just like an {executable array}
in the program stream is not executed but yields the proc on the stack, exec
ing a string containing one will also yield the proc on the stack.
For the named arguments style, I make use of a few special rules of postscript: executable arrays are not executed, thus the array of variable names can be written as an executable array and then used as data without any additional hassle. But by using the executable syntax, the contents -- the names -- can be written without the /
s. So, instead of [ /f2 /f1 ]
we can write the shorter { f2 f1 }
.
The arguments part could also be factored into its own function.
/argsbegin { % a r g s _ {n a m e s} . -
dup length dict begin
{exch def} forall % currentdict:<</n _ /a s /m g /e r /s a>>
} def
/compose { {f2 f1} argsbegin
({//f1 exec //f2 exec}) token pop exch pop %another way to invoke the scanner
end } def
Or, to actually put the arguments the right way around, it could be like the following. It's just a little more awkward to simulate a backwards forall
with a for
loop.
/argsbegin { % a r g s _ {n a m e s} . -
dup length dup dict begin % a r g s _ {} n
1 sub -1 0 { % a r g s _ {} i
3 2 roll % a r g s {} i _
3 copy % a r g s {} i _ {} i _
pop % a r g s {} i _ {} i
get % a r g s {} i _ /s
exch def % a r g s {} i
pop % a r g s {}
} for % {}
pop
} def
/compose { {f1 f2} argsbegin
({//f1 exec //f2 exec}) cvx exec
end } def