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
Zyou must print out all numbers between 1 and
Z, space separated, replacing all numbers divisible by
and all numbers divisible by
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
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
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
<>, 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
fizzBuzzSingle :: Int -> Int -> Int -> Maybe String fizzBuzzSingle f b n = ("F" <$ guard (mod n f == 0)) <> ("B" <$ guard (mod n b == 0))
<$ 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
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
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
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