(Please forgive in advance the LLM-like wording of what follows. I
wrote it entirely by hand, including reading the documents I linked, but
I?ve been coaxing
Lawrence D?Oliveiro <
ldo@nz.invalid> writes:
On Tue, 08 Sep 2026 02:09:43 -0300, Kragen Javier Sitaker wrote:
I think of PostScript as being Lisp, more or less, but with
quasi-Forth syntax ...
PostScript is homoiconic, but the resemblance to Lisp ends there. Lisp
has macros, PostScript doesn?t. Lisp needs macros,
At the time that PostScript was designed, most Lisps did not have
macros; they still had fexprs. Some Lisps did have macros, most notably MACLISP, but they had not yet become central to language style the way
they are today. Remember that PostScript first shipped in 01982, when
the world was dominated by BASIC, FORTRAN, and COBOL.
PostScript does in fact have something like readmacros; in particular, readhexstring was the recommended way to handle image data, so that the
image data didn?t have to be loaded into a PostScript object by way of
the PostScript parser. But, for the most part, like Smalltalk,
PostScript uses a lightweight lambda syntax for most of the things you
would use macros for in Lisp.
PostScript has several other key similarities to Lisp. Referencing Paul Graham?s ?What Made Lisp Different? <
https://paulgraham.com/diff.html>:
1. Conditionals. Yes, but all languages had conditionals by 01982.
2. A function type. ?In Lisp, functions are first class objects--
they?re a data type just like integers, strings, etc, and have a literal representation, can be stored in variables, can be passed as arguments,
and so on.? Yes; PostScript goes even harder here than Lisp, because
there?s actually no way to define a named function in PostScript. You
have to define an anonymous function and pass it as an argument to /bind
in order to give it a name. This is somewhat related to homoiconicity
but is not the same thing.
3. Recursion. Yes, but only FORTRAN lacked recursion by then. COBOL
and BASIC lacked local variables, though, which generally made recursion impractical.
4. A new concept of variables. ?In Lisp, all variables are effectively pointers. Values are what have types, not variables, and assigning or
binding variables means copying pointers, not what they point to.?
PostScript does work this way, as does Smalltalk (not coincidentally,
from the same lab, which was also heavily into Lisp). It?s easy to
forget how unusual this was, because now languages more or less like
this dominate the scene: Python, JS, Ruby, PHP, Lua, and to some extent,
even Java and C#. But none of the other popular languages of the day
worked that way. Not conventional assembly (untyped, and variables are
memory addresses), not BASIC, not C, not Pascal, not awk, not the Bourne
shell, not the C shell, not Forth, not BLISS, not FOCAL, not Mesa, not
ML, not Algol, not PL/I, not Ada.
Moreover, in both Lisp and in PostScript, this extends to not just
regular variables, but any item of any data structure. In BASIC, C,
FORTRAN, or Pascal, you might have arrays of strings, or arrays of
integers, or in some cases arrays of 3-element arrays of integers, but
you couldn?t have an array of arbitrary-type values. PostScript arrays
contain arbitrary-type values, as do the most common type of vectors in
Common Lisp, and lists in any Lisp.
5. Garbage-collection. Yes.
6. Programs composed of expressions. Not really, but closer than any of
the currently popular languages.
7. A symbol type. Yes, PostScript distinguishes /names from strings,
which is arguably the feature of all of these that is most distinctive
to Lisp (the only other languages that do this are Ruby, Smalltalk, and,
in a way, JS), and PostScript shares it.
8. A notation for code using trees of symbols. Yes. The only
difference is that in conventional Lisp they?re binary trees, while in PostScript they?re N-ary ordered trees.
9. ?The whole language always available?, i.e., arbitrarily overlapping
compile time and runtime. Yes.
Given that PostScript clearly hits 8 out of 9 of these criteria, and
arguably all 9, while no then-popular language other than Lisp came
anywhere close, I think it?s pretty clear that it mostly belongs to the
Lisp family. Its resemblance to Lisp is much deeper than just being homoiconic.
Forth hits #1, #3, and #9.
****
If you?re not familiar with PostScript, you might be puzzled by #8.
Let?s define a new function that tells us the sign of a number and bind
it to the symbol `sign`:
GS>/sign {dup 0 gt {pop /positive} {0 lt {/negative} {/zero} ifelse} ifelse} def
GS>3 sign = 0 sign = -53 sign =
positive
zero
negative
We can use `load` on the quoted symbol to find the current definition of
`sign` without executing it:
GS>/sign load ===
{dup 0 gt {pop /positive} {0 lt {/negative} {/zero} ifelse} ifelse}
It turns out that?s an array of length 3:
GS>/sign load type =
arraytype
GS>/sign load length =
6
We can fetch things from it:
GS>/sign load 0 get === /sign load 3 get ===
dup
{pop /positive}
See, the zeroth thing in the array is `dup`. Which is a ?name?, not a
string, which PostScript also has:
GS>/sign load 0 get type = (hello, world) type =
nametype
stringtype
We can even modify the array in place to modify the code, although we
can?t lengthen it:
GS>/sign load 0 /pop cvx put
GS>/sign load ===
{pop 0 gt {pop /positive} {0 lt {/negative} {/zero} ifelse} ifelse}
Now it crashes because `pop` drops the operand `gt` wants to compare:
GS>3 sign =
Error: /stackunderflow in --gt--
You can define a regular, non-executable array and turn it into a
function with regular programming operators:
GS>/square 2 array def
GS>square ===
[null null]
GS>square 0 /dup cvx put square ===
[dup null]
GS>square 1 /mul cvx put square ===
[dup mul]
GS>/square square cvx def 3 square ===
9
GS>/square load ===
{dup mul}
If you?re familiar with old Lisp systems, almost all of this will seem
very familiar, but the syntax will seem backwards, and you?ll wonder why
code is stored in arrays instead of lists.
You can also define a function that uses the PostScript reader `token`
to consume input, similar to `word` in Forth but much more similar to
`read` in Lisp:
GS>/typeof {currentfile token pop type} def
GS>typeof dup ===
nametype
GS>typeof 34 ===
integertype
GS>typeof (hello, world) ===
stringtype
Where `token` diverges radically from Forth is that it will read an
entire nested expression (without executing it), not just a token as the
term is normally understood:
GS>typeof {dup mul dup add} ===
arraytype
(Note that this *doesn't* work for [3 4], because `[` and `]` are
operators, so [3 4] isn?t an expression!)
Given that you can parse expressions from the input stream at compile
time and generate code at runtime, you can obviously implement Lisp-like
macros in PostScript. It just isn?t an established practice.
The one very non-Lispy weird thing, which doesn't have a similar feature
in any other language I know of, is that the executable bit set by `cvx`
is an attribute of references, not objects. So `{dup mul}` and `[dup
mul]` can be the same object as seen through two different references.
That?s why making `square` executable required calling `def` again
rather than just `cvx`.
****
To get a feel for what 01982 was like, check out the October 01982 issue
of the CACM <
https://dl.acm.org/toc/cacm/1982/25/10> or of Byte <
https://archive.org/details/BYTE_Vol_07-10_1982-10_Computers_in_Business>.
The languages mentioned in the CACM issue are Fortran (in the table of contents); COBOL, PL/I, and APL (in the data administration article);
Algol 60 and Macsyma but not Lisp (in the ?microanalysis? (profiling)
paper, which also included some Algol as pseudocode); Lisp as the
pseudocode, though without naming it (in Kornfield?s paper about ?combinatorially implosive? algorithms); none (in the geometric
algorithm paper); Fortran (in the gamma-deviate-generation comment); and
Ada (in the ?ACM Forum? letters column.) It?s a total head trip.
In Byte, the languages mentioned are 68000 assembler, FORTRAN 77,
Pascal, BASIC, COBOL, and C (in the Cromemco ad); Visicalc, BASIC,
Pascal, FORTH, and BASIC again (in the table of contents); Pascal
(though it?s really talking about the UCSD Pascal OS) and FORTRAN (in
the editorial); BASIC, COBOL, BASIC again, and LISP (in the Letters);
Visicalc and something called Microfinesse (in the Visicalc article);
etc.
That?s just the first 41 pages; I?ll leave the other 490 pages to you.
Kragen
--- PyGate Linux v1.5.19
* Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)