Ren-C's eloquence in the face of FizzBuzz

So there's a silly problem called "FizzBuzz" which is stated very simply:

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".

The claim is that as easy as this problem sounds, many programmers who walk into interviews have trouble with it. On the c2 programming wiki, someone argues for why this might be the case:

"I think Fizz-Buzz is "hard" for some programmers because (#1) it doesn't fit into any of the patterns that were given to them in school assignments, and (#2) it isn't possible to directly and simply represent the necessary tests, without duplication, in just about any commonly-used modern programming language."

But the design of Ren-C has more than enough chops for this problem! It's particularly elegant using a recent change to DELIMIT, including its specializations like SPACED and UNSPACED.

So how about that FizzBuzz?

count-up n 100 [
    print [
         unspaced [
              if n mod 3 = 0 ["Fizz"]
              if n mod 5 = 0 ["Buzz"]
         ] else [n]
    ]
]

Presto...that's all it takes!!! UNSPACED will return null if everything in its body opts out, which is the cue for the generic ELSE to run. This brings the magic of being able to avoid duplication in the tests. It's like every piece of problem specification corresponds to just one word in the program!

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
31
32
Fizz
34
Buzz
Fizz
37
38
Fizz
Buzz
41
Fizz
43
44
FizzBuzz
46
47
Fizz
49
Buzz
Fizz
52
53
Fizz
Buzz
56
Fizz
58
59
FizzBuzz
61
62
Fizz
64
Buzz
Fizz
67
68
Fizz
Buzz
71
Fizz
73
74
FizzBuzz
76
77
Fizz
79
Buzz
Fizz
82
83
Fizz
Buzz
86
Fizz
88
89
FizzBuzz
91
92
Fizz
94
Buzz
Fizz
97
98
Fizz
Buzz
2 Likes

Love it. FizzBuzz is indeed a neat example of a language challenge.
I'm still inexperienced with unspaced, so its use here seems like a fancy trick but not an intuitive function name.
This is in no way a complaint :wink: , I'm just thinking that someone coming from another language might see this short eloquent example but wonder "Cool-- Wait, what the heck is unspaced?".

1 Like

I guess it seems fairly sensible to to me...especially when people get introduced to it as print unspaced [...]. If that's how you learn it, you then think of being able to lift out the unspacing behavior you used when you needed it into a function. And you can say print spaced [...] too, though that's usually redundant.

Though if it were just JOIN it might appear clearer in this particular case, I think reserving JOIN for something else makes sense. Trying to use a generic series-joining operator for string production is problematic, as @Brett explains in detail.

But it's a good point that presenting FizzBuzz probably needs a little lead-in to explain UNSPACED to the uninitiated, to make absorption smoother.

Yes, I must relearn some of my old ways... I still think in terms of the JOIN/REJOIN vernacular. JOIN is a nice verb which intuitively suggests its purpose. UNSPACED on the other hand, seems defined in contrast to its opposite, which is also less obvious. To a dim bulb like me, anyway. Still not a complaint; I think with more use of REN-C I won't blink at some of the nuanced changes.

This seems like a good example to put on whatever homepage is eventually made for the project.

I knew about spaced/unspaced, had not seen it in action yet. I think unspaced here was very intuitive, like it very much, it is a true Rebol way solution. (And totally unmatched anywhere else afaik).

2 Likes

There's a new tool in the arsenal: the TOKEN! (a.k.a. "issuechar!") which does not participate in delimiting in DELIMIT-based primitives.

Can it help with FizzBuzz?

The UNSPACED is used as an aggregator for the ELSE to trigger on. We can reshape it to trigger off the PRINT's output...but then we'd need another PRINT:

count-up n 100 [
    print [
        if n mod 3 = 0 [#Fizz]
        if n mod 5 = 0 [#Buzz]
    ] else [
        print [n]
    ]
]

Well...it's different. It trades the potential confusion of what "unspaced" means for the semantics of unspaced TOKEN! in PRINT, and repeating PRINT twice. You lose a level of indentation--that's nice.

But I definitely like how PRINT can be opted out of completely...including the newline...and can telegraph its opt-out-edness to another construct. The fact that you can do this with two PRINTs is interesting in its own right.

Just because we can, I'll point out soft-quoted branching making this lighter (at the expense of comprehensibility, but improving space/speed by removing two series allocations/nested-evaluations)

count-up n 100 [
    print [
        if n mod 3 = 0 '#Fizz
        if n mod 5 = 0 '#Buzz
    ] else [
        print [n]
    ]
]
2 Likes

As we further chase down essentialism, I think the definition of FOR is going to be arity-3, and INTEGER! semantics would be COUNT-UP. Plus @ lets us print any value, so you could write:

for n 100 [
    print [
        if n mod 3 = 0 '#Fizz
        if n mod 5 = 0 '#Buzz
    ] else [
       print @n
    ]
]

But if I were making the poster today, it would probably say:

for n 100 [
    print [
        if n mod 3 = 0 [#Fizz]
        if n mod 5 = 0 [#Buzz]
    ] else [
        print [n]
    ]
]

Or even clearer:

for n [1 thru 100] [
    print [
        if n mod 3 = 0 [#Fizz]
        if n mod 5 = 0 [#Buzz]
    ] else [
        print [n]
    ]
]

This helps you know precisely whether it goes up-to-and-including a hundred (1 thru 100) or up-to-but-not-including a hundred (1 to 100), and that it starts at 1 and not 0.

1 Like

Nice.

But to be frank, I do like the 'count-up version better. It makes the language stand out a little more from all the other languages providing their 'for loop constructions.

But the 'for function has the better options.

1 Like

2 posts were split to a new topic: How Is COUNT-UP Implemented?