fabio_cevasco wrote:
> After playing around a little bit, I just wanted to ask you guys how you
> cope with the (generally speaking) lack of readability of concatenative
> (but I'm referring mainly to Joy here) code.
Reading -- and writing -- concatenative code is _different_. You have to
learn some new thought patterns, and it'll take some time.
> Although I really like everything concatenative programming offers, I'm
> somehow overwhelmened by it, in the sense that I find it more difficult
> to read (and mentally execute) concatenative code without local
> variables.
> How do you cope with that?
You're on the right path; good naming is important. Forth programmers
are urged to keep a thesaurus handy; terse, appropriate names for words
are very useful to improve readability (long-winded names are better
than a lack of factoring, but there are advantages to terseness as
well). Being very willing to factor common sequences out into a word of
their own also tends to be very helpful, so long as the result doesn't
hide your intended algorithm.
One of the biggest things, I think, is realizing that first,
concatenative languages express algorithms, NOT expressions. Most
languages are decent at expressing both sequences of actions (AKA
processes, procedures, or algorithms) and expressions (AKA math
formulas). Concatenative languages are specialized to express sequences
of actions.
> To improve readability, the only thing I can think of is labeling each
> quoted program, using definitions, so:
> DEFINE
> remove_pivot_and_split_list == uncons [>] split
> insert_pivot_and_merge_lists == [swap] dip cons concat
Great! Get bolder and make two definitions for each of those:
"remove_pivot", "split_list", "insert_pivot", and "merge_lists".
> Is there anything better than this? How do you keep track of what's
> going on the stack in a language with no variable assignments?
Well, we do have a few rules. Nothing hard and fast, but most of them
should make sense. Don't assume that one of the rules is more important
than another one -- apply them to your situation.
The first rule is to keep it simple. Try to write words that use three
or fewer parameters, that take the parameters in a natural order (so you
don't have to shuffle), that are short. A single line is ideal; three
lines is excellent; eight lines is long.
The second rule is to keep it algorithmic. An algorithm is "a finite set
of unambiguous instructions performed in sequence to achieve a
goal" (dictionary.com). Breaking this down, we see some sub-rules:
* Keep your goal in mind and make it evident (probably in comments).
* Keep your steps in mind and make them evident (probably in word
names).
* Keep your steps unambiguous (in choice of word names).
* Show the sequence of steps clearly (probably by making one specific
word contain ONLY the words that each implement each step in your
algorithm).
The third rule is to try to keep your stack effects clear; the
programmer should be able to know what your code does without having to
execute it in his head. It's possible to write code that consumes a
variable number of items off the stack, but it's usually a bad idea.
It's possible to write code that depends on knowing how deep the stack
is... But likewise, usually a bad idea. It's easy to write code that
does a lot of stack juggling, but modern concatenative languages provide
combinators like cleave and spread that allow you to express your
intentions in terms of how the data flows rather than how you want the
stack to be juggled right now.
The rest of the rules you'll find in any guide to good design -- you
should seek highly coherent design, low coupling, clear purpose... These
are just the ones that apply especially strongly to concatenative
languages. (Yes, most languages work better with good algorithm
expression, but it's an especially strong requirement with concatenative
ones; and I think concatenative languages have some strength on the
topic, since they are read in strict sequence left-to-right.)
> Fabio
-Wm