
Why Concatenative Programming Matters - evincarofautumn
http://evincarofautumn.blogspot.com/2012/02/why-concatenative-programming-matters.html
======
barrkel
The problem with stack languages for me has always been in typing. I find it
difficult to track the exact stack effects of each word (function),
particularly if these functions are pushing, popping and applying functions
that themselves modify the stack. Even when dealing with something as simple
as optimized x87 code, I would annotate all the FXCH etc. to keep track of the
bits of the arithmetic expression on the stack, as I tried to keep multiple
operations in flight.

Yes, I know Christopher Diggins' Cat is statically typed. The problem isn't in
compiler diagnostics, it's in readability.

In a stack language, an expression as simple as 'a b c d' is cryptic. If c
consumes two arguments, in applicative terms it would be d(c(b, a)); if b
consumes one argument and c only one, then it would be d(c(b(a))); if d
consumes two arguments, b one, and c none, then it would be d(c, b(a)). And
this ambiguity makes it very hard for me to read the code unless I'm intimate
with all the individual words. You have to understand everything to understand
anything.

Unfortunately, I suspect that concatenative languages tend to derive their
power from this ambiguity, because the lack of specificity leaves considerable
flexibility for what the words can do with the stack.

So my considered opinion for now - and this article doesn't really change
that, it is mostly a tutorial - is that stack languages are best as a compiler
target rather than a human source form.

~~~
dkersten
I always liked this (slightly paraphrased) quote:

    
    
        Idiomatic Factor does not (explicitly) use the stack.
    

That is to say, in Factor, a popular concatenative language, you tend to use
higher order functions and high level combinators instead of manually
shuffling stuff around on the stack. Sure, from time to time you still need to
move stuff into the right place, but its easy to keep track of the stack if
you only need to consider two or three items. At least, that seems to be the
theory - I have not used Factor in a number of years so don't know how well
that holds up in practice.

Also, Factor does allow you to use local (named) variables and infix syntax
when it makes more sense to do so. Concatenative languages do not _need_ to be
unreadable at all and for some things I think the concatenative code is
actually more naturally readable.

" _If c consumes two arguments, in applicative terms it would be d(c(b, a));
if b consumes one argument and c only one, then it would be d(c(b(a))); if d
consumes two arguments, b one, and c none, then it would be d(c, b(a))._ "

 _'a b c d'_ in a concatenative language is akin to _'a | b | c | d'_ in a
shell script (where a stack is being threaded from function to function) or
_'d(c(b(a(STACK))))'_ in most languages where each function has a type _'Stack
func (Stack s)'_. It doesn't change depending on how many arguments a function
takes off the stack - its always _'d(c(b(a(STACK))))'_. Of course, since
literals are essentially functions which push their value onto the stack, what
you say is true if _a_ , _b_ and _c_ are literals. I don't find this nearly as
confusing as what it sounds like from your comment.

In code like '1 2 + print', all the words you see are functions. 1 pushes the
value of 1 onto the stack and so on. This is interesting because you can
easily factor out any part by replacing it with a new word that calls the
replaced word:

    
    
        :A 1 2 + print
        
        :add2 2 + ;
        :B a add2 print
    

A and B are the exact same.

This is also interesting:
<http://concatenative.org/wiki/view/Concatenative%20language>

~~~
barrkel
Here's another way to look at it. The stack is the global state of the
program; and this global state is passed as an argument to every function. By
an unkind interpretation, a stack language is the opposite of functional;
every function isn't just mutable, it mutates the world.

In a shell pipeline of 'a | b | c | d', I can see the dataflow clearly. 'c'
never sees the output of 'a' directly; it must be mediated by 'b'. But for a
stack, 'a b c d', I have no idea if c uses the value a or not, unless I know
the definition of b and how many items c consumes.

When reading stack code, I find myself mentally inserting parentheses to see
how far back each operation reaches; for higher-level operators, I find myself
wanting to insert comments describing the shape of the code being implicitly
constructed. I need to see the "tree" (though frequently a dag) shape.

This isn't different in complex infix expressions. There too, parentheses are
often needed for clarity - and good programmers use them even when not
necessary - and for very complicated expressions, I'll spread them out across
multiple lines, with indentation to make the tree explicit, where performance
or similar concerns prevents writing out the expression in a more long-hand
fashion.

~~~
_delirium
That's one reason I've found the pipeline metaphor not great as a way of
introducing concatenative languages. To me, the shell pipeline, which has a
data-in/data-out streaming kind of flavor, generalizes (with multiple
input/outputs) into dataflow languages, not into concatenative languages.
There are definite connections between the paradigms, but imo the stack-
manipulation is a different thing not present in stream/pipe based languages.
The graphical music language Pd literally draws the pipelines as wires
connecting input and output ports on functions (inspired by physical patch
cables), which seems much closer to what I'd expect a generalized version of
shell pipelines to look like.

