Love letter to catlangs

There is a strong connection between concatenative programming languages and linear logic, which I’m not the first to raise, but I wanted to put it down in my own words.

In classical logic, bindings are inexhaustible, if a formula proves something from x, you can use x ten times, or zero times, it doesn’t matter. But programming doesn’t work like that, resources do matter. I don’t say this to mean that copying a register is costing cycles, but that semantically speaking, closing a file or freeing memory should consume the access to that resource.

In catlangs, bindings are fuel, if you want two copies, you must duplicate. It becomes an operation, not a ambient assumption. Jean-Yves Girard wrote extensively about this, and his insight was that this linear logic was closer to reality, it makes logic reflect process. In other words, traditional logic is static and he wanted a logic of change.

I see Forth thrown left and right around permacomputing circles on vague notions of efficiency and human-scaleness, but I think what lies beneath these intuitions is that classical logic assumes infinite copyability. Which is unrealistic for memory, energy and just about any physical system. Stack machines expose the structural rules that classical logic hides, duplication and erasure are explicit instructions. This conservation law aligns logic with a finite natural world.

Programming languages typically hide duplication and lifetimes, or tack helpers on top as an afterthought. Values duplicate freely, things exist everywhere at once, names abstract away placement, this may activate one’s linguistic thought process but keep the spatial system asleep. My experience with catlang has had less to do with fussing with names and symbols and more to do with something like weaving. On this loom, things don’t have names but occupy spaces in a braid over time. If I had to guess, I’d say that probably triggers the same geometrically thinking part of the brain that tracks physical objects.

And that’s the unique bit about catlangs.

5 Likes

That’s useful insight, thanks! And it fits my (by now a bit weak) memories of Forth programming on my 8-bit home computer in the 1980s. The stack was working memory, easily visualized with paper and pencil.

However, when I moved on to more powerful machines, not having to deal with resource management felt as a significant liberation. My dream has always been a programming system that lets me move back and forth between automated and manual resource management.

And now I am wondering if there are development environments for catlangs that address this question. Something like a small-scale compiler that translates a managed language to a catlang, generating legible (and possibly annotated) code for programmers to study and fine-tune. The managed language could of course be a catlang as well, but with additional elements such as named variables.

1 Like

There are a couple of algol-looking things that compile down to stack machines, albeit somewhat inefficiently, but I think one space to look at is interaction nets, as they share a lot in common with pure stack machines. INets are represented as drawings usually, but internally they are a stack of connection between ports in a graph. I would love a beastiary of functions in inets so that I could use that as a base for catlangs implementations!

2 Likes

I’m drawn to riff on some of the concepts in the post by a contrast of opposites, like:

  • Linear logic (sequences) ↔ Non-linear logic (jumps, loops)
  • Geometrical or spatial thinking (images) ↔ Linguistic thought process (words)
  • Illusion of infinite time, memory, and other computational resources ↔ Working with limits, boundaries, the usefulness of constraints
  • Abstraction as hiding of reality and details ↔ Revealing inner processes and material
  • Names and symbols (references and representations) ↔ Values and operations (referents, the thing or process being represented)

If signs can be used to tell the truth, they can also be used to lie. – Umberto Eco

But I’m unfamiliar with catlangs, other than introductory knowledge like in this talk at Strange Loop.

Where Uxn is mentioned in the latter part as an example of a versatile “readable” assembly language. My interest in Forth, postfix notation (“reverse Polish”), and stack-based languages was renewed recently, as a kind of surprise. I’ve been pursuing an idea of a low-level Lisp dialect that compiles to WebAssembly, trying from various angles and levels of abstraction - written in C, Scheme, Python, TypeScript, then eventually raw Wasm text format.

Hand-writing assembly code is oddly comfortable after a while. There’s just a big array
of bytes as linear memory, and a handful of instructions to operate on numbers. The rest of the universe is up to you. To get fluent, I’m reading other people’s hand-written assembly programs - some are truly a kind of literature.

Like this Ichigo Lisp, a LISP 1.5 interpreter by a Japanese author. “Ichigo” いちご means strawberry, :strawberry:.

It even includes a just-in-time compiler. Or the Ribbit Scheme virtual machine written in Wasm. :frog:

WebAssembly code looks like Lisp, a tree structure of symbolic expressions, but that’s only syntactic sugar for ease of reading and writing.

Folded Instructions - Instructions can be written as S-expressions by grouping them into folded form. In that notation, an instruction is wrapped in parentheses and optionally includes nested folded instructions to indicate its operands.

The expression (* (+ x 2) 3) in folded form:

(𝚒𝟹𝟸.𝚖𝚞𝚕
  (𝚒𝟹𝟸.𝚊𝚍𝚍
    (𝚕𝚘𝚌𝚊𝚕.𝚐𝚎𝚝 $𝚡)
    (𝚒𝟹𝟸.𝚌𝚘𝚗𝚜𝚝 𝟸))
  (𝚒𝟹𝟸.𝚌𝚘𝚗𝚜𝚝 𝟹))

It’s unfolded to a stack-based linear stream of instructions, which directly corresponds to the binary format.

