This is an idea that I got from studying category theory.
Concatenative programming is just a first step in that direction.
I leave the details an an exercise for the reader.
One of the problems of the purely applicative approach is that you get a nice algebra for describing your results, including the sequence of effects, but reasoning about the resources spent by the computation becomes harder.
I might argue that laziness makes haskell something different than purely applicative.
It's not just pure values being passed around. It's a language built atop suspended self-caching invocable thunks getting knotted up and then firing off in chain reaction.
main = do
let
hello = foo world
world = bar hello
putStrLn $ show hello
putStrLn $ show world
where
foo v = [v]
bar v = length v
https://dspace.mit.edu/handle/1721.1/5753
The point is there's a closer connection between functions and gotos than you might imagine.
This is roughly the point Steele is making, in addition to his point that the stack pointer side effects are often unnecessary (i.e. tail jumps).
Implementing a stack pointer in hardware is merely a legacy hack: Before people figured out continuations they didn't know any better.
That's like saying "There's really no such thing as a 'chair' at the quantum level."
(Yes, I'm aware of PostScript... Seems to be one of the sole exceptions here... And, uh, PDF... of course...)
Forth is a wonderful way to build a high-ish level self-contained programming environment on very low-resource hardware, like a 8-bit MCU. But thinking in terns of the stack is useless mental gymnastics, this is something a machine should do instead.
Fortunately the PS language was very well documented. That made writing PS pretty straightforward, at least for the reasons I was using it. Curiously other concatenative languages have been harder for me to grasp. Maybe that's because I regarded PS as a specific-use tool vs. a general purpose language.
If nothing else PS showed the value of excellent documentation. Lack of it probably accounts for many software project failures, particularly in the open-source world.
> Maybe that's because I regarded PS as a specific-use tool vs. a general purpose language.
In my opinion, it is both. Many of the programs I write in PostScript do not involve a printer at all.
> If nothing else PS showed the value of excellent documentation. Lack of it probably accounts for many software project failures, particularly in the open-source world.
I also find a problem with many programs that do not have good documentation. When I write my own, I try to provide documentation.
There are mental gymnastics but they are far from useless and not just thinking in terms of the stack. I don't quite have it down yet but I am getting closer and can see the sense in it, I haven't quite figured out how to plan and structure a program in Forth.
She gave me the dirtiest "are you mansplaining me" look.
Using the HP was perfectly natural for her as she had used cash registers.
I was working in sales/support/marketing for printer companies in the eighties and people were buying PostScript printers for the scalable fonts, device independence and compatibility. The most common objection was performance which was somewhat related to the programmability.
We used to supply the red and blue books with each printer but I don’t remember anybody breaking the seal on them.
Having said all that I have spent the last year writing a PostScript interpreter.
I’m not sure I’d call PDF concatenative, you certainly can’t cat two PDF files together to make a merged document. There are header and footer sections to deal with.
I do not agree. I think the mistake was using PostScript as the output document format, not its programmability. PostScript should be the input format and not the output format.
I think PDF has many problems and is badly designed in many ways, though.
> It is considered good functional programming style to write functions in point-free form, omitting the unnecessary mention of variables (points) on which the function operates.
This is wrong.
It may be considered good programming style in certain small use-cases when a small local composition is clearer. But as a general software engineering principle, the exact opposite is true: It is considered good style to break larger constructs into smaller pieces and give them meaningful names.
There's no reason that function parameters should be an exception to that and in practice, names for function parameters are often some of the most critical names for understanding a system.
I think concatenative languages are really cool as a mental exercise in getting a lot of power out of something simple. But as far as why they aren't used in practice. I think it's largely because they are a bad idea carried to its logical extreme.
The thing that makes Forth and other similar languages write-only is that you can easily lose track of the variables when you are forced to toss them all on a stack. When you add the ability to use local variables, you get some of that power back, but then you've added a huge impedance mismatch with the rest of the system.
As much as I hate all the () in Lisp, it's the source of much of its power.
There are two big differences for me:
1. If I look at Forth code I wrote a month ago I have no idea what it's doing. This is never a problem with Lisp.
2. I can -- and have -- written a complete Forth compiler bootstrapping my way up from assembler. Sounds like you've done the same thing. I could never bootstrap a Lisp compiler that way.
> If I look at Forth code I wrote a month ago I have no idea what it's doing.
Sometimes when we are learning a language, we plateau in our learning for so long we think we are done, however I assure you that other people can read Forth code written months or years ago, even written by other people, and can read it.
That's not you, at least not yet, and you should be cautious with your certainty about things you just don't know about.
Writing code is substantially easier than reading it, but reading is when it is possible for us to know the language.
> I could never bootstrap a Lisp compiler that way.
Don't be so hard on yourself: You can, but you would have to change and learn in order to do it.
In Forth, you read your word from left to right, "playing" stack effect in your head, and you always know where you're at processing wise.
Pretty simple, but I guess one must have inclinations towards thinking about the generated native code underneath rather than thinking in terms of symbols.
If you see write-only Forth programs, that's probably because they are poorly written, or maybe you are not familiar enough with Forth to read them.
> is that you can easily lose track of the variables when you are forced to toss them all on a stack
You don't "have" to keep everything on the stack in Forth any more that you "have" to allocate everything on the heap in C/C++.
A Forth programmer who is not a dilettante will look for the data which is used throughout the program (e.g. a file or device handle, a memory address) and put it in a (global) variable.
This eliminates most of the stack juggling and your definitions generally use no more than 2 arguments, so it's easy to tell what they do by looking at them.
I do. Maybe my ten thousand lines of Forth were barely readable. But not anymore. I have dozens of scripts meant to be throwaways, not something perfect and beautiful meant to published on Github, some of them I actually reused No Problem.
And I'm not even a professional Forth programmer; it only has been my go-to language for scripts, prototypes, utilities for a solid decade now.
Maybe your intuition tells you that it "has" to be write-only. Full disclosure: I became interested in this language because I thought there's no way it could be viable - forget about "write-only" and "readability", I was already over those fallacies at that time. But on the other hand, there was contrary evidence [1]. So I gave a honest and earnest shot at it.
What I learned, among other things, is that most of the work is about going against the habits and ways of thinking that you learned from mainstream languages. Of course it is not something one can learn by stagnating in the comfort zone of Algol++ languages.
when somebody's first answer to a problem/inquiry is "you're not good enough or else you would see the lights" it raises alarms in my head.
that said, my first steps in programming were forth (and hp RPL). so i got some familiarity, read code, wrote code, wrote basic interpreters too (for learning purpose). forth is cool and has its uses.
Local variables are also not necessary to have reasonable ergonomics, even in mathy contexts. Instead, the language can define special operators that access fields on the stack by type name and move or copy them to the top of the stack. For some reason, in the math example, the author deliberately added an unnecessary parameter that had to be dropped and put the arguments in the wrong order. Cleaning up the signature, there are several reasonable ways to solve the problem in a concatenative language with the features I've described.
type n number
type y n
type x n
def op_math_example [(y x) -> (n)] \\x * \\y \\y * + \y abs -
where \FOO moves a value of type FOO to the top of the stack and \\FOO copies the value to the top, leaving the original where it was. Yes, the \\ is a little ugly, but these should generally be used sparingly.Even if we restore the original signature and use the author's syntax, there is a much cleaner solution to the problem without the move/copy operators.
def sq dup *
def op_math_full drop swap sq over sq + swap abs -
This is extremely cryptic. I suspect most forth programmers would have added a stack signature comment, but it is even better if the compiler statically verifies the signature for us, which is how the language I am describing differs from vanilla forth (where stack comments also have the risk of being wrong in an evolving system). If we restore the type system, it becomes: type n number
type x n
type y n
type z n
def sq [(n) -> (n)] dup *
def op_math_impl [(y x) (n)] sq over sq + swap abs -
def op_math_full [(x y z) -> (n)] drop swap op_math_impl
Of course it is still not as obvious to the reader what op_math_full actually does as (y * y) + (x * x) - abs(y). But in practice it would have some name like projection_size so once it has been written correctly, it doesn't really matter that it is a bit obscure to read at a glance. You also get really good at simulating the stack in your head and they type signature makes this much easier. Still, when you are confused, you can always add stack debugging like so: def projection_size [(x y z) -> (n)] drop swap PRINT_STACK sq over sq + swap abs -
1 2 3 projection_size
and the output would be something like: ({ y 2 } { x 1 })
Writing programs in this style is very nice so long as you have enough tooling to facilitate it.I have only used concatenative languages that I have implemented myself, however. I have read quite a bit about forth, but for me, there are clear problems with vanilla forth that are solved for me with the meta operators like [, [[, \ and \\ as well as good debug tools like PRINT_STACK above. Forth was an incredible discovery for its time. We can do a lot better now.
dang•1d ago
Why concatenative programming matters (2012) - https://news.ycombinator.com/item?id=32124621 - July 2022 (55 comments)
Why Concatenative Programming Matters (2012) - https://news.ycombinator.com/item?id=25244260 - Nov 2020 (18 comments)
Why Concatenative Programming Matters (2012) - https://news.ycombinator.com/item?id=19665888 - April 2019 (33 comments)
Why Concatenative Programming Matters (2012) - https://news.ycombinator.com/item?id=5542695 - April 2013 (36 comments)
Why Concatenative Programming Matters - https://news.ycombinator.com/item?id=3582261 - Feb 2012 (40 comments)
7thaccount•1d ago
https://news.ycombinator.com/item?id=13345832
andsoitis•23h ago
Stem, an interpreted concatenative programing language - https://news.ycombinator.com/item?id=39151094 - Jan 2024 (74 comments)
A Simply Arrived Concatenative Language - https://news.ycombinator.com/item?id=15576215 - Oct 2017 (21 comments)
Factor: A Practical Stack Language - https://news.ycombinator.com/item?id=32215048 - Jul 2022 (45 comments)
Xs: a concatenative array language inspired by kdb+ and FORTH - https://news.ycombinator.com/item?id=23437003 - Jun 2020 (27 comments)
Cat: a statically typed concatenative language - https://news.ycombinator.com/item?id=24534755 - Sep 2020 (13 comments)
8th: a secure, cross-platform, concatenative programming language - https://news.ycombinator.com/item?id=15672361 - Nov 2017 (59 comments)
Programming in the Point-Free Style - https://news.ycombinator.com/item?id=14077863 - Apr 2017 (101 comments)