> divides m n = m `mod` n == 0We then construct a function that takes a divisor, a string, a number, and returns a string. Note that the number is the last argument.
> genWord :: Int -> String -> Int -> StringNow the fun begins! We can generate infinite lists with "fizzes" and "buzzes", just by mapping genWord over an infinite list.
> genWord x str i
> | i `divides` x = str
> | otherwise = ""
> fizzes = map (genWord 3 "Fizz") [1..]So, for example, the first ten elements of fizzes looks like this:
> buzzes = map (genWord 5 "Buzz") [1..]
> nums = [1..]
["","","Fizz","","","Fizz","","","Fizz",""]Now, having three infinite lists, we need to combine those to one infinite list. Is this hard to digest? I guess it's not the "normal routine" that one follows when constructing imperative programs. So, how could this in any way be useful outside the esoteric world of academia? Let us talk a bit more on that after all code, focusing on one thing at a time. Here's combine:
> combine :: String -> String -> Int -> StringIn other words, we concatenate the "fizz" string with the "buzz" string and if the concatenated string is empty, we return a number instead (as a string). This makes the case of "n%15" unnecessary, which is nice. The idea for creating combine is that if we know how to combine one element, we are much closer to a solution for our problem. Though, we do restrict ourselves when defining the type for combine. The only requirement for "n" is that it is showable, but the example is just much more clearer when not using parametrized types..
> combine str1 str2 n
> | null ret = show n
> | otherwise = ret
> where ret = str1 ++ str2
A problem left to solve is the problem of having "a bunch of lists" and returning one list. The function zip does exactly that, and zip3 is function that takes three lists and returns a list of triples (source here).
From ghc's Prelude documentation:
zip3 :: [a] -> [b] -> [c] -> [(a, b, c)]Now, we could rewrite combine to take a triple instead of three arguments. Currying is perhaps not well-known concept, but essentially it is another way of applying arguments to a function:
zip3 takes three lists and returns a list of triples, analogous to zip.
f(x,y) // Normal style (un-curried)
f x y -- Curried
So, we define a function, uncurry3, which takes a function (with three arguments) as the first argument, and returns a function which takes one argument, a triple. A funny thing is that the type declaration is longer than the implementation:
> uncurry3 :: (a -> b -> c -> d) -> ((a, b, c) -> d)Here's the "grande finale"! We apply fizzes, buzzes and nums to zip3, giving an infinite list of triples. We then map the uncurried function (of the "third order") on that infinite list, giving an infinite list of fizzbuzzes with numbers in between.
> uncurry3 f (a,b,c) = f a b c
> resultInf :: [String]Finally, we take the first 100 elements of the infinite list, and print it to stdout.
> resultInf = map (uncurry3 combine) (zip3 fizzes buzzes nums)
> result100 :: [String]
> result100 = take 100 resultInf
> printResult :: IO ()
> printResult = mapM_ putStrLn result100