𝚕𝚘𝚌𝚊𝚕.𝚐𝚎𝚝 $𝚡
𝚒𝟹𝟸.𝚌𝚘𝚗𝚜𝚝 𝟸
𝚒𝟹𝟸.𝚊𝚍𝚍
𝚒𝟹𝟸.𝚌𝚘𝚗𝚜𝚝 𝟹
𝚒𝟹𝟸.𝚖𝚞𝚕

Even in this simple example - or maybe because of its simplicity, it’s easier to see the difference - the true nature of the computation happening is revealed in its catlang form. The Lisp syntax offers an illusion for convenience, from the first step: an action (multiply) that “waits” for its arguments to be evaluated and passed; but the actual first action that happens is to push a value from x on the stack.

In script/infix form, (x + 2) * 3, the evaluation order goes like this, step by step:

(      ; Start expression - Defines precedence of add operation over multiply
  x    ; Step #1 <-- Actual first thing evaluated
  +    ; Step #3 Add..
  2    ; Step #2 <-- Next thing evaluated
)      ; Push result to stack
*      ; Step #5 Multiply..
3      ; Step #4 <-- Evaluated and passed to multiply 

In list/folded form:

(*     ; Step #5 Multiply..
  (+   ; Step #3 Add..
    x  ; Step #1 <-- Actual first thing evaluated
    2) ; Step #2 <-- Next thing evaluated
  3)   ; Step #4

In :cat_face: form:

x      ; Step #1 Push value to stack
2      ; Step #2 Push value to stack
add    ; Step #3 Add (and push result to stack)
3      ; Step #4 Push value to stack
mul    ; Step #5 Multiply

Today, before I started this comment, I’d been listening to this talk:

And I happened to pause it on a slide that says:

Embrace non-linear editing

Pure functional programming (programming without state) complements non-linear editing because, without state, one need not manage time.

I wonder how it relates, but in the comparison I made above of how an expression in script/lisp/catlang syntax is evaluated in order, the stack-based concatenative notation seems to map most directly to the actual computation happening; with the most straight-forward sequential logic in a linear stream of instructions; with the least number of “things to keep in your head” while evaluating the steps.


WAForth: Forth Interpreter and Compiler for WebAssembly.

This is another example of hand-written WebAssembly of non-trivial scope.

There’s a lot to love about the project, including Thurtle for turtle graphics. :turtle:

Looking at this code though, maybe something like LOGO is more intuitive for teaching programming. The first line:

450 CONSTANT SIZE

For anyone unfamiliar with how it works, it probably feels too sudden to start with the value 450. The “size” is the main character of the line, and 450 is only a specific point in the range of values. If it were written in natural language, it might flow better as:

const size = 450
  • The constant (adjective)
  • “size” (noun)
  • is (verb)
  • 450 (quantity).

But then again, this subjective naturalness of grammar may depend on a person’s mother tongue or primary language. In anthropology I’ve heard of languages with no nouns, where all forms are temporary states in the eternal flux: there is no table, it is only table-ing. That describes the situation more accurately than nouns and objects with an implicit assumption, for what is a “constant” but an illusion of infinite time.

Maybe in some human languages the speaker is expected to “get to the point” and say the specific value first. 450 is the size of the plant.7 branches. 160 degrees spread.


About illusion of infinite memory, what comes to mind is garbage collection and how it frees programs from worrying about managing memory - efficiently allocating, freeing, and defragmenting space. Somewhat related educational articles by the same author:

That Scheme to Wasm compiler is getting closer to what I wanted, but it’s based on WasmGC (WebAssembly garbage collection) feature, that’s relatively new and not part of Wasm v1, the original simpler spec I’ve decided to use as a stable pillar, similar in role to C99, for its ease of implementation and wide availability/compatibility with runtimes. In any case this particular Scheme is still too high-level and dynamic, I want a language that can be slightly above assembly, so it needs static typing - in Wasm v1 there are only 4 types of values (integers i32/i64 and floats f32/f64) - and manual memory management, no garbage collection, with possible benefit for use cases like audio processing.

Then I found Pre-Scheme, “a statically-typed dialect of Scheme with the efficiency and low-level machine access of C”. Very interesting, but doesn’t quite fit my use case.

Whether a language compiles to Wasm, or x86/ARM/RISC-V instruction set architectures, what’s left after removing the abstractions is a stack-based machine, for which a concatenative language is a direct mapping and representation of how it works. But I confess my true longing, and what’s emerging from my explorations, is a Scheme-like abstract language to bridge between languages and data formats, from low-level assembly code to higher abstractions like names, lists, strings - to describe the spectrum of data and code, documents and programs.

What I’m learning is that being able to deterministically “fold” into a Lisp-like tree, and “unfold” into a Forth-like linear stream of instructions, is useful not just in terms of data structure and algorithm, but as part of language design and syntax, conversion/translation, interpreting and compiling programs.

Another area where this is relevant for me, is that I’ve been exploring tree-sitter, a parser generator library that supports dozens of languages in a uniform interface. Each grammar is compiled to a C program (therefore can be built as Wasm binary) that parses source code of the given language to a concrete syntax tree.

