8
\$\begingroup\$

Vogel612 and I decided to take a shot at the StackSTV challenge in Haskell. This is part of the CRitter Collaboration challenge. Except I don't know any Haskell. So let's try a FizzBuzz first!

I'm quite pleased with the readability of the following code. Let me take a shot at trying to explain how it works.

main = mapM_ (putStrLn . fizzbuzzer) [1..100]

fizzbuzzer number | mod number 15 == 0 = "FizzBuzz"
                  | mod number 3  == 0 = "Fizz"
                  | mod number 5  == 0 = "Buzz"
                  | otherwise = show number

I'm not too fond of the mod number 15 part in there, but I'll explain why I think it can't be done without.

mapM_: Map each element of a structure to a monadic action, evaluate these actions from left to right, and ignore the results[1]. mapM would work here as well, except we don't care about the output anyway. Right?

A monadic action is required because I'm directly handling the I/O and all I/O is considered "impure" by Haskell. Everything impure should be wrapped in a Monad.

Basically, I iterate over every number in the range of 1 to 100 inclusive and put it in fizzbuzzer. Depending on whether the number is a multiple of 3, 5, 15 or none of those, a String is selected. This get's pushed into putStrLn which outputs the String. Because only one response can be selected, the output for being divisible by 15 has to be explicitly mentioned.

I think using pattern guards like I did here is idiomatic. It feels extensible, and that's a good thing for future Haskell solutions. Feel free to poke any holes in my code and/or theory.

\$\endgroup\$
  • 3
    \$\begingroup\$ in terms of how i'd actually write fizzbuzz I think its fine, if you want to go extreme you can get rid of the 15 case though themonadreader.files.wordpress.com/2014/04/fizzbuzz.pdf \$\endgroup\$ – jk. Sep 2 '16 at 14:46
  • 1
    \$\begingroup\$ Just to be totally clear, your code doesn’t use pattern guards at all, just regular guards. Your code looks fine, though it’s hard to judge much from contrived examples like FizzBuzz. \$\endgroup\$ – Alexis King Sep 2 '16 at 16:47
4
\$\begingroup\$

First things first: is this a good variant of FizzBuzz? Well, yes. I would probably write the same variant, except for whitespace and types.

In your usual Haskell code, you want to annotate the top-level bindings with their types:

main :: IO ()                                       --  here
main = mapM_ (putStrLn . fizzbuzzer) [1..100]

fizzbuzzer :: Int -> String                         --  here
fizzbuzzer number
  | number `mod` 15 == 0 = "FizzBuzz"               -- whitespace is personal
  | number `mod`  3 == 0 = "Fizz"                   -- preference, as is
  | number `mod`  5 == 0 = "Buzz"                   -- infix style
  | otherwise            = show number

GHC will usually infer the type correctly, but it's better to add type annotations to all top-level bindings.

Now to your remarks:

mapM_: … mapM would work here as well, except we don't care about the output anyway. Right?

Well, if you change main's type to IO (), it will not work anymore.

A monadic action is required because I'm directly handling the I/O and all I/O is considered "impure" by Haskell.

Hm. At some point, you have to use IO, at least in main. However, we could also write

main = putStr (unlines (map fizzbuzzer ([1..100])))

where we have only one IO. Either way, I'm not 100% happy with your second sentence:

Everything impure should be wrapped in a Monad.

Yes and no. Monads in Haskell just provide an abstraction to chain operations together. There are many monads where you can "escape" back to your usual world, e.g. the Identity monad:

newtype Identity a = Identity { runIdentity :: a }

instance Functor Identity where 
  fmap f = Identity . f . runIdentity  

instance Applicative Identity where 
  pure   = Identity
  (Identity f) <*> (Identity x) = Identity (f x)

instance Monad Identity where
  x >>= f = f (runIdentity x)

The important part about IO however is, that there is no IO a -> a function(*). That's what keeps pure and impure code apart.

Either way, as I already said, your code is fine (except for missing type signatures). Note that you've only used "guards", though, not pattern guards, since you do not actually use a pattern in your code, just boolean expressions.

