You can't have a variadic function like this in OCaml because every tuple is a distinct type, and OCaml functions are statically typed with those enforced at compile-time. Polymorphism doesn't affect the arity of tuples.
Maybe with a PPX you could generate functions like apply_tuple3 where you might write something like tuple |> curried_func [@apply_tuple 3]
But you'd really want to ask if you have an XY problem in that event and why you want to do this. It'd be helpful to see a real world situation where you'd want this so we might make suggestions on other approaches that are more idiomatic.
Note that ChatGPT's helper function is needlessly verbose with type annotations when you can simply write:
let apply_tuple3 f (a, b, c) = f a b c
You might also call these functions uncurry2, uncurry3, etc.
Beware that in trying to do something like this you aren't hurting the expressiveness of your code. For instance, if I were writing one of the "students' grades" programs that's typical in an intro class, I might have a function which returns a student's name, grade level, and numeric grade as a string * int * int tuple.
It's tempting to want to write something like the following.
# let get_info () = ("Bob", 11, 86);;
val get_info : unit -> string * int * int = <fun>
# get_info ()
|> uncurry3 @@ Format.printf "%s: Grade %d, Score %d%%\n";;
Bob: Grade 11, Score 86%
- : unit = ()
But if we give these things names, it becomes more clear to the next person (which might be you!) what each piece of info coming back from get_info () is.
# let (name, grade_level, score) = get_info () in
Format.printf "%s: Grade %d, Score %d%%\n" name grade_level score;;
Bob: Grade 11, Score 86%
- : unit = ()
Is the first example better than the second?