Search code examples
postscript

How do you keep the `roll` operator straight?


In postscript, the roll operator is very general and difficult to visualize. How do you make sure you're rolling in the right direction?

I want to get a solid handle on roll because I want to be able to transform functions using variables

/f { % x y z
    /z exch def
    /y exch def
    /x exch def
    x dup mul
    y dup mul
    z dup mul add add % x^2+y^2+z^2
} def

into functions using stack manipulations, more like

/f { % x y z
    3 1 roll dup mul % y z x^2
    3 1 roll dup mul % z x^2 y^2
    3 1 roll dup mul % x^2 y^2 z^2
    add add % x^2+y^2+z^2
} def

or

/f { % x y z
    3 { 3 1 roll dup mul } repeat
    2 { add } repeat      % x^2+y^2+z^2
} bind def

These should both execute faster by having fewer name-lookups (hashtable search).

With roll I always have to test it out; and I usually get it wrong on the first try! I'm okay with exch, though


Solution

  • I had difficulty with roll for a very long time. I remember it now using these ways, which are all equivalent:

    the rhyme (-ish)

    n j roll

    • positive j, to roll away

      7 8 9  3 1 roll
      % 9 7 8

    • negative, to get it back (or "negateeve, to then retrieve")

      % 9 7 8
      3 -1 roll
      % 7 8 9


    stack (of things)

    Perhaps a better way to think of it is a physical stack (of books, say) so the top of stack is literally "on top".

    Then a positive roll goes up:

       for j number of times
         pick up n books
         put the top one on the bottom (shifting the substack "up")
         put them back down

    And a negative roll goes down:

       for j number of times
         pick up n books
         put the bottom one on top (shifting the substack "down")
         put them back down

    sideways

    But I usually picture the stack sideways, the way the objects would look in a file as a sequence of literals. So I think of the positive roll as stashing the top j things behind the nth thing; and the negative roll as snagging j things starting with the nth thing. Give and Take.

    Away.

    n j roll
    
    __ j > 0 __     move top j elements to the bottom of n
    
     n            TOS
      -------------|
     |       j     |
     |        -----|
     |       |     |
     V       V     |
    
     a b c d e f g h
    
    ^       |       |
    |       |-------|
    ^           |
     -<-<-<-<-<-
       move
    

    And back.

    __ j < 0 __   move j elements from the bottom of n to the top
    
     n            TOS
      -------------|
     |     j       |
     |-----        |
     |     |       |
     V     V       |
    
     a b c d e f g h
    
    |       |       ^
    |-------|       |
       |           ^
        ->->->->->- 
           move
    

    lint-roller

    Still another way is to picture it sideways, and laying a sticky wheel on top (a lint-roller, maybe)

    (a) (b) (c) (d) (e) 5 3 roll
    
               _______
              /       \
              |   3   |
              | / | \ |
              \_______/
     (a) (b) (c) (d) (e)

    Then a positive roll goes counterclockwise just like arc and rotate.

           _______ (e)
          /     / \
          |   3 --| (d)
          |     \ |
          \_______/ (c)
     (a) (b)
    
    
       (e)__(d)__(c)
         /\  |  /\
         |   3   |
         |       |
         \_______/
       (a) (b)
    
    
       (c)_______
         /\      \
     (d) |-- 3   |
         |/      |
         \_______/
      (e) (a) (b)
    
    
        _______
       /       \
       |   3   |
       | / | \ |
       \_______/
     (c) (d) (e) (a) (b)

    And a negative roll goes clockwise like arcn and a negative rotation.

        _______
       /       \
       |   3   |
       | / | \ |
       \_______/
     (a) (b) (c) (d) (e)
    
    
       (a)_______
         /\      \
     (b) |-- 3   |
         |/      |
         \_______/
      (c)       (d) (e)
    
    
       (c)__(b)__(a)
         /\  |  /\
         |   3   |
         |       |
         \_______/
       (d) (e)
    
           _______ (c)
          /     / \
          |   3 --| (b)
          |     \ |
          \_______/ (a)
     (d) (e)
               _______
              /       \
              |   3   |
              | / | \ |
              \_______/
     (d) (e) (a) (b) (c)

    eliminate the negative

    It shouldn't be difficult to see that negative rolls are entirely unnecessary because if j<0, it can be replaced by n-j. eg.

    3 -1 roll  % roll bottom 1 element from 3 to the top
    3 2 roll   % roll top 2 elements behind the 3rd
    

    are the same.

    16 -4 roll  % roll bottom 4 elements from 16 to the top
    16 12 roll  % roll top 12 elements behind the 16th
    

    are the same.


    This leads to the final, ultimate simplified view (though each of the above will work, too).

    Roll is just a big Swap

    You're really just exchanging the top j elements with the n-j elements below that.

    Say you have this mess on the stack (where $TOS$ marks the top of the stack), and want to order it properly:

    g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z  a  b  c  d  e  f $TOS$
    

    Count up (down) for n and j.

    g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z  a  b  c  d  e  f
    26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9  8  7  6  5  4  3  2  1
    |                                                         | j = 6 .  .  .  .
    | n = 26 .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
    
    > 26 6 roll   pstack
    
     a  b  c  d  e  f  g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z
    

    A negative value for j simply positions that dividing line relative to the deepest element from among the n elements (it counts from below).

    t  u  v  w  x  y  z  a  b  c  d  e  f  g  h  i  j  k  l  m  n  o  p  q  r  s
    26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9  8  7  6  5  4  3  2  1
    .  .  .  .   j = -7 |                                                      |
    .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  . n = 26 |
    
    > 26 -7 roll  pstack
    
     a  b  c  d  e  f  g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z
    

    Here is a convenience function that gives an interface to roll that's more closely analogous to the big swap view.

    % r0..rN  s0..sM  N  M   swap   s0..sM  r0..rN
    % a gentler interface to the power of roll
    /swap {
        exch 1 index add exch
        roll
    } def
    0 1 2 3 /a /b /c 4 3 swap pstack
    

    Output:

    GPL Ghostscript 8.62 (2008-02-29)
    Copyright (C) 2008 Artifex Software, Inc.  All rights reserved.
    This software comes with NO WARRANTY: see the file PUBLIC for details.
    3
    2
    1
    0
    /c
    /b
    /a
    GS<7>GS<7>