I would recommend, as a rule of thumb, to never put tuples in the arguments of a function signature.
Why? Well, if the point is to group stuff together, then tuples do a rather measly job at it. Sure, you could use nested tuples and type synonyms to explain what they mean, but all of that is brittle and much better and safer done with proper record types. As you've identified, the x- and y-components of a vector usually come together. Well, not only that, in many a sense it is a good idea to keep the x- and y-components completely hidden from any interesting code. That's exactly what the Vector type should accomplish. (Which should probably be called Vector3 or ℝ³ instead.) And the only purpose of the vector function should be to assemble one of those from the components. Well, if that's the only thing it does, then the three components are the only arguments, and there's no point grouping them together any further... that's basically just putting a single suitcase into another transport box. Better just use the right container right away as a single wrapper.
vector3 :: Float -> Float -> Float -> Vector3
An example of a tuple in a signature of a commonly used function is
randomR :: (Random a, RandomGen g) => (a,a) -> g -> (a,g)
Why is this a bad idea? Well, you're using a tuple to denote an interval... but also in the result to denote something completely different, a grouping of the obtained random value with the updated generator. The proper way to do this is to either have a type that properly expresses what it is
data Interval a = Interval {lowerBound, upperBound :: a}
randomR :: (Random a, RandomGen g) => Interval a -> g -> (a,g)
...or better, separate the concerns, i.e. that manual state-threading should be hidden in a suitable monad – such as RVar. At that point the range limits become the only arguments, thus you don't need to group them together anymore!
uniform :: Distribution Uniform a => a -> a -> RVar a
That doesn't mean you should never use tuples at all. For result values, the currying mechanism doesn't work as easily†, so if you have a function that gives back two results but there's not really any meaningful interpretation for what those two values represent together, well, give back a tuple.
Furthermore, if you're grouping together completely abstract types, you can't possibly have an interpretation for what they mean together. That's the reason why zip :: [a] -> [b] -> [(a,b)] gives a list of tuples.
†You can also have multi-result functions with tuples. For that, you need to use continuation-passing style, for example splitAt :: Int -> [a] -> ([a],[a]) becomes splitAt' :: Int -> [a] -> ([a] -> [a] -> r) -> r.