Function composition that doesn't care about arity

I’m writing a web API wrapper where I have a bunch of functions that return cohttp responses. I’d like to be able to expose both these “lowlevel” functions directly and versions that parse the responses into more user-friendly values. I’d like to do this without having to rewrite the function arguments for each pair of functions - I could write let f x y z = [stuff returning cohttp response] and then let f' x y z = f x y z |> [parser], but that gets tedious with lots of functions to cover.

Conceptually, I’d like something with a type of ('b -> 'a) -> ('c_1 -> ... -> 'c_n -> 'b) -> 'c_1 -> ... -> 'c_n -> 'a, but I don’t believe I can write that function in OCaml. Is there some other technique that would work well here?

I have something working right now where I pass a continuation into each of the “lowlevel” functions and pass the cohttp response into the continuation, but I’ve had to do some extra wrangling with optional arguments and the value restriction after making this change. I’m wondering if there are better alternatives.

There are a few possibilities.

  1. Use tupled argument lists. Functions with tupled arguments compose the same whatever the length of the tuple. This has the disadvantage that tupled arguments are not idiomatic OCaml.

  2. Write combinators for compose2, compose3 and use the appropriate one at each site. I would personally consider this the most promising, although there will be limitations on things like labelled and optional arguments:

    let compose1 f g = fun x -> f (g x)
    let compose2 f g = fun x y -> f (g x y)
    let compose3 f g = fun x y z -> f (g x y z)
    
  3. Just write the boilerplate. At least it will be straightforward.

4 Likes

You can use a GADT representation of a natural number that caries enough type-information to have a generic compose arity f g that covers all these case.

type (_, _, 'b1, 'b2) lift =
  | Z : ('b1, 'b2, 'b1, 'b2) lift
  | S : ('t1, 't2, 'b1, 'b2) lift -> ('c -> 't1, 'c -> 't2, 'b1, 'b2) lift

let rec compose : type t1 t2 a b. (t1, t2, a, b) lift -> (a -> b) -> t1 -> t2 =
  fun arity f g ->
    match arity with
      | Z -> f g
      | S n -> fun x -> compose n f (g x)

# compose (S (S Z)) string_of_int (+) 1 2;;
- : string = "3"
11 Likes