Any solution should somehow generalize the tuples, because by default they are just disjoint types. The most common solution would be to use typeclasses to index the idea of ββtypes that have a "first element", such as Control.Lens or Data.Tuple.Select .
class Sel1 ab | a -> b where sel1 :: a -> b instance Sel1 (a1,a2) a1 where sel1 (x,_) = x instance Sel1 (a1,a2,a3) a1 where sel1 (x,_,_) = x instance Sel1 (a1,a2,a3,a4) a1 where sel1 (x,_,_,_) = x ...
or
instance Field1 (Identity a) (Identity b) ab where _1 f (Identity a) = Identity <$> indexed f (0 :: Int) a instance Field1 (a,b) (a',b) aa' where _1 k ~(a,b) = indexed k (0 :: Int) a <&> \a' -> (a',b) instance Field1 (a,b,c) (a',b,c) aa' where _1 k ~(a,b,c) = indexed k (0 :: Int) a <&> \a' -> (a',b,c) instance Field1 (a,b,c,d) (a',b,c,d) aa' where _1 k ~(a,b,c,d) = indexed k (0 :: Int) a <&> \a' -> (a',b,c,d) ...
In both cases, consider the type of your function, it will need to indicate a way for your first argument to be "the kind of thing with the first element."
test :: (Sel1 s A) => s -> ... test :: (Field1 st A b) => s -> ...
You can also follow the fixed-vector route and consider tuples as short homogeneous vectors. You lose the ability to act on heterogeneous vectors, but you get neat types (but ugly values) like
test :: (Vector v A, Index N1 (Dim v)) => v A -> ... test v = let (A a) = index (1,2) (undefined :: Z) in ...
although for all his magic he still does this work through class classes.