A simple one would be
combine :: [Char] -> [String] -> [String]
combine [] _ = []
combine _ [] = []
combine (c:cs) (x:xs) = x ++ [c] : combine cs xs
Or even more simply using zipWith
combine :: [Char] -> [String] -> [String]
combine = zipWith (\c x -> x ++ [c])
I had to do a bit extra to get this to work. I'll break it down for you.
First, I specified the type of the function as [Char] -> [String] -> [String]. I could have used String for the first argument, but what you're operating on conceptually is a list of characters and a list of strings, not a string and a list of strings.
Next, I had to specify the edge cases for this function. What happens when either argument is the empty list []? The easy answer is to just end the computation then, so we can write
combine [] _ = []
combine _ [] = []
Here the _ is matching anything, but throwing it away because it isn't used in the return value.
Next, for the actual body of the function We want to take the first character and the first string, then append that character to the end of the string:
combine (c:cs) (x:xs) = x ++ [c]
But this doesn't do anything with cs or xs, the rest of our lists (and won't even compile with the type signature above). We need to keep going, and since we're generating a list, this is normally done with the prepend operator :
combine (c:cs) (x:xs) = x ++ [c] : combine cs xs
However, this is such a common pattern that there is a helper function called zipWith that handles the edge cases for us. It's type signature is
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
It walks down both input lists simultaneously, passing the corresponding elements into the provided function. Since the function we want to apply is \c x -> x ++ [c] (turned into a lambda function), we can drop it in to zipWith as
combine cs xs = zipWith (\c x -> x ++ [c]) cs xs
But Haskell will let us drop arguments when possible, so we can eta reduce this to
combine :: [Char] -> [String] -> [String]
combine = zipWith (\c x -> x ++ [c])
And that's it!
... = x ++ [y] : combine ys xs