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,
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.
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.
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.
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.
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`.
(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
PostScript has several other key similarities to Lisp. Referencing Paul >Graham's "What Made Lisp Different" <https://paulgraham.com/diff.html>:
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."
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."
5. Garbage-collection.
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,
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.
Forth hits #1, #3, and #9.
In Forth, neither variables nor values have types that are known to
the Forth system. The way to use that is that the programmer must
know the type, and that typically means always storing the same type
into a variable.
6. Programs composed of expressions. Not really, but closer than any of
the currently popular languages.
Forth is like Postscript here: Programs composed of words (operators
in Postscript), which work on values on the stack.
At the time that PostScript was designed, most Lisps did not have
macros; they still had fexprs.... PostScript first shipped in 01982,
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.
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
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.
?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.?
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.
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.
6. Programs composed of expressions. Not really, but closer than any
of the currently popular languages.
No distinction between ?statements? and ?expressions?.
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.
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.
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?.
[...]
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 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.
[...] 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.
Thank you for that wonderful exposition of PostScript, I'm almost tempt-
ed to write my very own P.S. interpreter, but that's going to have to
wait a better time, as I already have n+1 projects.
Best wishes, and happy coaxing Lawrence!
Lawrence D?Oliveiro <ldo@nz.invalid> 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.
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
<ast.Name object at 0x7f52bb870130>
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.
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.
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:
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.
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 `#<FUNCTION (LAMBDA ()) {534960EB}>` or
`#<procedure>`. But that?s still a string representation! And *most* languages don't have the ability to print out, for example, struct
literals.
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:
...
This is definitely more awkward than in most languages, but it
doesn?t seem prohibitive to me.
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`.
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?
(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.)
Forth is not strongly-typed. I?m not aware of any high-level dynamic
language that is not strongly-typed. At a bare minimum, you need to
know what value is a pointer and what isn?t, otherwise memory
management is not going to be a happy business.
| Sysop: | Jacob Catayoc |
|---|---|
| Location: | Pasay City, Metro Manila, Philippines |
| Users: | 4 |
| Nodes: | 4 (0 / 4) |
| Uptime: | 497099:23:32 |
| Calls: | 182 |
| Files: | 744 |
| D/L today: |
6 files (5,132K bytes) |
| Messages: | 73,551 |