Composition by writing things next to each other
In most languages, combining two functions takes some syntax: parentheses, a pipe operator, a variable to hold the intermediate result. In a concatenative language, placing two pieces of code next to each other is the combination. The program A B means do A, then do B, and the two communicate only through a shared stack.
Take a hypothetical word-based notation, similar in spirit to Forth, Joy and Factor:
3 4 + dup *
Reading left to right: push 3, push 4, add them to leave 7, duplicate the top so there are two 7s, multiply them to give 49. Every word is a function from stack to stack. The number words push themselves. The operators take their arguments off the stack and leave a result. The whole line is the composition of five small functions, and the order on the page is the order of execution.
A useful consequence
Because each word takes a stack and returns a stack, you can cut a program anywhere between words and get two valid programs. The first half leaves something on the stack, and the second half continues from it. That makes refactoring unusually mechanical. If dup * appears in many places, you can give it a name such as square and replace each occurrence, with no variables to rename and no parameters to pass.
This is different from the idea behind a typical expression language, where f(g(x)) has to be restructured to extract g(x). In a concatenative language the pieces are already in a line.
No variables, so shuffling words
With no named arguments, values are kept in place by stack manipulation words. Typical ones are duplicate, drop, swap and rotate. A program that needs the same value twice duplicates it; one that needs two values in the opposite order swaps them. Programmers often find that good code in this style is short and that a word needing more than three or four items from the stack is a sign it should be split up.
This is also where the style gets its reputation for being hard to read. Without names, the only guide to what the stack holds is a comment or your own memory, which echoes the code readability problem shared by many odd languages. One practical habit is to annotate each definition with a short note of what it takes and leaves.
Quotations: code as a value
Concatenative languages commonly let you wrap a piece of code in brackets without running it. The wrapped code, often called a quotation, sits on the stack like any other value. Another word then runs it, or runs it several times, or chooses between two of them.
[dup *] call
Here the bracketed part is pushed unevaluated, and call takes it off and runs it on whatever lies underneath. Conditionals and loops are built this way. An if is a word that takes a flag and two quotations and runs one of them. There is no need for special syntax for control flow, because control flow is just words that consume quotations.
This idea reappears in tiny stack languages that manipulate strings of code, where parenthesized text is stored, copied, joined and finally executed. It is the same trick in a plainer form.
Where odd languages echo the idea
Not every stack language is concatenative. Befunge puts numbers on a stack and does follow the postfix style for arithmetic, but its control flow depends on a two-dimensional grid, so you cannot cut the text at any point and expect two programs. In the Befunge language, 23+4*. still means (2 + 3) * 4 and prints 20, and the program stack behaves just as a concatenative runtime would. The difference is that the surrounding code can send the pointer in any direction.
Pure concatenative languages are also close relatives of other formal systems. The lambda calculus builds everything from function application, whereas the concatenative view builds everything from composition. Combinators such as those used in combinator calculus sit between the two, since they too are small fixed functions glued together. Seeing the connections helps explain why these languages attract people who like to think about what a program really is.
A small mental exercise
Try to predict the stack after each word of 2 3 swap - 5 *. Start with 2, then 3, giving 2 below 3. Swap puts 3 below 2. Subtract takes the top as the second operand, giving 3 minus 2, which is 1. Then 5 is pushed and multiplied, leaving 5. Working through a few lines like this is the quickest way to get comfortable with the style, and it prepares you for the rules of how a language defines syntax and semantics separately.
What the style is good at
It is compact, composes naturally and has simple implementations: a basic interpreter is little more than a loop over words and a stack. It is less comfortable for programs with many long-lived values. Neither is a flaw, and the experiment of writing something real in such a language shows which kind of problems fit.