It can incrementally parse syntactically incomplete/invalid programs, which is useful for integrating with code/text editors. By design the parser emits a linear stream of events, which for most purposes is the best way to efficiently process the syntax tree part by part, creating and discarding state, instead of keeping the entire tree in memory. A similar contrast exists between immediate-mode graphics, that creates and destroys the universe on every moment (“tick” or frame); and retained-mode graphics, which builds the entire tree at the start and mutates its state over the course of the running program. It’s simpler to manage a single immutable moment every time (now), than having to keep up the illusion of a stable identity over time.

It feels like every language and data format can be parsed into a Lisp-like tree (or graph), then flattened and unfolded into a linear cat-lang form as needed. Maybe these ways of structuring an expression - grammar or dialect - are computationally equivalent, as a sentence spoken in different languages can have the same meaning - or similar enough for practical purpose, where the conversion to and from syntaxes are lossless in terms of information.


Let me illustrate this with a recent example, involving my work on “metabiology” that models biological evolution by studying the evolution of mutating software. I am attempting to unify theoretical biology and theoretical computer science based on the ansatz that DNA is digital software.

And having set out on this path, I immediately discover that Stephen’s NKS is highly relevant. In particular, I think that the discussion of the Cambrian explosion in Stephen Jay Gould’s Wonderful Life is illuminated by Wolfram’s metaphor of mining the computational universe, and that the mysterious origin of life, which is actually the spontaneous discovery of software by Nature, is illuminated by Wolfram’s thesis that it is easy to build a universal Turing machine out of almost any discrete combinatorial components.

– From Irreducibility and Computational Equivalence: 10 Years After Wolfram’s A New Kind of Science, in the book series Emergence, Complexity and Computation.


Commercially available memories are available only in finite sizes (more’s the pity).. In order to make such memories useable to our processor we must interpose between EVAL and the storage system a storage manager which makes a finite vector memory appear to the evaluation mechanism to be an infinite linked-record memory.. The catch is that at no time may more records be active than will fit into the finite memory actually provided. The memory is “apparently infinite” in the sense that an indefinitely large number of new records can be “created” using the CONS operator. The storage manager recycles discarded records in order to create new ones in a manner completely invisible to the evaluator.

..A final philosophical thought: it may be worth considering kinds of “stuff” other than vectors and linked records to use for representing data. For example, in LISP we generally organize the records only into trees rather than general graphs. Other storage organizations should also be explored. The crucial idea, however, is that the instruction set should then be fit into the new storage structure in some natural and interesting way, thereby representing programs in terms of the data structures. Continuing the one example, we might look for an evaluation mechanism on general graphs rather than on trees, or on whatever other storage structure we choose.

Finally, the instruction set, besides being represented in terms of the data structures, must include means for manipulating those structures. Just as the usual computer has ADD and AND; just as the LISP architecture presented here must supply CAR, CDR, and CONS; so a graph architecture must provide graph manipulation primitives, etc. Following this paradigm we may discover yet other interesting architectures and interpretation mechanisms.

– Design of LISP-Based Processors - LAMBDA: The Ultimate Opcode
https://research.scheme.org/lambda-papers/lambda-papers-ltu-opcode.html


..A portable low-level bytecode called WebAssembly. It offers compact representation, efficient validation and compilation, and safe low to no-overhead execution. Rather than committing to a specific programming model, WebAssembly is an abstraction over modern hardware, making it language-, hardware-, and platform-independent, with use cases beyond just the Web.

..WebAssembly computation is based on a stack machine; code for a function consists of a sequence of instructions that manipulate values on an implicit operand stack, popping argument values and pushing result values. However, thanks to the type system, the layout of the operand stack can be statically determined at any point in the code, so that actual implementations can compile the data flow between instructions directly without ever materializing the operand stack. The stack organization is merely a way to achieve a compact program representation, as it has been shown to be smaller than a register machine.

..The stack layout is fixed (branches must push and pop the same number of operands), so Wasm engines can compile the stack away, producing efficient machine code operations on registers, rather than managing a stack at runtime. A stack machine architecture was chosen as it can have higher code density than a register machine.

– Bringing the web up to speed with WebAssembly
https://dl.acm.org/doi/10.1145/3062341.3062363


Well, while it was fun to write, this comment doesn’t do justice to the topic of catlangs. I wanted to describe how my philosophical love of Lisp as a universal language of computation is enriched and challenged by its encounter with assembly language and stack-based machine instructions - and, indirectly, how a concatenative paradigm like Forth seems better suited for expressing the actual concrete computational process happening at the level of the machine.

In a concatenative programming language, things are evaluated by composing several functions which all operate on a single piece of data, passed from function to function. This piece of data is usually in the form of a stack. Additionally, this function composition is indicated by concatenating programs. Examples of concatenative languages include Forth, Joy, PostScript, Cat, and Factor.

Although the terms stack language and concatenative language sometimes get thrown around interchangeably, they actually represent similar but distinct classes of languages. A concatenative language is not necessarily a stack language. For example, Om uses prefix notation, rather than postfix, and passes the remainder of the program as the data from function to function. See Deque for another example of a stack-free concatenative language.