(*): well, there is one, but you should only ever use it if you know completely what you're doing

\$\endgroup\$
3
\$\begingroup\$

It's considered an element of good style (and often assists with type inference) to give type signatures for all of your top-level definitions.

main :: IO ()
fizzbuzzer :: Int -> String

@jk mentioned my favorite paper on FizzBuzz in a comment to your question for the most principled method of eliminating the 15 case, but there are of course less fancy methods of achieving the same effect. One such method comes from the common interpretation of an empty list as failure.

fallback :: Int -> String -> String
fallback n s | null s    = show n
             | otherwise = s

Building s is then a matter of testing the individual divisors and constructing the string piecewise. Note that the empty list is again useful due to its function as an identity element (any list appended or prepended with the empty list gives you back the original list).

fizz, buzz :: Int -> String -> String
fizz n s | n `mod` 3 == 0 = s ++ "Fizz"
         | otherwise      = s

buzz n s | n `mod` 5 == 0 = s ++ "Buzz"
         | otherwise      = s

But of course we want to repeat ourselves as little as possible, so we write a function that produces functions.

test :: Int -> String -> (Int -> String -> String)
test modulus value n s
  | n `mod` modulus == 0 = s ++ value
  | otherwise            = s

fizz = test 3 "Fizz"
buzz = test 5 "Buzz"

Now we combine our small, single-purpose functions back into one describing our original problem.

fizzbuzzer :: Int -> String
fizzbuzzer n = fallback n . buzz n . fizz n $ ""

This solution is about ¾ of the way toward what you'll find in the above paper. Definitely give it a read if you followed this far, it's very accessible!

As an exercise, consider adding additional tests (e.g., numbers divisible by 7 should produce “Hiss”) under both your original solution and mine.

\$\endgroup\$
1
\$\begingroup\$

I'm late to the party here, but I thought it'd be interesting to consider some more optimisation you could do. First let's consider the number of 'modulo operations' your code does per 15 integers:

  • Integers 0 through 14 all undergo the initial check for mod 15
  • Integers 1 through 14 all undergo the check for mod 3
  • Integers 1,2,4,5,7,8,10,11,13 and 14 undergo the check for mod 5

This makes a sum of 15 + 14 + 10 = 39 modulo operations per 15 numbers. Can we do better?


It won't be very often that a number is 0 mod 15, so let's tuck that mod 15 check away so that it only fires if the number passes the 0 mod 3 check first:

fizzbuzzer number | mod number 3  == 0 = if (mod number 15)==0 then "FizzBuzz" else "Fizz"
                  | mod number 5  == 0 = "Buzz"
                  | otherwise = show number

Now we have

  • Integers 0 through 14 all undergo the mod 3 check.
  • Integers 0,3,6,9, and 12 undergo the mod 15 check.
  • Integers 1,2,4,5,7,8,10,11,13 and 14 undergo the mod 5 check.

That's 15 + 5 + 10 = 30 modulo operations per 15 numbers. How about if we swap the mod 5 and mod 3 checks?


fizzbuzzer number | mod number 5  == 0 = if (mod number 15)==0 then "FizzBuzz" else "Buzz"
                  | mod number 3  == 0 = "Fizz"
                  | otherwise = show number
  • Integers 0 through 14 all undergo the mod 5 check.
  • Integers 0, 5 and 10 undergo the mod 15 check.
  • Integers 3,4,6,7,8,9,11,12, 13 and 14 undergo the mod 3 check.

That's 15 + 3 + 10 = 28 modulo operations per 15 numbers.


Now to be fair, I don't know what the bottleneck in this code actually is, and I'm sure your application probably doesn't call for such niggly optimisation, but I still think it's interesting. I'd wager that the third version is the fastest.

\$\endgroup\$
  • \$\begingroup\$ I don't know whether it's useful either, but it's definitely interesting and good to know. \$\endgroup\$ – Mast Sep 27 '16 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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