13
\$\begingroup\$

The core problem is simple enough, but I've developed a type system that I'm not sure about. The intention is, if the result is a number, calling num $ fizzBuzz 16, for example, lets me retrieve 16 as an Int. However, it seems awfully repetitive, to have to mention "Fizz" so many times in the code. Can it be done better?

data NoiseNum = Fizz | Buzz | FizzBuzz | Num Int
  deriving (Eq)

instance Show NoiseNum where
  show FizzBuzz = "FizzBuzz"
  show Fizz     = "Fizz"
  show Buzz     = "Buzz"
  show (Num n)  = show n

num :: NoiseNum -> Int
num (Num n) = n

fizzBuzz :: Int -> NoiseNum
fizzBuzz n
  | n `mod` 15 == 0 = FizzBuzz
  | n `mod`  3 == 0 = Fizz
  | n `mod`  5 == 0 = Buzz
  | otherwise       = Num n

main = do
  mapM_ putStrLn $ map (show . fizzBuzz) [1..100]
\$\endgroup\$
3
\$\begingroup\$

In the issue 23 of The Monad.Reader, there is a nice discussion of FizzBuzz and how to solve it efficiently in Haskell. The efficiency being in terms of future extensions such as "output Bazz if it's a multiple of 7, FizzBazz if it's a multiple of 21, (...)". The proposed solution is linear in the number of conditions.

The final answer goes something like this (but I'd advise you to go through the paper: it's a pretty nice read!):

fizzbuzz n = (test 3 "Fizz" . test 5 "Buzz") id (show n)
  where test d s x | n `mod` d == 0 = const (s ++ x "")
                   | otherwise = x
\$\endgroup\$
3
\$\begingroup\$

Copied from my earlier comment.

I think you may be feeling unsatisfied with your solution due to the negligible benefit creating a new type has gotten you. That is, you could change the fizzBuzz function to fizzBuzz :: Int -> String and not win or lose any benefits from static typing.

Have you read this paper that solves FizzBuzz by refining a solution based on a command type? FizzBuzz in Haskell by Embedding a Domain-Specific Language

Sometimes the most useful types are those a step abstracted from your actual problem. Developing an intuition as to when this is the case - and knowing what types to actually use - is a challenge! You may find some success in working through Pearls of Functional Algorithm Design by Richard Bird. Each chapter is much like the FizzBuzz paper linked above, simple but inefficient solutions are iteratively refined into equivalent, algorithmically efficient versions.

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

In this case the extra data type does nothing but complicate your code. If you change the type of fizzBuzz to Int -> String, change all the values to the actual strings, and delete everything that's no longer used, you end up with about half the code you've got now and it runs faster too.

Your main function could also be tidied up a bit.

main = mapM_ (putStrLn . fizzBuzz) [1..100]
\$\endgroup\$
  • 1
    \$\begingroup\$ I just want to emphasize 'everything that's no longer used' includes function num, which is unused code to begin with. \$\endgroup\$ – abuzittin gillifirca Aug 18 '14 at 8:06

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.