concatenative.org/wiki

2 Likes

Lisp is famously unfit to express the computation steps happening( LISP programmers know the value of everything and the cost of nothing, yadda yadda..), but it’s also sometimes its beauty, it’s a good abstraction when you don’t need to fuss with the irrelevant.

Baker, who was old school Symbolics and wrote the paper I linked above about Forth, did some exploration within Lisp about Linear memory.

I understand that it won’t be as elegant as forth, since you have clojures and bindings in the way, but it gives a playground to at least experiment with linear logic-like behaviors in the comfort of Lisp, it might be interesting to you : )

3 Likes

Do you have citations? I can see the truth in these but have never encountered these criticisms.

I’m weary about answering this because this might look like a put down, which I didn’t want to put catlangs vs high level languages. There’s space for both. Speed is not the end all goal for everyone writing programs, and understandability of state transformation might not even be on the roadmap of languages that lean heavily on GC.

In Lisp it is very easy to write programs that perform very poorly; in C it is almost impossible to do that.

I think that quote by Gabriel reveal some insight about the nature of the computer distance of the two languages, and if we consider that Forth is even further down to the side of C, as it’s often called an assembly language. But first, a word in favor of Lisp not being close to the metal, I think The Text that somewhat dives into this aspect deeper than other sources while remaining favorable to Lisp is Gabriel’s The Good & The Bad. It admits that Lisp is more disconnected to the mechanics of the host, but that it’s not the only vector to consider, like memory safety and correctness. Here’s Erik Naggum hammering down that point:

People use C because it feels faster. Like, if you build a catapult strong enough that it can hurl a bathtub with someone crouching inside it from London to New York, it will feel very fast both on take-off and landing, and probably during the ride, too, while a comfortable seat in business class on a transatlantic airliner would probably take less time but you would not feel the speed nearly as much.

More concretely tho, a while back, Autodesk made this thing which explicitly puts equivalent lisp against forth programs(autolisp vs auto..forth?) against each other:

It is small. A minimal implementation of FORTH is a tiny thing indeed, since most of the language can be defined in itself, using only a small number of fundamental primitives. Even a rich implementation, with extensions such as floating point and mathematical functions, strings, file I/O, compiler writing facilities, user-defined objects, arrays, debugging tools, and runtime instrumentation, is still on the order of one fifth the number of source lines of a Lisp interpreter with far fewer built-in functions, and occupies less than of 70% the object code size. Runtime data memory requirements are a tiny fraction (often one or two percent) of those required by Lisp, and frequently substantially less that compiled languages such as C. It’s kind of startling to discover that an entire interpretive and compiled language, including floating point, all the math functions of C, file I/O, strings, etc., could be built, in the original 32-bit version, into a MS-DOS executable of 50964 bytes.

It is fast. Because it is a threaded language, execution of programs consists not of source level interpretation but simple memory loads and indirect jumps. Even for compute-bound code, the speed penalty compared to true compilers is often in the range of 5 to 8. While this may seem a serious price to pay, bear in mind that tokenising Lisp interpreters often exhibit speed penalties of between 60 and 70 to 1 on similar code, and source-level interpreters, such as the macro languages found in many application programs, are often much, much worse than that. In most programs, the execution speed of FORTH and compiled code will be essentially identical, particularly when FORTH is used largely in the role of a macro language, calling primitives within an application coded in a compiled language.

If you want to dig even deeper into that faff, there’s always c2.

3 Likes

I’ve certainly seen many criticisms of Lisp performance, and it’s not news that it occupies a point in the space of trade-offs that is different from Forth or C. But what was new and interesting to me in your previous comment was the gesture at the effect it has on the programmer.

LISP programmers know the value of everything and the cost of nothing, yadda yadda..

So yeah, I don’t care to debate it. I just like to collect points from all perspectives.

Ah! In that case, I might have something to add.

So, there is something that exists at the threshold of Lisp and the mechanical nature of linear logic which I haven’t mentioned in the thread, but we’re going off in the weeds with this.

So let’s say that you can settle for a subset of lambda calculus that doesn’t allow for re-use of bindings

This forms the basis of the interaction net evaluation which is HIGHLY mechanical, and satisfies linear logic, and maps rather well to sillicon; and so if I had to guess, it might be where the future of functional programming lies, this is my own speculation and I don’t have a citation for this one.

By the way, that Lisp quote is from Perlis. : )

3 Likes

LISP programmers know the value of everything and the cost of nothing.

There’s a joke in there about “value”, because in LISP everything is an expression that returns a value; and the cost of evaluating some operations are hidden from the programmer.

That hiding is the abstraction that is Lisp, and I think in my attempt to make a low-level Lisp-like language that’s close to assembly, I unexpectedly found some limitations of the Lisp paradigm itself. It’s not so suitable for directly expressing what’s happening at the level of the machine, because the machine is not designed that way. There’s a mismatch between the language and reality. Lisp creates values out of thin air as if there was infinite space; tail recursion is implicitly transformed into a linear stream; etc.

