HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

A guarded FizzBuzz

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
guardedfizzbuzzstackoverflow

Problem

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.

Solution

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

Code Snippets

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
main = putStr (unlines (map fizzbuzzer ([1..100])))
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)

Context

StackExchange Code Review Q#140294, answer score: 4

Revisions (0)

No revisions yet.