Home   Archive   Permalink

Rebol vs. FizzBuzz

There's a programming problem that someone came up with, and people have supposedly found it to confound a surprising percentage of interview candidates. It goes like this:
     "Write a program that prints the numbers from 1 to 100. But for multiples of three print 'Fizz' instead of the number, and for the multiples of five print 'Buzz'. For numbers which are multiples of both three and five print 'FizzBuzz'."
While it seems quite easy (and well...*is* easy), many programmers will want to express the string "Fizz" and "Buzz" just once each in the program, while doing the tests for divisibility just once each. That's not something most languages can do. If you want to see the carnival of workarounds in various languages, here's a wiki link where some people discuss the problem:
This is the kind of problem that the Rebol evaluation model would seem suited at factoring the way people would want. Rebol2 seems like it might be able to do it with something like:
     repeat n 100 [
         print any [
             rejoin [
                if 0 = mod n 3 ["Fizz"]
                if 0 = mod n 5 ["Buzz"]
It's a pattern that would *almost* work, foiled by REJOIN stringifying the nones instead of propagating them as a none return result:
     >> rejoin [none none]
     == "nonenone"
Let's imagine for the moment a variant of REJOIN which always produces a STRING! (like AJOIN) but that returns a NONE! if no values in the block reduce to anything besides NONE!. (Note that REJOIN has a number of problems when used for strings: https://forum.rebol.info/t/rejoin-ugliness-and-the-usefulness-of-tests/248 )
I'll call this new operation UNSPACED:
     unspaced: func [b [block!]] [
         b: reduce b
         remove-each item b [none? item]
         if not empty? b [ajoin b]
Changing the FizzBuzz code above to use UNSPACED instead of REJOIN, we'll get a working solution.
Ren-C has UNSPACED defined "in the box", as well as SPACED. Both of these are specializations of a generic DELIMIT operator. You could make more specializations:
     comma'd: specialize 'delimit [delimiter: ", "]
     >> comma'd ["a" if false ["b"] "c"]
     == "a, c"
     >> comma'd ["a" case [1 > 2 ["b"] 2 > 3 ["c"]] "d"]
     == "a, d"
But it also has a significantly more powerful model for infix. Not only can it drive generic THEN and ELSE operations which complete their left hand side and check for the absence of a value, but comparison operators like = can also complete their left hand side...offering more natural precedence. MOD is also infix. COUNT-UP acts like REPEAT, with a complementary COUNT-DOWN operation.
So it shapes up like:
     count-up n 100 [
         print [
             unspaced [
                 if n mod 3 = 0 ["Fizz"]
                 if n mod 5 = 0 ["Buzz"]
             ] else [n]
It's the same idea, *evolved*. (The details about how this is driven by a complete absence of value (NULL) opposed to a "falsey" NONE! are beyond the scope of this post, but are fairly epic.)
All told, it's nice to see a kind of coup-de-gras against FizzBuzz, where each word of code can be mapped back to the problem statement. This is no trick--things haven't been designed just to make FizzBuzz--the components are just elegant enough that this is one of the many problems it addresses cleanly.
There's plenty, but here's a nice one on how `--` and `??` are being reshaped for debug output:
And hey, look...I know people are envious...but envy isn't necessary. They're good ideas--just respect them as such, and you can use them too.

posted by:   Fork       16-Nov-2018/18:27:18-8:00



Type the reverse of this captcha text: "d n o c e s"