Combinators in concatenative programming
This page is a work in progress. Here be dragons.
Combinators are higher-order functions in concatenative languages.
Combinators as data flow
Most languages use named variables for storing values, and routing them where they need to be. When a value needs to be used in more than one place, or passed through several steps of computation, a name gives you a way to refer to it again without losing track of it.
Concatenative languages, on the other hand, allow the programmer to express more
complex dataflow schemes using combinators, removing the need for naming values
in many cases. For example, bi fans a single value out to two quotations,
bi@ runs two values through the same quotation, and keep calls a quotation
preserving the value it consumes.
Combinators as control flow
Languages like Factor and Growl use combinators for expressing
control flow and looping. For example, in Growl, there is no if construct
built into the language, nor branching opcodes in its virtual machine.
A combinator like if (named ifte in Growl) is simply constructed from the primitives choose, which selects between two values based on a flag, and call, which calls a quotation in the stack.
def ifte
{ choose call }
Looping can be achieved by using tail-recursion, such as the definition of
list/each for Growl:
def list/each
[..r, a list, [..r, a -> ..r] -> ..r]
{ list/case: swap
[drop]
[swap unbury [unbury drop call] 3keep
swap drop list/each];
}
Writing combinators
When writing combinators you should try to keep the stack clean when calling the
quotation, which can be done using the retain stack (if available) or preserving
combinators such as dip and keep.