quiltro.org

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.