~~~
evincarofautumn
Statically typed concatenative languages are precisely dataflow languages.
_Dynamically_ typed ones aren’t, because they can have dynamic stack effects.
But the diagrams in my article look just like PureData programs for a reason.

~~~
dkersten
Dataflow languages are what I'm most interested in these days when it comes to
programming languages and I've always liked the symmetry between concatenative
languages and pure dataflow languages (visual ones or just stream/flow based
textual languages).

I've also been doing a lot of Max/MSP programming lately and really love
working in its "passing messages along patch cables" system, but am frustrated
by its lack of higher order objects and lack of aggregate data types (hell, it
won't even let me create a list of lists).

What I really want is a Max/Pd-like language with richer set of data types
(algebraic types with pattern matching would be a good start) which can be
trivially and unambiguously converted between visual and textual
representations. A concatenative language may make this a lot easier.

~~~
evincarofautumn
I think you’re really going to like Prog, then. It’s concatenative but focused
on ADTs and pattern matching. Abstraction is easy thanks to quotation. I have
some neat ideas in mind for an editor, as well, that would do just what you
say—display a flow diagram of a program or selection as you edit.
Concatenative languages are indeed, as the bland Wikipedia article puts it,
“highly amenable to algebraic manipulation” of this sort.

------
jacobolus
For anyone who’s never read it, I strongly recommend _Thinking Forth_. The
style of thinking is surprisingly relevant to programming in other kinds of
languages.

<http://thinking-forth.sourceforge.net/>

Also see <https://news.ycombinator.com/item?id=3389286>
<http://news.ycombinator.com/item?id=2378062>
<http://news.ycombinator.com/item?id=2884131>
<http://news.ycombinator.com/item?id=2585750>

------
tailrecursion
This article introduces concatenative langauges, but it doesn't explain why
they matter.

In my work, I found that the lack of syntactic marking for arguments of
function calls is a devastating drawback. Coming back to my own function
definitions a week after writing them, I spent minutes unpuzzling the code. I
could use every trick and write the function in the most elegant way, using
combinators in the style of Factor, and that made it worse. But writing it out
with local bindings also didn't help. As soon as you write a nested
expression, you start to lose the sense of which values are being passed
where.

gcd n1 n2 n2 0 = if n1 else n2 n1 n2 mod gcd

gcd zero if else "mod" plus swap gcd

factorial 1 interval product

I was designing a Forth-like functional language with generic functions, and
for a long time I felt that the lack of markings didn't matter. I thought that
one could understand the code "well enough" without knowing exactly where the
arguments were going. There is something to this argument, and there's a lot
we can do to make code say what it does. But sometimes the author just had to
_implement_ something, and technicalities arose, and when you go to read it,
the code doesn't "read" and you have to bring your own intelligence to figure
out what it does -- it won't tell you. In these cases, explicit argument
marking is indispensable.

In Forth-like languages, simple things are wonderful and elegant. There is no
other shorter way to write than point-free. And, writing with combinators is
satisfying, and it can make the "essence of the function" more evident. But
unless you severely constrain your style to ultra short definitions, avoid
nested expressions, and use local variables profusely, you're constructing
puzzles for yourself.

It's almost like the concatenative style is too optimal. You have to back up a
step in beauty in order to get a practical tool.

~~~
gruseom
The two best-known features of Chuck Moore's style -- extreme simplicity and
extreme factoring -- must be how he avoids the dangers you describe. I wonder
if these qualities go beyond stylistic choice and are actually necessary if
one is to build real systems in Forth. That might explain why programmers who
come from other languages tend not to stick with it. Your story is reminiscent
of others I've read in the past (most prominently
[http://www.yosefk.com/blog/my-history-with-forth-stack-
machi...](http://www.yosefk.com/blog/my-history-with-forth-stack-
machines.html)) that talk about being in love with the Forth model and then
abandoning it because it got too complicated to make it do what they want.

Makes me wonder if trying to do what you want in that model is a recipe for
disaster; instead, maybe you have to let the model tell _you_ what you can do:
if it's simple you get to do it and if it's not you don't. Chuck would see
that as a great advantage, but most programmers wouldn't.

Edit: the article I linked to talks about that at length. His conclusion is
that if you're going to build systems in Forth, you need the freedom to shrink
or morph the problem into something that can be solved simply in Forth. If you
don't have that freedom (e.g. because people are feeding you inflexible
requirements), it probably won't work.

------
cromwellian
If this article is a homage to the famous "Why Functional Programming Matters"
paper, I think it failed in a major way. The Why FP Programming Matters paper
began each section with practical programming problems that FP solved
elegantly. It built up the solutions piece by piece and showed at each step,
how easy they were to comprehend, extend, and abstract.

The FP paper is very effective in making people who are hard core imperative
programmers want to try FP. But the CP article here doesn't really IMHO elicit
the same desire.

You should try writing another one with some practical examples.

