Some functions in the Prelude can be of help. One is `map`, which allows you to write a simpler `handleFizzBuzz`.

<!-- language-all: haskell -->

    handleFizzBuzz :: String -> String
    handleFizzBuzz =
        concatOutput . fizzBuzz . convertInputLine

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

Another is `unwords`, which is equivalent to your `concatOutput`.

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`](https://hackage.haskell.org/package/base-4.8.1.0/docs/src/GHC.Base.html#line-617).

    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:

    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.