It's not that they're hard to understand, it's that they're much denser. From Factor's examples page:
> 2 3 + 4 * .
There's a lot more there to mentally parse than:
> (2 + 3) * 4
It's the same as when Rob Pike decries syntax highlighting. No, it's very useful to me. I can read much quicker with it.
It's the same principle behind how we use heuristics to much more quickly read words by sipmly looking at the begninnings and ends of each word, and most of the time don't even notice typos.
Well, I guess it might boil down to how one "thinks"?
Some people prefer:
2 3 + 4 *
Some other people prefer:
(* 4 (+ 2 3))
And some other people prefer:
(2 + 3) * 4
I personally find the last one easier to read or understand, but I have had my fair share of Common Lisp and Factor. :D
Syntax highlighting is useful for many people, including me. I can read much quicker with it, too. I know of some people who write Common Lisp without syntax highlighting though. :)
Forth could be written devilishly where you have this
2 .... hundreds of words .... +
where the operands of + are 2 and the result produced by the hundreds of words!
Which could also be:
.... hundreds of words .... 2 +
which would be a lot easier to read!
If you're writing Forth, it likely behooves you to try to adhere to the latter style of chaining where you take everything computed thus far, and apply a small operation to it with a simple operand. Not sure if it's always possible:
Yes, this is why you are supposed to have short words. You should factor out the complex parts into short, self-contained, and descriptively named words, which is going to make your code much easier to read, test, and maintain.
For example:
Instead of:
a b + c d + * e f + g h + * /
You should probably have:
: compute-numerator a b + c d + * ;
: compute-denominator e f + g h + * ;
: compute-ratio compute-numerator compute-denominator / ;
Most (if not all) Forth books mention this as well.
Don't you now have actions in the middle of the computation that are putting names into a global dictionary? I'd at least give them names like tmp-numerator to put them into a namespace of local/temporary functions, and then "forget" them immediately after the computaton that references them.
What's the compiled version of : compute-numerator a b + c d + * ; look like? I imagine at the very least that there has to be a call to some run-time support routine to insert a compiled thunk under a name into the dictionary.
Yes, defining words like "compute-numerator" does add entries to the dictionary, but that happens entirely at compile time. Forth doesn't insert a "compiled thunk" at runtime, the word is compiled as a name bound to a sequence of code field addresses (CFAs). When invoked, it's just a jump through the usual inner interpreter. There's no runtime cost for defining the word itself. When you invoke "compute-numerator" at runtime, the inner interpreter simply threads through those CFAs. There's no indirection, JIT, or dynamic thunk creation involved. The only runtime effect is the word being executed when called. All linking is resolved at compile time.
If you're concerned about polluting the global dictionary, a common idiom is (which you already know):
\ Define and forget immediately if temporary
: tmp-numerator a b + c d + * ;
tmp-numerator
FORGET tmp-numerator
or alternatively, you can isolate temporary definitions in a separate vocabulary:
TL;DR: Defining intermediate words adds entries to the dictionary, but this happens at compile time, not runtime. There's no additional runtime overhead. Naming conventions, FORGET, or vocabularies can mitigate dictionary pollution / clutter, but still, factoring remains the standard idiom in Forth.
Note: In some native code compiling or JIT-based Forth implementations, definitions may generate machine code or runtime objects rather than simple CFA chains I mentioned, but even in these cases, compilation occurs before runtime execution, and no dynamic thunk insertion happens during word calls.
I hope I understood your comment correctly. Please let me know!