Generalizing code of many handlers

For example I have a set of different handlers like that:

let  handlermod1 ~data ~info ~outchannel =
      let typ1_opt = MyModule1.of_bigarray ~data ~info in
      match typ1_opt with
      | Some typ1 -> (
              let result = MyModule1.some_more_processing ~data ~info typ1 in
              match result with
              | true ->
                    {
                          status = Process_Success;
                          consumed = 0;
                    }
              | false ->
                    {
                          status = Process_Failure;
                          consumed = 0;
                    }
      )
      | None -> {
          status = Process_WrongData;
          consumed = 0;
      }

And I have many-many handlers like this, then filling a list of pairs like:

let handlers = [
     (Typ1, handlermod1);
     (Typ2, handlermod2);
     ...
]

Along with this I have many-many different modules like MyModule1, MyModule2, etc.
So instead of copy-pasting the code I want to generalize the code for most of the handlers, using “MyModule1” as an argument to some Functor maybe? Can you help me to understand how to perform this?

Once you have written the common signature S of your set of module, probably something like

module type S =  sig
  type intermediary
  val of_bigarray: data:data -> info:info -> intermediary
  val some_more_processing:
     data:data -> info:info -> intermediary -> result
end

writing a functor should be as simple as wrapping your code within

module Make(MyModule:S) = struct
  let  handler ~data ~info ~outchannel = ...
end

But then you need to instantiate the functor for each module:

module M1 = Make(MyModule1)
module M2 = Make(MyModule2)
let handlers = [ M1.handler; M2.handler ]

Another option might be to use a first class module argument in handlermod:

let  make (module MyModule:S) ~data ~info ~outchannel = ...
let handlers = [ make (module MyModule1); make (module MyModule2) ] 

Note that here, the first class module is used to bundle a bunch of functions together,
thus using a record of functions works too. However, we need to avoid exposing the intermediary type. This can be done by returning the closure some_more_processing typ1 rather than typ1_opt:

type of_bigarray =
    data:data -> info:info -> (data:data -> info:info -> result) option

Yet another option, is to keep a simple record of functions, and use a GADT to make to existentially quantify the intermediary type:

type 'a simple =  {
  of_bigarray: data:data -> info:info -> 'a option;
  some_more_processing: data:data -> info:info -> 'a -> result
}
type bundle = Bundle: 'a simple -> bundle [@@unboxed]

but this is essentially a first class module in disguise.

2 Likes