Ask, and ye shall receive.
In yesterday's post, I reported on an attempt to solve the classic Fizzbuzz problem in a functional style. Near the end of the post, I wrote:
This solution is fun but feels a little unsatisfactory. I'm not as fluent with functional programming as I am with OOP. Perhaps there is a more idiomatic FP way to do this? Let me know.
Thanks to everyone who responded with ideas and conversation!
Chris Johnson offered a couple of different approaches and ended up with a slick solution that is fully functional: create an array containing the different values to print, then use cycled values for Fizz and Buzz to construct an index into the array.
Here's an elegant solution in Haskell, courtesy of Chris:
pick :: (Int, Int, Int) -> String pick (f, b, n) = [show n, "Fizz", "Buzz", "FizzBuzz"] !! (f + b)main = do let fizzes = cycle [0, 0, 1] let buzzes = cycle [0, 0, 0, 0, 2] mapM_ putStrLn $ take 30 $ map pick $ zip3 fizzes buzzes [1..]
Beautiful. 1 (= 1 + 0), 2 (= 0 + 2), and 3 (= 1 + 2) select the appropriate word to print in cases divisible by 3 and/or 5, and 0 (= 0 + 0) selects the number as a default. Beyond that, the code is a couple of maps on top of a zip.
This technique reminds me of a similar trick Joe Bergin used when we ran the Polymorphism Challenge workshop back in 2005, solving one problem by looking up in an array an object to send a message to. I'd completely forgotten that solution until I saw Chris's!
Racket is not as concise as Haskell for a problem like this, but a Racket solution is nice, too.
First, here's choose:
(define (choose f b n)
(let ((answers (vector (number->string n) "Fizz" "Buzz" "FizzBuzz")))
(vector-ref answers (+ f b))))
Then we define the cycles:
(let ((fizzes (cycle '(0 0 1) size))
(buzzes (cycle '(0 0 0 0 2) size))
(numbers (range 1 (add1 size))))
...)
Finally, the code to implement the functional idea is:
(map displayln
(map (lambda (L) (apply choose L))
(zip fizzes buzzes numbers)))
I have to apply choose to the zipped lists because
Racket doesn't have a destructuring define form.
Of course, Racket is a language for making languages, so we
can create one if we'd like! I've written a three-place form
hardcoded to handle (define-match (choose (f b n)) ...),
but I'll have to do some work to generalize it to take lists of
any size.
Thanks again to everyone who wrote in response to my post, and especially to Chris for the fine solution. Readers of this blog continue to teach me new things. I hope they find some value in my posts, all too rare these days. In any case, I have much to be thankful for on this Thanksgiving Day.
in the Fizzbuzz language
Over the weekend I ran across a blog post by Evan Hahn in which he took on this exercise:
... write Fizz Buzz with no booleans, no conditionals, no pattern matching, or other things that are like disguised booleans.
Ah, the memories! In 2005, Joe Bergin and I ran a SIGCSE workshop
called The Polymorphism Challenge, which I mentioned briefly in
a trip report
at the time and reported more fully in
a 2012 post.
The goal of that workshop was to help participants learn how to use
polymorphic objects to solve a variety of standard problems without
relying on if statements to select among different
conditions. It was great fun, and many of the workshop participants
found it both challenging and enlightening.
Hahn's post gives a functional solution to Fizzbuzz in Python, using iterators and a creative, though limited, string mask:
from itertools import cycledef string_mask(a, b): return b + a[len(b):]
def main(): fizz = cycle(["", "", "Fizz"]) buzz = cycle(["", "", "", "", "Buzz"]) numbers = range(1, 101) for f, b, n in zip(fizz, buzz, numbers): print(string_mask(str(n), f + b))
I thought, hey, I can do that in Racket:
(define string-mask
(lambda (num-str fb-str)
(string-append fb-str
(guarded-substring num-str
(string-length fb-str)))))
(define fizzbuzz
(lambda (max)
(let ((fizz (cycle '("" "" "Fizz") max))
(buzz (cycle '("" "" "" "" "Buzz") max))
(numbers (range 1 (add1 max))))
(for ((f fizz)
(b buzz)
(n numbers))
(displayln (string-mask (number->string n)
(string-append f b)))))))
Two notes:
cycle function. Racket has a
sequence->repeated-generator function in
racket/generator, which behaves like
cycle in Python's itertools,
but I wanted to write a simple for over
lists.
substring is more careful than Python's [:].
The string masking idea is clever, but Hahn points out that it starts to fail when the number is long enough that the Fizzbuzz string can't cover it all.
This solution is fun but feels a little unsatisfactory. I'm not as fluent with functional programming as I am with OOP. Perhaps there is a more idiomatic FP way to do this? Let me know.
Bonus code: If you want to see a solution that will melt your face, check out this article implementing Fizzbuzz in the array language Q. I learned APL on 1985 or so, and its way of thinking will always have a strange hold on my brain!
Anyway, that was a nice diversion for a few minutes during Thanksgiving break. As I tell my students almost every day in class, I like to program. I'm thankful that I am able to do it.