Marshal determinism and stability

I don’t know about “minimal boilerplate”, but we do something along the lines of:

type comp =
  EFun : { key: string; name: string; f : 'arg -> 'res } -> comp

let registered : (string, comp) Hashtbl.t = Hashtbl.create 10

let register ~__LOC__ ~name f =
  let key = Digest.to_hex (Digest.string (name, __LOC__)) in
  let comp = EFun {key; name; f} in
  Hashtbl.replace registered key comp;
  comp

Then you can register a function by doing

let () =
  register ~__LOC__ ~name:"hello" (fun () -> print_endline "Hello!")

and can identify function by their key fields.

(This is just a sketch, at LexiFi we use features of our fork of the OCaml compiler to implement this more ergonomically, but the idea is the same.)

Unfortunately, this code is not public.

Cheers,
Nicolas

We have similar registration code in Coq, eg https://github.com/coq/coq/blob/cb76d6aaaa6fec0a919a459992f91a13552dbc96/pretyping/coercion.ml#L629-L640


type hook = env -> evar_map -> flags:Evarconv.unify_flags -> constr ->
  inferred:types -> expected:types -> (evar_map * constr) option

let all_hooks = ref (CString.Map.empty : hook CString.Map.t)

let register_hook ~name ?(override=false) h =
  if not override && CString.Map.mem name !all_hooks then
    CErrors.anomaly ~label:"Coercion.register_hook"
      Pp.(str "Hook already registered: \"" ++ str name ++ str "\".");
  all_hooks := CString.Map.add name h !all_hooks

let active_hooks = Summary.ref ~name:"coercion_hooks" ([] : string list)

The value of a Summary.ref can get sent to forks through Marshal (no closures) so needs to be marshal safe, and is related to the closures through the all_hooks map.
Registration is just let () = register_hook ~name:"whatever" the_fun eg https://github.com/LPCIC/coq-elpi/blob/7a5e4bc31dbff3c4ee66a465ddc43388564ebd46/apps/coercion/src/coq_elpi_coercion_hook.mlg#L41

Unlike lexifi we don’t use __LOC__, so the name needs to be globally unique.