Here’s some interesting code I read - it’s the Uxn stack-based virtual machine written in WebAssembly.

Wasm doesn’t have GOTO, only structured blocks and loops - which has certain advantages but makes it difficult to express things like jumps and other non-linear control flows. The ingenious solution in the above VM was to create blocks/labels for all available machine instructions, so at every step it can effectively move the program counter (instruction pointer) and go anywhere. It’s so smart how that works. I think the same idea can be used for a Scheme runtime to implement tail-call optimization, continuation, threads, and so on.

I suppose the need for a runtime is a reason why the Lisp paradigm doesn’t fit well for a low-level assembly language. In contrast I imagine Forth and maybe concatenative languages in general can compile to machine code with no runtime, even the stack is apparently a convenience that can be compiled away to nothing. (Which means that’s an illusion too.)

A story I heard about Forth is, if you ever find yourself in a situation with a bare machine and no languages, a minimal Forth is the simplest language to write in assembly, to then bootstrap yourself to write a Lisp interpreter or small subset of C compiler, then gradually compile your way to the modern tool stack and mainstream languages.


That quote by Alan Perlis is from his “Epigrams in Programming”.

It’s an entertaining read with pithy one-liners.

A programming language is low level when its programs require attention to the irrelevant.

To understand a program you must become both the machine and the program.

There will always be things we wish to say in our programs that in all known languages can only be said poorly.

The most important computer is the one that rages in our skulls and ever seeks that satisfactory external emulator.


For starting to learn about catlangs, this site has a wealth of good information.

It has a page on Uxntal.

That reminds me, a fun resource for learning languages is Learn X in Y Minutes. Where X=Uxntal: https://learnxinyminutes.com/uxntal/. It’s a wonderful code example that demonstrates aspects of the stack-based language.

2 Likes

Idris 2 mixes constructive logic with quantities.

Sometimes we need linear logic, in other times we need non-linear logic.

For a theorem that is always true , we need to be able to use it an infinite amount of times.
That same theorem might not even exist at runtime because it does not affect the computation, only its correctness.

Personally, I would separate the implementation from the logic on top of it.

And again, we should be able to switch between levels of abstractions and between runtimes , this is the main theme that many have agreed on.

Different abstractions lead to different types of thinking, a type of neurodivergence that is very useful.

2 Likes

On another note, there are optimizations that are not easy to understand. So they are better to be done mechanically by the compiler.

Probably a human would never be able to do them.

So it is not apparent whether we should have a low level language or the opposite. We should have a choice.

Why Concatenative Programming Matters by Jon Purdy. It’s an insightful article that deepened my appreciation and understanding of the charm of catlangs.

  • Concatenative languages are simple and consistent (“everything is a”)
  • They are amenable to dataflow programming
  • Stack languages are well studied and have good performance
  • You can easily roll your own control structures
  • Everything is written in point-free style (for better or worse)

One of the great strengths of concatenative languages.. is their ability to refactor complex expressions. Because every sequence of terms is just a function, you can directly pull out commonly used code into its own function, without even needing to rewrite anything. There are generally no variables to rename, nor state to manage.

His catlang kitten, written in Haskell.

Kitten: A statically typed concatenative systems programming language

From its starter tutorial:

For the sake of familiarity, and to improve the ability to share code with other languages, Kitten uses infix for operators and postfix for function calls.

..Kitten is both functional and stack-based. These two seemingly opposing features are reconciled by a simple notion: effects. The only side effect of the + operator is to pop two values from the stack and push their sum, and it always produces the same output given the same inputs—it is a pure function from one stack state to another.

..We can reason about these imperative stack procedures as pure mathematical functions, just like in a functional programming language, but since there’s only ever one stack “live” at a given time, we can update it in-place for efficiency. This combination of imperative style and functional reasoning is called concatenative programming, and it’s a powerful way of thinking about programs.


He also wrote a short blog post titled, What’s Wrong with Lisp.

Quoting below to see if I understand what he means - not necessarily to agree or disagree.

..In short, minimalism = perfection, and Lisp (well, Scheme anyway) favours it: uniform syntax, a few special forms, and then it’s turtles all the way up.

But even though I’d be the first to say that there’s a lot to take away from a thorough study of Lisp, there’s also a lot that could be taken away from Lisp itself, and for that reason I can only recommend it as an intermediate step in a programmer’s journey to linguistic nirvana.

I advise Lisp programmers to .. look through the weird, the esoteric, and the insane, to reach the power of what lies beyond. Question your assumptions.

Why the oh-so-important parentheses? Simple: nesting! So why do you need nesting? To create scope. Why do you need scope? Variables, of course. So why do you need variables? To store values, obviously. What is the purpose of a value? To model state. Why do you need state? You don’t.

If you model everything in terms of data flow, for example, you no longer need state or any of the other things that come with it. You can write your program solely in terms of function composition. You don’t even need scoping (lambda abstraction) or application at all.

It’s like peeling away the onion of abstractions, destructuring the language until there’s nothing left but the real primitives of computation.

The point is that lambda calculus is only the beginning. It is (for lack of a better term) the assembly language of functional programming..