~~~
evincarofautumn
It wasn’t really supposed to be, but sure, I gladly will.

------
Drbble
The body does not deliver on the promise of the title, but does raise the
question.

The answer seems to be that concatenative languages give you the power of
functional languages using a low-level nearly absent syntax (like Lisp). I
guess that's useful as way to demystify the compilation process (like how
assembler and CS demystify how the CPU actually gets work done, as contrasted
against magic like Ruby), and matters if you want to understand or write a VM,
or reason about a program in a completely unbiased way that (for better or for
worse) has no cognate to human language (but may be an elegant non-linguistic
mechanical language).

For general purpose programming, I prefer a little syntactic sugar. Humans
seem to have an instinct for language, and it is helpful to leverage that
instinct when writing and ituitively grokking programs, if not for
constructing formal proofs.

~~~
evincarofautumn
Agreed on all points, actually. My language Prog, though concatenative to make
my life easier, has syntactic sugar for yours.

------
dustingetz
minimalistic syntax isn't necessarily the holy grail. We can't pinpoint
exactly why Clojure is hockey-sticking but Common Lisp didn't[1]. But Clojure
took a minimalistic language, then made a few very tactical departures from
minimalism - common data-structures have native syntax encouraging people to
use more than just lists, and the native data structures are immutable
encouraging a functional style. from this we learn that minimalism is a great
base, but the langage forms opinions about what code should look like via its
syntactical choices, and these opinions shape its community, libraries and
ultimately its success. language designers must take this into account.

with that said, you can do "concatenative" programming in any language
supporting a functional style (python, ruby, clojure, haskell, javascript),
and some of these languages (clojure, haskell) encourage or force functional
style. clojure and haskell have different opinions about how this should be
done, but they are opinions, not gospel, and each has their use for different
problem domains and personalities.

the author makes some assertions that a "concatenative" language, one that
encourages a concatenative style, are better than functional languages. i
don't think these opinions were adequately backed up at all - i'm not just
skeptical, i sort of think he's wrong. both clojure and haskell strongly
encourage composition over application. these languages already have
concatenative opinions. i don't really see the fuss here, i don't see the need
to introduce more metaphors into an ecosystem which is already unfriendly to
noobs. what we need is a language as noob friendly as python which has
functional opinions. ruby got noobs using lambdas without realizing it, but
ruby also has some weird opinions about magic which the functional community
frowns upon. there's a clear niche here.

[1] (edit for gruseom) but we can try to learn from those who are successful,
especially those who failed a few times first. Martin Odersky has written all
about his failures leading to Scala, and Rich Hickey talks about what he
thinks made Clojure successful all the time.

~~~
gnosis
There's one simple reason why Clojure is getting so much momentum: Java.

If it weren't for it sitting on the JVM and having access to all those Java
libraries, Clojure would have been dead in the water.

~~~
weavejester
There are several Lisps on the JVM that predate Clojure by a number of years,
so whilst the JVM may be a factor, it's not the only reason for Clojure's
success.

------
mmphosis
Joy - Manfred Von Thun <http://www.latrobe.edu.au/phimvt/joy.html>

Cat - Christopher Diggins <http://www.cat-language.com/>

~~~
dkersten
Factor - Slava Pestov <http://factorcode.org/>

------
erichocean
Here's a practical example:

I have found a concatenative language (Factor, specifically) to be extremely
useful for use in a workflow system. In particular, since it's not
applicative, there's no API between "words", so it's damn near trivial to
combine workflow sequences together -- no API is necessary. Each sequence of
words (think of it as a workflow) can consume and or produce as much data as
needed -- again, without any API coordination whatsoever.

------
DasIch
As much as I like the basic idea, I fundamentally dislike that the control
flow becomes implicit. In order to have any understanding of what is happening
you need to know the effect each function has on the stack and you need a
mental model of the stack or at least it's current state.

In an applicative language this is explicit and it is probably the main reason
for the fact that it is relatively easy to understand code in any procedural
language once you've learned one.

~~~
dustingetz
in a purely functional language, you don't need to think about the stack any
more. your code expresses its dependencies via composition and the order in
which things happen becomes the compiler's concern. you don't need or want to
have an understanding of what's happening in an imperative sense. bam, a whole
class of bugs made impossible. [edit: you're right though, if you actually do
need to understand things in an imperative sense, e.g. for performance
optimizations, things get complicated.]

------
abecedarius
I once made a variant of Backus's Turing-lecture language FP, where to compose
functions you wrote 'f g' instead of the original 'g o f'. I'm curious if
people into concatenative languages would call this one of them -- it
satisfies the definition in the article, but there's no stack, so I'd guess
not. It's more like point-free Haskell with some notational support for the
style.

(On second thought, this post's title echoes Backus's. Maybe they would
include FP.)

------
adrusi
The way of reasoning about `2 3 x` that it presents is extraordinarily
confusing, I can't really comprehend it as it is described.

