Forth And Csample

There was some discussion in the ChuckMoore page that Forth is no more difficult to read than C. Here's some sample Forth code, and some C code that does the same thing. You be the judge. The Forth source is from the ExampleForthCode page, but the comments (other than stack comments, which serve to identify input-output parameters) are removed.


Forth

: star ( - ) [char] * emit ;
: stars ( n - ) 0 do star loop cr ;
: square ( n - ) dup 0 do dup stars loop drop ;
: triangle ( n - ) 1 do i stars loop ;
: tower ( n - ) dup triangle square ;
: main ( - ) cr 7 stars cr 3 triangle cr 6 tower ;

C

void stars(int n) {
for (int i=0; i putchar('\n');
}
void square(int n) {
for (int i=0; i }
void triangle(int n) {
for (int i=0; i }
void tower(int n) {
triangle(n-1);
square(n);
}
void main() {
stars(7);
triangle(3);
tower(6);
}

I have a little bit of Forth experience and significantly more C experience, but I would say that the example above does support the statement that "Forth is no more difficult to read than C". However, I would also say that this example is so trivial as to be almost meaningless -- there is hardly anything here but the basic syntax that you'd get on day one of learning any language.


The above C code ignores several C shortcuts. Using "while" instead of "for" makes the code more readable to a C programmer(how?). In addition, the single-statement functions and non-looping functions can be written legibly on one line (although some C purists might disagree). It's legible, but 'less' legible than the multi-line versions.

Here is the simplified C code. Except for "n--", it should be understandable even by someone who does not know C.

void stars(int n) {
while (n-- > 0) putchar('*');
putchar('\n');
}
void square(int n) { int i=n; while (i-- > 0) stars(n); }
void triangle(int n) { int i=n; while (i-- > 0) stars(i); }
void tower(int n) { triangle(n-1); square(n); }
void main() {
stars(7);
triangle(3);
tower(6);
}

Nice, but the triangle looks upside-down from the other versions.


To translate the above (incorrect) code back into Forth:

: stars ( n -- ) begin dup while 1- [char] * emit repeat drop cr ;
: square ( n -- ) dup begin dup while 1- over stars repeat 2drop ;
: triangle ( n -- ) begin dup while dup stars 1- repeat drop ;
: tower ( n -- ) dup 1- triangle square ;
: main cr 7 stars 3 triangle 6 tower ;

Note that many Forth dialects support an explicit counted loop form too, despite it not being ANSI-compliant:

: square ( n -- ) dup for dup stars next drop ;

To correct the triangle upside-down bug,

: triangle ( n -- ) 1 begin 2dup >= while dup stars 1+ repeat 2drop ;
: tower ( n -- ) dup 1- triangle square ;

Also, if your coding conventions favor readability over conciseness, then a naive coder would really' write square as:

: row ( n x -- n x ) over stars ;
: square ( n -- ) dup begin dup while row 1- repeat 2drop ;

A more experienced Forth coder would see the general pattern:

begin dup while ... 1- repeat

occur all over the place. So she can refactor the whole construct into a higher-order function:

: asNecessary ( ... n xt -- ... )
>r begin dup while r@ execute 1- repeat drop r> drop ;
: .* [char] * emit ;
: stars ( n -- ) ['] .* asNecessary cr ;
: row ( n x -- n x ) over stars ;
: square ( n -- ) dup ['] row asNecessary drop ;
: row ( n x -- n x ) swap dup stars 1+ swap ; ( hooray for HyperStaticGlobalEnvironments! )
: triangle ( n -- ) 0 swap ['] row asNecessary drop ;
: tower ( n -- ) dup 1- triangle square ;
: main cr 7 stars 3 triangle 6 tower ;

If you're even more advanced, you can write words to permit code blocks right inside of Forth colon-definitions. We employ knowledge of immediate-words to extend the compiler, plus knowledge of run-time string evaluation to implement text-substitution macros.

( Add code quotations to GForth; odds are good it'll work elsewhere too. )
variable quotedCode
: $( S" ahead [ :noname " evaluate ; immediate
: $) S" ; quotedCode ! ] then [ quotedCode @ ] literal " evaluate ; immediate
: stars $( [char] * emit $) asNecessary cr ;
: square dup $( over stars $) asNecessary drop ;
: triangle 1 swap $( swap dup stars 1+ swap $) asNecessary drop ;
: tower dup 1- triangle square ;
: main cr 7 stars 3 triangle 6 tower ;

So, with only 2 lines of "blood, sweat, and tears", easily tucked away into a library, to define a new language feature, plus one higher-order function, the remainder of the program actually becomes substantially more readable and maintainable than any C version posted here to date.

--SamuelFalvo


First of all, you really need to stop simulating DO/FOR loops with WHILE loops. Especially on a page demonstrating the readability of Forth. Secondly, while code quotations are awesome, I really don't think that this example is more readable with them. This code should be written like so (the standard words are in uppercase only for strict ANSI compliance):

\ Just in case your system doesn't have FOR loops.
[UNDEFINED] FOR [IF] : FOR 0 POSTPONE LITERAL POSTPONE DO ; IMMEDIATE
SYNONYM NEXT LOOP
[THEN]
\ Also, asNeccesary is traditionally defined as
: times ( i*x xt n -- j*x ) FOR DUP EXECUTE NEXT DROP ;
: star [CHAR] * EMIT ;
: stars ( n -- ) FOR star NEXT CR ;
: square ( n -- ) DUP FOR DUP stars NEXT DROP ;
: triangle ( n - ) 1 DO I stars LOOP ;
: tower ( n -- ) DUP triangle square ;
: go CR 7 stars 3 triangle 6 tower ;

---

I'm new to Factor, but here's an attempt at a port

: star ( -- ) "*" write ;
: stars ( n -- ) [ star ] times nl ;
: square ( n -- ) dup [ stars ] curry times ;
: triangle ( n -- ) iota [ 1 + stars ] each ;
: tower ( n -- ) [ triangle ] [ square ] bi ;
: main ( -- ) nl 7 stars nl 3 triangle nl 6 tower ;

See also: ForthReadability ChuckMoore


CategoryForth