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: Thu, 10 Sep 2026 01:06:45 -0300 Organization: Primarily biological and memetic Lines: 301 Message-ID: <87h5jxagx6.fsf@debian> References: <117hl2n$1ne4b$1@dont-email.me> <871pb7l36f.fsf@nightsong.com> <875x0gib1k.fsf@debian> <117prfv$gq7e$3@dont-email.me> <87o6e7cifh.fsf@debian> <117qido$ng8h$3@dont-email.me> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Injection-Date: Thu, 10 Sep 2026 04:09:05 +0000 (UTC) Injection-Info: dont-email.me; logging-data="1791291"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1/wXZZr0GVGiqv7CCHw1Frp"; posting-host="c87f7b499d57f9157cd7adfbbe69f310" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.2 (gnu/linux) Cancel-Lock: sha1:5EwNpOTrMRp8dnt/3s8MxAQZry4= sha1:Jrs0auYtNSBrZD+5ZVy+5jArsJQ= sha256:JaUgDiYpZlo/b5KYS1PRv9jn7IzK0XUndxlWBz0leQA= sha1:Il0G6P5SJxxU+hAb/8am08qQqhY= sha256:bvpAs9rxu7u1NqT7cNiRhcU991m5OFrHQ0KIQ4tXM44= Xref: csiph.com comp.lang.forth:135638 comp.lang.postscript:4040 alt.folklore.computers:235557 comp.lang.lisp:61635 (Apologies if this sounds like AI slop; I’ve been interacting with Claude Opus 5 a lot today, and although I didn’t use any LLM to write any of this, its voice may be leaking into mine.) Lawrence D’Oliveiro writes: > On Tue, 08 Sep 2026 22:38:58 -0300, Kragen Javier Sitaker wrote: >> 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. > > It’s just a function. It’s like saying Python has “readmacros” because > the JSON and XML library modules have functions for reading input It’s a function that runs while the PostScript source code is being read, which means that it can change the interpretation of that source code. That’s a way that PostScript is like Lisp and unlike Python. Python’s JSON and XML library modules’ functions are different from Lisp readmacros because they cannot read from the Python input stream; the Python parser finishes running before any Python code runs. There’s no way to do the equivalent of this readmacro-like example from my post in Python: GS>/typeof {currentfile token pop type} def GS>typeof dup === nametype To sharpen this, note that it doesn’t matter that `dup` is defined: GS>typeof oagjijgw === nametype You’d have to be able to write something in Python which gave you a session like this: $ python3 Python 3.11.2 (main, Aug 26 2024, 07:20:54) [GCC 12.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from somemodule import typeof >>> typeof(oagjijgw) # or ideally even without the parentheses I claim that this `typeof` function is impossible to write in Python, because the parser throws a NameError before `typeof` has a chance to run, given that I haven’t defined `oagjijgw` previously. That is how PostScript’s `token` (and `readstring`, `readhexstring`, etc.) are more like Lisp readmacros than they are like any function in any possible Python module. >> 2. A function type. “In Lisp, functions are first class objects-- [...] > > Homoiconicity comes in because the function body is stored in an array > object, which is a PostScript language type, and its contents are also > objects of various PostScript language types. The analogous statements were true of most Lisps in 01982, because `lambda` was basically implemented as `quote` when it was the argument of a function (or subr), but they are true of almost no other programming languages except assembly language, so this is another similarity between PostScript and Lisp. However, the analogous statements are not true of most current Lisps, which have adopted the position that lambda-expressions are lists, and functions are not lists. SBCL, for example: * (type-of 3) (INTEGER 0 4611686018427387903) * (type-of (lambda () 3)) COMPILED-FUNCTION * (type-of '(lambda () 3)) CONS Or Racket: > (pair? (lambda () 3)) #f > (pair? '(lambda () 3)) #t > (procedure? (lambda () 3)) #t > (procedure? '(lambda () 3)) #f Perhaps you would argue that PostScript is homoiconic and SBCL and Racket are not. However, most people who know the word do consider them to be homoiconic, because, even though the *runtime representation* of functions does not contain objects of various Lisp language types, the *source code* is just a Lisp list, so building Lisp code in Lisp is easy: > (eval (cons 'lambda (cons '() (cons 3 '())))) # > ((eval (cons 'lambda (cons '() (cons 3 '()))))) 3 Unfortunately, this definition of homoiconicity suggests that any language compiled from sequences of characters is “homoiconic” unless for some reason it lacks the ability to manipulate sequences of characters. So “homoiconic languages” is no longer a logically well-defined category; it’s more a sliding scale of what programmers find easy or difficult. But, look again at PG’s explanation of what he means by “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.” The only one of these that plausibly relates to homoiconicity is “have a literal representation”. In old Lisps you could just print out the definition of any function as an S-expression, but, as we see above with Racket, that is not currently the case; we get something like `#` or `#`. But that’s still a string representation! And *most* languages don't have the ability to print out, for example, struct literals. What makes a “string literal” or “integer literal” a *literal* is that you can put it in your source code without having to give it a name. C since C99 has “compound literals” for structs and arrays , which look like `(struct foo){3, "hi"}`, and evaluate to a value of that type without having to bind it to a name. Older versions of ANSI C treated structs the way C still treats functions; instead of writing return (struct point){x + dx, y + dy}; you had to write struct point p = {x + dx, y + dy}; return p; Golang struct literals are the same thing : “A struct literal denotes a newly allocated struct value by listing the values of its fields.” JS has “array literals” for which the example given is `["Apple", "Banana"]`. So the most plausible gloss of PG’s “has a literal representation” is that there are Lisp expressions which create new functions when evaluated, which is just as true of modern Common Lisp and Scheme as it was of Franz Lisp in 01982. And it’s obviously true of PostScript, but not of C, Pascal, BASIC, etc. > Where PostScript is lacking is in missing support for lexical binding. > Anybody who has done much PostScript programming knows how awkward it > is to simulate anything resembling local variables. This is another similarity between PostScript and the then-popular Lisps, which almost entirely used dynamic scope like PostScript, rather than lexical scope like almost all other programming languages. However, it is true that the facility is more convenient to use in Lisp. Here’s a slightly reformatted interactive session showing how awkward it is to simulate anything resembling local variables, in case anyone is curious: GS>/! {exch def} def GS>/printbuf ( ) def /. {printbuf cvs print} def GS>/hanoi { 4 dict begin /n ! /dest ! /storage ! /src ! n 0 gt { src dest storage n 1 sub hanoi n . ( from ) print src . ( to ) print dest = storage src dest n 1 sub hanoi } if end } def GS>/A /B /C 3 hanoi 1 from A to C 2 from A to B 1 from C to B 3 from A to C 1 from B to A 2 from B to C 1 from A to C This is definitely more awkward than in most languages, but it doesn’t seem prohibitive to me. One major annoyance for interactive use is that, if execution aborts with an error, all those local variable scopes are left on the stack. Might be useful for debugging, I guess, but you can end up redefining things in the wrong dictionary. >> 5. Garbage-collection. Yes. > > Note that this was only added in PostScript level 2. In the original > language implementation, memory management had to be done in (I think > it’s called) mark-release style, using explicit calls to the “save” > and “restore” functions. Really? I had no idea! I always wondered why they thought `save` and `restore` were worth incorporating. So that’s a way in which the original PostScript was actually more like Forth, where you use `marker` (or `forget`) to deallocate a chunk of the dictionary. >> 6. Programs composed of expressions. Not really, but closer than any >> of the currently popular languages. > > No distinction between “statements” and “expressions”. Yes, that’s true, PostScript doesn’t have a statement/expression distinction. But in that case we should award this Lispiness point to Forth as well. >> 7. A symbol type. [...] > > True enough. > > Java has the option for “interned” strings, which is a way of having a > symbol type without having a symbol type, if you like. That’s true, and in particular that’s useful if you’re implementing some kind of language parser or interpreter in Java. Python has this too. >> 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. > > In Lisp, the tree structure is apparent in the static syntax. In > PostScript, the tree structure (such as it is) gets largely > constructed dynamically at run time, and need not correspond to static > syntax at all. I’m not sure I agree. Here’s my example of tree-structured code in PostScript, from the previous post: GS>/sign {dup 0 gt {pop /positive} {0 lt {/negative} {/zero} ifelse} ifelse} def This is an executable array of six things, the fourth of which is `{pop /positive}`. To me, that’s *more* apparent from the static syntax than the fact that in `(lambda (x y) (sqrt (+ (* x x) (* y y))))` the car of the car of the cdr of the cdr is `sqrt`. The Lisp structure is a binary tree, but the static syntax is formatted as an N-ary ordered tree. In PostScript, the N-ary ordered tree of the static syntax corresponds to the N-ary ordered tree in the PostScript data model. (Note that this is another way in which PostScript is like Lisp but unlike Forth: in Forths that expose their representation of threaded code, it’s an array of xts, not a tree.) There are *other* structures that get constructed dynamically at run time in PostScript, such as the implicit gsave/grestore tree, the call/return tree, the implicit begin/end tree, the implicit save/restore tree — basically anything with a stack could be said to dynamically construct a sort of tree structure at run time. >> 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. > > Only insofar as every dynamic language “mostly belongs to the Lisp > family”. PostScript is a lot insofarther, I think. For example, Python, JS, and Lua hit #1-5, but not #6-9, although they do have `eval`, so you can compile code at runtime. None of them let you run code at compile time or at read time. Perl4 is usually considered a dynamic language and doesn’t have references at all, so we lose #4; Perl5 has references, but distinguishes between the reference and the thing it refers to, so that you can take references to references, so we still lose #4, but we gain #9 because you can write things like Perligata. Tcl not only doesn’t have references, it doesn’t even have dynamic typing; everything is a string, but it sort of has #8 except that it doesn’t have a symbol type. > [...] >> the executable bit set by `cvx` is an attribute of references, not >> objects. > > The same is true of read and write access bits -- except for > dictionaries. I had no idea! > I remember doing some experiments with an Apple LaserWriter and a > Macintosh II back in the late 1980s. Someone had cracked the > encryption of the “eexec” operator, and we used this to discover that > there was this other thing called “cexec” (only allowed within > encrypted “eexec” execution, which is why you never saw it in public > code) that let you load MC68000 machine code into the printer and hook > into an API provided by the PostScript interpreter to manipulate > PostScript objects and make function calls. Wow, that’s amazing! I only ever knew that there was some controversy about extracting fonts. Maybe this is how it was done? > [...] The Macintosh compilers already generated MC68000 machine code, > and I was able to whip up build scripts to link that code into the > right format, with appropriately-set-up header fields, so that the > printer would load and execute it. What a great feeling! Kragen