25
\$\begingroup\$

There are quite a large number of existing implementatons of FizzBuzz but I awoke this morning in a cold sweat with the terrible revelation that Code Review had no FizzBuzz implementation in Forth! To redress this terrible oversight, I wrote this while sipping my first cup of coffee.

: FIZZBUZZ ( -- )
  CR 100 1 DO 
     I 3 MOD 0= I 5 MOD 0= 2DUP AND IF 
        ." fizzbuzz " DROP DROP
     ELSE IF 
        ." buzz " DROP
     ELSE IF 
        ." fizz " 
     ELSE I . THEN THEN THEN
  LOOP ;

I'm interested in a general review, including possible alternatives to the use of MOD, and whether further factoring should be used. For those unfamiliar with Forth, try http://www.forth.org/tutorials.html

\$\endgroup\$
1
  • 1
    \$\begingroup\$ This looks familiar. Why does this look familiar? Why do I recognize this language? Found it. Evolve used KForth. \$\endgroup\$
    – RubberDuck
    Commented Jul 29, 2014 at 4:41

2 Answers 2

7
+100
\$\begingroup\$

Are you using two or three spaces of indentation? Yes.

Are you using two and three spaces of indentation? Yes.

Are you using two xor three spaces of indentation? No.

I don't know the coding conventions of Forth, but I'd personally prefer to see using four spaces (one tab) of indentation. Most importantly however, be consistent. </nitpicks> As far as I understand the Forth style conventions, three spaces should be used as indentation always.


I 3 MOD 0= I 5 MOD 0= 2DUP AND IF 

This line is excessively long and contains a lot of different instructions, in my opinion this is unnecessarily confusing. By splitting it up I consider it easier to read and understand.

It can easily be split up to the following:

I 3 MOD 0=
I 5 MOD 0=
2DUP AND
IF

: FIZZBUZZ ( -- )
  CR 100 1 DO 

I'm not quite sure why you print a new line (CR) at the beginning of your function (sorry, your "word"). IMO it's better to print a new line after each iteration.


It would be easy to extract the 100 to be a parameter to your word instead of hardcoding it into it. That would give some flexibility at least, even though most Fizzbuzz challenges only goes to 100. This can easily be accomplished by removing the 100 at the beginning of your word.

Speaking of 100. Your current implementation actually excludes the number 100. This is easily fixed by adding 1 + to the beginning of your word.


Your nested if-else-if-else-if-else-then-then-then is hard to follow when you're not used to how Forth works (yes, I am speaking from experience here). By only changing the indentation of it, it gets easier to follow along with it.

Although it still may look a bit strange that the THEN is at the end, but that's not anything you can do something about :)


With the suggestions above, the code would look like this:

: FIZZBUZZ ( u -- )
    1 + 1 DO 
        I 3 MOD 0=
        I 5 MOD 0=
        2DUP AND IF 
            ." fizzbuzz" DROP DROP
        ELSE
            IF 
               ." buzz" DROP
            ELSE
                IF 
                    ." fizz" 
                ELSE
                    I .
                THEN
            THEN
        THEN
        CR
    LOOP
;

As for alternatives to using mod, even though it is possible to use two separate counters and decrease them and reset them once they hit zero, I don't think that would improve the code much.

Considering that I managed to understand your code and learn a bit of Forth, I'd say that's a compliment to your code. I believe that with some of the changes I've suggested here, it would be much easier for new Forth programmers to understand it.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the review. See forth.org/forth_style.html for an example of Forth coding conventions, although I'm actually using a somewhat newer style. In Forth, it's common practice to have a line end with IF. Also, your stack comment should be modified to ( u -- ). \$\endgroup\$
    – Edward
    Commented Nov 23, 2014 at 15:28
  • 4
    \$\begingroup\$ "this line is very confusing for those who have never read or written Forth before" is that a fair standard? Before I learned it, most Python looked like gibberish to me. \$\endgroup\$ Commented Nov 23, 2014 at 17:41
  • \$\begingroup\$ @raptortech97 Edited that part. \$\endgroup\$ Commented Nov 23, 2014 at 20:36
2
\$\begingroup\$

[Disclaimer: I love many ideas in FORTH, but don't actually use it.]

Let's try splitting into smaller words. How each number is transformed is separate from the loop, so I'd like the top level to be:

: 100FIZZBUZZ  ( -- ) 
   100 1 DO          
      I FIZZBUZZ     
   LOOP ;            

(I omitted the CR. The output itself is horizontal on single line, so line feeds should be up to call, can CR 100FIZZBUZZ CR or whatever.)

The core of how it's displayed is actually a 4-way branch on 2 decisions, so let's split that too — we'll take 2 booleans on the stack:

: DISPLAY ( n fizz? buzz? -- )
   IF
      IF 
         DROP ." fizzbuzz "
      ELSE
         DROP ." buzz "
     THEN
   ELSE
      IF
         DROP ." fizz "
      ELSE
         .
      THEN
  THEN ;
  • Easy to test interactively:

    15 false false DISPLAY 15  ok
    15 true  false DISPLAY fizz  ok
    15 false true  DISPLAY buzz  ok
    15 true  true  DISPLAY fizzbuzz  ok
    

OK, now make the 2 decisions:

: FIZZY? ( n -- fizz? ) 3 MOD 0= ;
: BUZZY? ( n -- buzz? ) 5 MOD 0= ;
: FIZZBUZZ { n -- }  n  n FIZZY?  n BUZZY?  DISPLAY ;

( I guess actual FORTH coders would use some DUP and SWAP rather than named local here, but after seeing Factor's dataflow combinators that express the intent of "feed a single value to both predicates [ FIZZY? ] [ BUZZY? ] bi" I can't bring myself to low-level stack manipulation... )

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Forth is all about the stack, so stack manipulation is a requirement in most Forth programs. \$\endgroup\$
    – Edward
    Commented Feb 10, 2021 at 19:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.