Is there any ppx to reduce the boilerplate from translating from one record to another?

Sounds simple enough. I’ll take a look at it, thanks

Oh hm, maybe this is an even simpler path to solve your problem. Here’s a little experiment:

# module M1 = struct
type t = { a :int ; b : string } 
end ;;

module M1 : sig type t = { a : int; b : string; } end
# module M2 = struct
type t = { a :int ; b : string ; c : float } 
end ;;

module M2 : sig type t = { a : int; b : string; c : float; } end
# let migrate =  fun {M1.a;b} -> let c = 4.0 in {M2.a;b;c};;
val migrate : M1.t -> M2.t = <fun>

So the function that maps a record of type M1.t to a record of type M2.t looks remarkably like the record-type itself with just the field-labels preserved. One could imagine a little PPX rewriter that did just that rewrite: rewrite a type to a pattern, and another one that did so to an expression, as in the manner of the function in the last expression above. Then combining these together, would get you your transformer, viz. migrate above. One could imagine it as:

let migrate = fun [%recty: M1.t] -> let c = 4.0 in [%recty: M2.t] ;;

or maybe better (to reuse ppx_import to get the types copied-in)

let migrate = fun [%recty: [%import: M1.t]] -> let c = 4.0 in [%recty: [%import: M2.t]] ;;

These might be simple enough to implement easily with PPX rewriters.