One strategy is to make it evident using the type system when you have “costly” partial applications (i.e., partially-applied functions that are expensive to construct because they do something with arguments as they are passed one-by-one).
For example, Base does this with a module called Staged:
type +'a t
val stage : 'a -> 'a t
val unstage : 'a t -> 'a
So for example you might have a regexp-matching function that first compiles the regular expression:
(** [does_match pattern input] *)
val does_match : string -> string -> bool
You might be tempted to use it like so, which would be expensive because it would compile the regular expression over and over again:
let nums = List.filter (fun str -> does_match "\\d+" str) many_strings in ...
does_match is changed to have the following type:
val does_match : string -> (string -> bool) Staged.t
Then it becomes obvious that partially applying the function to only its first argument is “expensive”, so then it is clear that the preceding code should be written like so, where we save the work of compilation:
let is_number = unstage (does_match "\\d+") in
let nums = List.filter is_number many_strings in
The different type makes it much more obvious when the result of partial application should not be evaluated more than once for performance (or other) reasons.