Forth And Csample

last modified: December 1, 2014

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


Loading...