Let us take more away, to reveal what more is yet to be.

I like how he calls lambda calculus a kind of “assembly language”, which is the direction I was getting pulled toward. But I thought his point was that it’s still a layer of abstraction, that there’s an even lower-level language that’s more powerful and expressive. In the comment section he replies to someone:

Forth and its relatives (such as Factor) come very close to what I consider ideal in a minimalist language. But the point of this post is not to name a single language or family, but rather to inspire critical thought about languages.

2 Likes

The reasoning looks OK if “linguistic nirvana” is the goal. It isn’t mine though. I write programs to make a computer do things for me, and to communicate these automated actions to fellow humans. It’s in particular the second part that is probably in conflict with anybody’s linguistic nirvana.

1 Like

programmer’s journey to linguistic nirvana

Well, those are his words, as provocative as the title, like: “What’s wrong with your language.” At one point he paraphrases Paul Graham about expressivity of languages, that some languages are more powerful than others. That’s true but I don’t see it as a hierarchy, closer or further to some ideal “maximum power”. That’s not the goal or purpose of a language, unless we’re using the language to express the ego like a sports car. The author makes it sound like LISP is not quite there, only an “intermediate step” toward something better, a language to be grown out of. That attitude is common among programmers, to look down on someone using an old working-class language. The other day someone seriously said, “Rust is the final language.” I respect what the language and community are achieving, but it’s cringe to hear an evangelist of the sect - because as one matures, we gain self-reflection and humility that no one has the complete answer, we’re all seekers on the path.

nirvana

It’s an unintended irony to use this term, meaning “extinguished” like a candle, becoming free of desires and worldly attachments. In various subjects including computer science I noticed my learning journey is marked by at first falling in love with a system, paradigm or concept; reading everything about it to learn and re-live its history and developments; and eventually coming to the realization that it’s not the utopian perfection I’d imagined it to be. LISP and lambda calculus is one of those platonic loves, as is Pythagorus’ harmony of the spheres, classical physics, or classical music. They’re conceptually beautiful and mind-opening, sometimes it feels like profound insight into the nature of reality.

The Pythagorean comma shown as the gap (on the right side) which causes a 12-pointed star to fail to close, which star represents the Pythagorean scale; each line representing a just perfect fifth.

This comma was one of my earliest intellectual heart-breaks when I was a teenager, I still haven’t gotten over it. I really wanted music theory to be an embodiment of the perfection of the universe, singing eternal harmonies of numbers and geometry. That gap where the star fails to close, it hurts - I mean, what kind of cruel god would create a world where there’s always a pebble in the shoe!

The phrase in modern art, “emancipation of dissonance” hits hard because it’s the kind of intellectual disillusionment I’ve experienced again and again from different angles. It seems my mind is drawn to seek crystalline perfection and eternal truths, when the real world is too messy for such beauty to exist for long, if it was even there at all and not a figment of imagination. That illusion of perfection is shattered by a fundamentally irresolvable “mistake” in the system - the ditonic comma, Gödel’s Incompleteness Theorems, Heisenberg’s Uncertainty Principle, indeterminacy, non-Euclidean geometry, chaos theory and fractals (from the Latin fractus, meaning “broken” or “fractured”)..

That is to say, each time, I learn that the search for nirvāṇa is itself saṃsāra, a running around in circles. I’d like to think it’s rather a spiral, gaining knowledge and experience as I evolve - but even now, as I go around, my mind asks: what is it I’m revolving around, what is that unmoving center, could that be it? Like catching eternity in the act - but when I open my hand, there’s nothing there. Tomorrow I’ll forget my broken heart, and start searching for her again. That’s what it’s like to be a utopian and a dreamer, a seeker of truth, and a fool for love as the poet Rūmī would say. I’ve been humbled, and I’m OK knowing that I’ll never have the Truth in its entirety. Of course it doesn’t fit in my primate brain. But that fleeting experience of eternity is thrilling, it really is love, flowing like wine in this material vessel. It makes my heart beat thinking of it. There’s a grand mystery out there, the mother of all things, pulling me toward it - and one day I’ll simply surrender my self and become none with it.

2 Likes

look through the weird, the esoteric..

K and other array languages. How do they relate to catlangs, I don’t know, if they relate at all.

From: https://ktye.github.io/

A language that fits in a postcard, that’s satisfying to see. Such terse Haiku-like programs, even a C compiler written in it. Well, it’s a rabbit hole I’ll explore another time..

2 Likes

I believe array languages often use a stack model as well, either explicitly for the programmer to reason about or internally as a semantic detail.

I was recently reading about Uiua (also mentioned on the forum a few months back), which was initially stack-focused but has since deprecated that in favor of their “modifiers”.

APL Wiki is a good resource for comparing various array languages (seems to be an array lang community resource covering more than just APL), including which are stack-based.

3 Likes

OMG, that Turbo APL screenshot! Looks like it’s just fanfic. We’re not in the right timeline.

One commonality between array languages and concatenative is the point free nature. Some of them achieve it not with a stack but by hard coding arg names.

4 Likes

