Search code examples
ghostscriptpostscript

point free procedure composition


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.


Solution

  • 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 loads 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, execing 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