Tue Jun 2 17:03:19 CEST 2009
Why Forth works
(TODO: Reformulate. The ideas are ok, but not well put.)
Forth works because:
* It has a powerful composition mechanism (words and stacks) which
provides referential transparency and absence of side effects,
allowing for a functional programming style.
* Using early binding and cooperative multitasking, the global
memory and machine state can be used without loosing local
reasoning, to extend the words+stacks core language.
Let's start with the biggest disadvantage of Forth or the Forth-like
approach to designing run-time systems: Forth requires you to think
differently about programs and programming.
Coming from a main-stream (C/C++) programming background, working
without lexical or object scope is weird. Giving up the use of local
names (random access to your problem's sub-parts) and exchanging them
with programming in terms of _incremental updates_ requires a change
One of the most amazing observations I made while _learning_ Forth is
is how my use of stack shuffling words drasticly reduced once I got
the knack of factoring. While it is in general more difficult to
factor a solution into very small steps, it is skill that can be
learned. Writing well factored code has the side-effect of bringing
deeper understanding. It requires an initial investment (think harder
about representation) but often provides good payback in the long run.
Once the problem is reduced to the right granularity of sub-problems,
the top-level solution is often trivial. A big advantage of
wel-factored code is that the individual parts can be tested
interactively and in isolation. Such is really important for
low-level programming, where the first words you write abstract the
concrete machine into something more intuitive to understand.
Surprisingly often the process of factoring leads to words that
operate only on the stacks. However, in some cases spilling to global
variables is necessary. Another thing that amazed me is that global
variables and dynamic scope seem to work better in Forth.
Single-threaded execution makes shallow binding easy: just push/pop
variables when you use them, preferrably in an abstract way. The
second is early binding. Code that defines names that already exist
won't interfere with the existing code. On top of this, for simple
embedded problems, often the global namespace is yours, which makes
state management into a design guideline without requiring much