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
but the user deleted his or her comment so I'm unsure who the attribution belongs to.