That screenshot is from a real working “Turbo K” editor, where one can compile K and even “save” (export) a Wasm binary file.

Under “Help” (Alt + H) there’s a tiny C parser/compiler, assembly interpreter, etc., in hand-written JavaScript in an “obfuscated” style like a machine or an extraterrestrial wrote it.

The interface feels familiar. One of the first programming languages I learned as a child was Turbo Pascal, then soon after Turbo C. It was a true integrated environment with editor, reference manual, debugger, helpful error messages, links within the code. (“Code is hypertext, IDE is the browser.”) As an exercise, my dad gave me a months-long task to translate a huge program from English to Japanese, I remember printing out pages of code and reading it, making notes with a pencil. Gee I sound like an old-timer, haha. Returning after years to re-learn C and assembly language (8086 then, Wasm now) feels nostalgic too. Not sure how practical it is, with the trend of language models generating more and more code, hand-writing anything is getting rare.

<rant> What’s worse is the cognitive dependency of letting the machine think. The equivalent of hand writing for the mind, the manual process of thinking with the old-school tool that is the brain, is starting to get replaced by automated thinking. There will be an inevitable cultural renaissance, or at least “what’s old is new again” of making things by hand. And thinking your own thoughts all by yourself just with your brain, imagine - no laptop or mobile, no in-ear microphone or skull-embedded chip, no internet connection even (audience gasps), just an off-grid self-hosted local-first neural network, the raw unmediated biological brain.

As human society is swallowed up into the silicon uncanny valley (rest in peace Jim Carrey), people will re-learn to appreciate the natural authenticity of the body interfacing with the physical material world. But it will be a world made anew, infused with electronics, robotics and intelligence, a seamless merging of technology, human beings, and the natural environment. Future children will grow up in a planet-scale living computer, learn the literacy of writing in the assembly language of life, thinking superhuman thoughts in an integrated cybermental space. It seems possible by extrapolating from the concept of IDE (integrated development environment), from programming computers to programming the self, society, and the physical world. </rant>

The other day someone was talking about OMNI Magazine from the 1980-90’s, what I’d call now a retro-futuristic mix of science fiction and new-agey thinking (echoes of a lost era of real magic), like the first issue included: a story by Isaac Asimov; an interview with Freeman Dyson; an article titled “Aging: Some of Us May Never Die”; and about Microworlds. I love that word, Seymour Papert used that term when talking about LOGO, another language I learned as a child.

Asimov warns about the implications of science and technology applied to society, a warning we apparently forgot about in the ensuing decades. Remember the Laws of Robotics?

“Pepperidge Farm Remembers” is a popular internet meme that originated from nostalgic commercials featuring an elderly man reminiscing about the past, often humorously highlighting outdated practices or items.


Found the source code of the Turbo K editor.

That Git repository is a wealth of dense programming genius, various implementations of minimal languages, parsers, compilers, assemblers. Lots of fascinating code, like:

C:0x
D:0x
T:`prg`fun`arg`sym`res`loc`sym`ast`asn`add`lit`get`ret`get`fun`arg`sym`res`loc`sym`ast`drp`cal`get`asn`cal`get`ret`get
P:0 0 1 2 1 1 5 1 7 8 9 9 7 12 0 14 15 14 14 18 14 20 21 22 20 24 25 20 27
I:0 0 0 0 0 0 0 0 0 2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
S:`asn`f`i`x`i`i`r``r`i`i`x`i`r`h`i`x`i`i`r```f`x`r`f`x`i`r

Files with names like a.h, b.c (I think b is another language), 86.js (apparently a CPU emulator in 200 lines of code).

There’s a file tuicss.min.css, which leads me to:

I believe the layout and keyboard shortcuts of this text-based user interface are related to:

The detailed CUA specification, published in December 1987, has similarities to Apple Computer’s human interface guidelines. Used originally in OS/2 and Microsoft Windows, parts of the CUA standard are now implemented in programs for other operating systems, including variants of Unix.

The Apple HIG is a book specifying how software for the 1984 Macintosh computer should look and function. When it was first written, the Mac was new, and graphical user interface (GUI) software was a novelty, so Apple took great pains to ensure that programs would conform to a single shared look and feel.

CUA had a similar aim, but it faced the more difficult task of trying to impose this retroactively on an existing, thriving but chaotic industry, with the more ambitious goal of unifying all UI, from personal computers to minicomputers to mainframes; and supporting both character and GUI modes.

2 Likes

Is Japanese a concatenative language? To compare, a sentence in English:

I     ; subject
eat   ; verb
the   ; definite article
cake  ; object

The syntactic structure corresponds, roughly, to an infix notation like const size = 45. The object is at the end, the thing or value on which an action is operating. Written in a script syntax, it might look like:

I->eat(the: cake)

The I is the class, eat is the method, the is the type (definite/specific, not indefinite a for any), and cake is the value. No wonder object-oriented programming is so popular among English speakers. (Joking of course.)

The same sentence in Japanese looks like this.

ケーキ  ; "keeki" - object (the cake)
を     ; "wo" - objective particle to relate verb and direct object
食べる  ; "taberu" - verb (to eat)

