8
\$\begingroup\$

CodeEval FizzBuzz

Quick Description:

Take 1 command line argument of a file-path. This file will contain test cases formatted as such 3 space separated numbers per line. For numbers X, Y and Z you must print out all numbers between 1 and Z, space separated, replacing all numbers divisible by X with 'F' and all numbers divisible by Y with 'B'.

I am entirely new to Functional programming, and my Haskell knowledge is from a single Pluralsight class, so I am looking for any amount of feedback, particularly for best practices. I also feel like the actual fizzBuzzSingle function can be solved in a list comprehension instead, but I'm still uncomfortable with the syntax so I didn't use one.

If my code is reading too object-oriented please let me know. I also did look up the other FizzBuzz questions on this site, but they all seem to not being handling file input, which seemed the hardest part to me, so it seemed worth posting this.

import System.Environment

fizzBuzzSingle :: Int -> Int -> Int -> String
fizzBuzzSingle f b n
    | n `mod` f == 0 && n `mod` b == 0  = "FB"
    | n `mod` f == 0                    = "F"
    | n `mod` b == 0                    = "B"
    | otherwise                         = show n

fizzBuzz :: (Int, Int, Int) -> [String]
fizzBuzz (f,b,end) = map (fizzBuzzSingle f b) [1..end]

convertInputLine :: String -> (Int, Int, Int)
convertInputLine x = packageInputs ((map read . words) x :: [Int])

packageInputs :: [Int] -> (Int, Int, Int)
packageInputs [f,b,end] = (f, b, end)

concatOutput :: [String] -> String
concatOutput (x : xs) =
    foldl (\x y -> x ++ " " ++ y) x xs

handleFizzBuzz :: [String] -> [String]
handleFizzBuzz [] = []
handleFizzBuzz (x : xs) =
    (concatOutput . fizzBuzz . convertInputLine $ x) : handleFizzBuzz xs

main :: IO ()
main = do
    args <- getArgs
    let path = args !! 0
    file <- readFile path
    putStrLn . unlines . handleFizzBuzz . lines $ file
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Some functions in the Prelude can be of help. One is map, which allows you to write a simpler handleFizzBuzz. Another is unwords, which is equivalent to your concatOutput.

handleFizzBuzz :: String -> String
handleFizzBuzz =
    map $ unwords . fizzBuzz . convertInputLine

main :: IO ()
main = do
    args <- getArgs
    let path = args !! 0
    file <- readFile path
    putStrLn . unlines . map handleFizzBuzz . lines $ file

Another is <>, the simple monoidal concat. As both String is a Monoid and (Monoid a) => Maybe a is a Monoid, so is Maybe String. It lets us eliminate two cases in fizzBuzzSingle if we also return a Maybe:

fizzBuzzSingle :: Int -> Int -> Int -> Maybe String
fizzBuzzSingle f b n =
    ("F" <$ guard (mod n f == 0)) <> ("B" <$ guard (mod n b == 0))

The <$ is a useful operator from Data.Functor. Its implementation is something like b <$ fa = fmap (\_ -> b) fa. guard returns a Nothing when our divisibility checks return False, which is perfect for us since that means "F" <$ Nothing evaluates to Nothing.

But, as we're now in Maybe-land, we need to discharge it. In the case where a number is divisible by either divisor, we just want to print the number. So we can turn to fromMaybe from Data.Maybe:

fizzBuzz :: [Int] -> [String]
fizzBuzz [f, b, end] =
    map (\n -> fromMaybe (show n) (fizzBuzzSingle f b n)) [1 .. end]

But we notice that the inner closure can be rewritten as \n -> (fromMaybe . show $ n) (fizzBuzzSingle f b $ n). That seems useless, but it's exactly like the implementation of the applicative sequencing operator for (->) r.

fizzBuzz :: [Int] -> [String]
fizzBuzz [f, b, end] =
    map (fromMaybe . show <*> fizzBuzzSingle f b) [1 .. end]

This is a pretty esoteric trick. Applicative is already somewhat of a stumper, but I think there's something about (->) r that makes its Applicative instance especially difficult to understand.

But, if we elect to use this trick and combine it with everything else, the program boils down to:

import Data.Monoid
import Data.Maybe
import Control.Monad
import System.Environment

fizzBuzzSingle :: Int -> Int -> Int -> Maybe String
fizzBuzzSingle f b n =
    ("F" <$ guard (mod n f == 0)) <> ("B" <$ guard (mod n b == 0))

fizzBuzz :: [Int] -> [String]
fizzBuzz [f, b, end] =
    map (fromMaybe . show <*> fizzBuzzSingle f b) [1 .. end]

main :: IO ()
main = do
    args <- getArgs
    file <- readFile (head args)
    (putStrLn . unlines . map (unwords . fizzBuzz . map read . words) . lines) file

I think there's something to be said for simplicity. I think your pattern matching makes for a more readable fizzBuzzSingle at the end of the day, though this implementation easily extends if you want to test three or four or five divisors. Anyway, monoids and functors and applicatives, oh my!

Credit is due: I saw this originally on /r/haskell.

\$\endgroup\$
1
  • \$\begingroup\$ Wow. you used a lot of constructs I just didn't know existed. I'll have to spend some time figuring out exactly what a monad is and a couple of your other functions. Would have been nice to know about unwords. It aalso worth noting my original attempt had something similar to the long chain of methods you have in the main funtion (prelude?) but I couldn't figure out the formaat to make it work, so I broke it down into multiple smaller calls for simplicity for myself. I'll take an hour or two to figure out what you're doing here though for sure. Thanks. \$\endgroup\$ Commented Dec 3, 2015 at 12:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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