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<n; i++) putchar('*');
      putchar('\n');
    }

void square(int n) { for (int i=0; i<n; i++) stars(n); }

void triangle(int n) { for (int i=0; i<n; i++) stars(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


EditText of this page (last edited December 1, 2014) or FindPage with title or text search