PostScript is an amazing programming language for describing scalable graphics and interfaces! I especially enjoy the ability to very concisely represent information, using dedicated PostScript facilities to construct domain-specific languages, and I have used it to illustrate many situations that would otherwise be quite difficult to show.
I hope that an example can illustrate this point: In Volume 4 of The Art of Computer Programming, Donald Knuth explains how we can find kernels with maximum weight in C_{100}, the cycle graph with 100 nodes. In that example, the weight he assigns to each node is the Thue-Morse code, i.e., (-1)^ν, where ν is the number of occurrences of 1 in the binary encoding of the node number. So for example, the weight of node 2 is -1, and the weight of node 3 is 1.
We can easily determine the Thue-Morse weight of an integer in PostScript:
/weight { 1 dict begin /nu 0 def
{ dup 0 eq { pop exit } if
dup 1 and 1 eq { /nu nu 1 add def } if
-1 bitshift } loop
-1 nu exp end } bind def
Example: 8 weight ⇒ -1
Now, how might we compactly lay out the cycle graph with 100 nodes? Using PostScript lets us easily play with different layouts. For example, let us use r, u, l and d to move right, up, left and down, respectively:
/instrs {
{ r r r r r r r r r r r r r r r r
d
l l l l l l l l l l l l l l
d
r r r r r r r r r r r r r r r
d
l l l l l l l l l l l l l l
d
r r r r r r r r r r r r r
d
l l l l l l l l l l l l l l l
l u r u l u u u } } def
We can see what the described path looks like by interpreting this mini-language:
Now, we only have to draw the nodes themselves on top of this path. First, let us define which nodes are actually part of a kernel with maximum weight, found with the methods outlined by Knuth (operations on reduced and ordered Binary Decision Diagrams):
And now we can simply draw the nodes by interpreting the instructions again, and indicating whether a node is part of the kernel, and whether its weight is positive:
/Palatino-Roman 0.4 selectfont
0.04 setlinewidth
1 1 instrs length {
/num exch def
newpath
num weight -1 eq
{ -0.4 -0.4 0.8 0.8 4 copy
inkernel num get { 0.8 } { 1 } ifelse gsave setgray rectfill grestore
rectstroke
}
{ 0 0 0.4 0 360 arc
inkernel num get { 0.8 } { 1 } ifelse gsave setgray fill grestore
stroke
}
ifelse
0 0 moveto num 5 string cvs dup stringwidth pop -2 div -0.12 moveto show
instrs num 1 sub get cvx exec } for
In this case, I am using circles for nodes with positive weight.
The beauty of your functional approach is that you're using PostScript code as PostScript data, thanks to the fact that PostScript is fully homoiconic, just like Lisp! So it's excellent for defining and processing domain specific languages, and it's effectively like a stack based, point free or "tacic," dynamically bound, object oriented Lisp!
The PostScript dictionary stack enables OOP with multiple inheritance and customizable instances (like prototypes, in that you can add methods and instance variables to individual objects).
>Object Oriented Programming in NeWS. Owen M. Densmore, Sun Microsystems.
Abstract
>The NeWS window system provides the primitives needed to create window
managers and user-interface toolkits, but does not, itself, supply either. This is done
to achieve a layering strategy for building several higher level systems that can share
NeWS as their low level window system. None of the traditional ‘‘tool kit’’ solutions
currently span the diverse set of clients NeWS needed to serve; they simply lack
sufficient flexibility. We are exploring an object oriented approach which uses a flexible
inheritance scheme. This paper presents our initial attempt at introducing a
Smalltalk style class mechanism to PostScript, and our first use of it.
Apps like (early versions of) Adobe Illustrator, and tools like Glenn Reid's PostScript Distillery (and later Acrobat Distiller), used a domain specific subset of PostScript as their save file format: A save file was a domain specific language that just happened to be PostScript (with no loops or other programming constructs), so you could prepend some standard PostScript function definitions to the front of the save file and send it to a PostScript printer to print. Or you could redefine those names to do something else, like extracting the text, or cataloging the colors, or creating interactive editable objects that you could manipulate and save out again.
Distillery (and the later Acrobat Distiller) went the other way, by partially interpreting any arbitrary PostScript drawing program against the stencil/paint imaging model (capturing a flat list of calls to fill and stroke, transforming the paths to a standard coordinate system, optimizing graphics state changes between rendering calls, unrolling all loops, and executing any conditionals, loops, function calls or other programming constructs). That's exactly what happens when you convert a PostScript file to PDF.
Acrobat is the (purposeful) de-evolution of PostScript from a Turing complete programming language to a safe static file format: PDF is essentially just PostScript without any of the programming constructs. (But then they added a lot more crap to it, to the point that it was so buggy they needed to push out patches several times a month over many years.)
Here's some more stuff about PostScript distilleries and metacircular PostScript interpreters (but with old links, so I've included the new one below):
>The Shape of PSIBER Space: PostScript Interactive Bug Eradication Routines — October 1989
Written by Don Hopkins, October 1989. University of Maryland Human-Computer Interaction Lab, Computer Science Department, College Park, Maryland 20742.
>Left shows objects on the process’s stack displayed in windows with their tabs pinned on the spike. Right shows a Pseudo Scientific Visualization of the NeWS rootmenu instance dictionary.
>Abstract: The PSIBER Space Deck is an interactive visual user interface to a graphical programming environment, the NeWS window system. It lets you display, manipulate, and navigate the data structures, programs, and processes living in the virtual memory space of NeWS. It is useful as a debugging tool, and as a hands on way to learn about programming in PostScript and NeWS.
>[...] Printing Distilled PostScript: The data structure displays (including those of the Pseudo Scientific Visualizer, described below) can be printed on a PostScript printer by capturing the drawing commands in a file.
>Glenn Reid's "Distillery" program is a PostScript optimizer, that executes a page description, and (in most cases) produces another smaller, more efficient PostScript program, that prints the same image. [Reid, The Distillery] The trick is to redefine the path consuming operators, like fill, stroke, and show, so they write out the path in device space, and incremental changes to the graphics state. Even though the program that computes the display may be quite complicated, the distilled graphical output is very simple and low level, with all the loops unrolled.
>The NeWS distillery uses the same basic technique as Glenn Reid's Distillery, but it is much simpler, does not optimize as much, and is not as complete.
I hope that an example can illustrate this point: In Volume 4 of The Art of Computer Programming, Donald Knuth explains how we can find kernels with maximum weight in C_{100}, the cycle graph with 100 nodes. In that example, the weight he assigns to each node is the Thue-Morse code, i.e., (-1)^ν, where ν is the number of occurrences of 1 in the binary encoding of the node number. So for example, the weight of node 2 is -1, and the weight of node 3 is 1.
We can easily determine the Thue-Morse weight of an integer in PostScript:
Example: 8 weight ⇒ -1Now, how might we compactly lay out the cycle graph with 100 nodes? Using PostScript lets us easily play with different layouts. For example, let us use r, u, l and d to move right, up, left and down, respectively:
We can see what the described path looks like by interpreting this mini-language: Now, we only have to draw the nodes themselves on top of this path. First, let us define which nodes are actually part of a kernel with maximum weight, found with the methods outlined by Knuth (operations on reduced and ordered Binary Decision Diagrams): And now we can simply draw the nodes by interpreting the instructions again, and indicating whether a node is part of the kernel, and whether its weight is positive: In this case, I am using circles for nodes with positive weight.