Path: csiph.com!eternal-september.org!feeder.eternal-september.org!nntp.eternal-september.org!.POSTED!not-for-mail From: Kragen Javier Sitaker Newsgroups: comp.lang.forth,comp.lang.postscript,alt.folklore.computers,comp.lang.lisp Subject: Re: Postscript in 4tH Date: Tue, 08 Sep 2026 22:38:58 -0300 Organization: Primarily biological and memetic Lines: 218 Message-ID: <87o6e7cifh.fsf@debian> References: <117hl2n$1ne4b$1@dont-email.me> <871pb7l36f.fsf@nightsong.com> <875x0gib1k.fsf@debian> <117prfv$gq7e$3@dont-email.me> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Injection-Date: Wed, 09 Sep 2026 01:41:17 +0000 (UTC) Injection-Info: dont-email.me; logging-data="743243"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX190fW8gIzWSqID3kxNQqNbc"; posting-host="14855f7c502621dd05bbebfb08663600" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.2 (gnu/linux) Cancel-Lock: sha1:w2H9fPudAiYX6F5VpCvH8hODz+U= sha1:xHlOlZyxJGRSVlGa4LFDRMWv2GA= sha256:vRXaLAtZOOYToD5jUQSwUAJ3futAmDzYdGSpg8FGSno= sha1:zJ1qvbr1kPHhtwS02Vz1Qy9M2ZA= sha256:ecjzB31X2d1Ftj2SEHeqoP2tKtWmFqy8CzadM+GikPk= Xref: csiph.com comp.lang.forth:135613 comp.lang.postscript:4025 alt.folklore.computers:235549 comp.lang.lisp:61628 (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 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” : 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 or of Byte . 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