The lack of I is common in Japanese grammar, it’s considered somewhat rude to insist on the self as the main subject. The above structure corresponds, roughly, to a stack-based postfix notation, like 45 const size, or:

cake ; push value to stack
wo   ; modifier
eat  ; operate on the value

The pattern is object + verb, like Forth.

To make it a question, end with rising voice intotation, and typically drop the particle.

ケーキ、食べる? “Cake, eat?”

There is no you, as it’s considered somewhat rude to refer to someone’s self directly. No wonder functional programming is so popular among the Japanese (citation needed). I jest but I think there may be a loose correlation between linguistic regions - Asia langs (CJK+), Hispanofonía, La Francophonie, Slavic langs, etc. - and popularity of certain programming languages. Lisp and Scheme are well-loved in Japan, or the Ruby language created there, it seems to suit a certain way of thinking.

En español se dice..

Como   ; eat (first-person)
el     ; definite article
pastel ; cake (object)

No need for I here either, it’s implied by the tail of the verb. To make a question, same structure with rising intonation.

¿Comes el pastel? “Eat the cake?”

Let’s say this verb + object pattern is like Lisp, prefix notation.

(como
  (:el pastel))

Well, it’s an amusing thought.


Starting from the Wiki, I spent a week reading fascinating articles about APL, array languages, catlangs, and related topics near and far. I’d heard of the language long ago but this time I revisited it with new eyes, including re-reading the influential article “Notation as a Tool for Thought” by Ken Iverson who created APL.

Applied mathematics is largely concerned with the design and analysis of explicit procedures for calculating the exact or approximate values of various functions. Such explicit procedures are called algorithms or programs. Because an effective notation for the description of programs exhibits considerable syntactic structure, it is called a programming language.

– Ken Iverson in A Programming Language

There’s much to be learned from going back through the history of collective thought, to understand the context in which certain concepts were created and developed. Why and how we use “language” to perform computation and mathematical operations, and “notation” to build symbolic expressions as algorithms and process of thinking. How it relates to ideograms (idea + writing), pictograms, diagrams, writing systems, even reaching the realms of design and illustration, of symbols as abstractions for communicating ideas.

This 1975 demonstration of APL on an IBM teletype machine, I see it was an early prototype of a REPL (read-eval-print-loop) and terminal command-line interface.


BQN: Big Questions Notation - “Finally, an APL for your flying saucer”. It’s a modern language with similarities to Uiua.

“Haskell is basically uncompressed J.”

Dyalog delivers a world-class APL development environment

Many of the Dyalog YouTube videos are narrated by an orthodox Jewish person, which makes me wonder about Hebrew, its history and modern revival - its role in the Kabbalah, a mysticism and philosophy of language, not only as an expression of thought but a medium with which to explore the relationship between the human and the divine.


An agglutinative language is a type of language that primarily forms words by stringing together morphemes (word parts)—each typically representing a single grammatical meaning—without significant modification to their forms (agglutinations).

In such languages, affixes (prefixes, suffixes, infixes, or circumfixes) are added to a root word in a linear and systematic way, creating complex words that encode detailed grammatical information. This structure allows for a high degree of transparency, as the boundaries between morphemes are usually clear and their meanings consistent.

Fusional languages or inflected languages, distinguished from agglutinative languages by their tendency to use single inflectional morphemes to denote multiple grammatical, syntactic, or semantic features. For example, the Spanish verb comer (“to eat”) has a form comí (“I ate”) where just one suffix, -í, denotes the intersection of the active voice, the first person, the singular number, the indicative mood, and preterite (which is the combination of the past tense and perfective aspect), instead of having a separate affix for each feature.


Alan Perlis talks a lot about APL.

What attracted me to APL was a feeling that perhaps through it one might begin to acquire some of the dimensions in programming that we revere in natural language — some of the pleasures of composition; of saying things elegantly; of being brief, poetic, artistic, that makes our natural languages so precious to us. .. For me, in listening to Ken, at that moment, there came what I can only call a revelation.

When I first learned to program, everybody used assembly language. We programmed in machine code. Looking back at it now, I realize that something went out of programming when FORTRAN came in. What went out of programming was the pleasure, the frustration, the challenge of writing a program that was elegant and clever.

LISP has a very precious character, people can express programming ideas to other people in the language in which they program.

I find that programming APL is fun. It’s charming. It’s pleasant. I find that programming is no longer a chore, and one of the reasons it’s not is the fact that there are always so many choices available to me. Whereas, the people in structured programming tell me if you put enough structure in programs, everybody in the room here will write the same ALGOL program or PASCAL program. Thus, it’s going to be easier to read — but also dull.

If Shakespeare were alive today, he’d be a programmer, and he’d be writing one-liners in APL.


We can locate in the poetry and fictions of Borges every motif present in the language mystique of Kabbalists and gnostics: the image of the world as a concatenation of secret syllables, the notion of an absolute idiom or cosmic letter—alpha and aleph—which underlies the rent fabric of human tongues..

– George Steiner in After Babel: Aspects of Language and Translation

1 Like