Path expression evaluation?

I’m confused as to what happens to the path expression in the following at runtime?

let g () =
  let r = M.N.f x in 
  ....

I thought that in lambda land M.N.f would be evaluated to head
normal form, a Closure_tag value on the heap, and that subsequent uses of g would not need to re-evaluate the path expression M.N.f again.

but after playing with dynlink modules I think that may not be the case after all, that in fact the path expression is eval’d each and every time anew.

if that is true, that doesn’t look good from a performance perspective.
am I misunderstanding matters?

A module declaration should behave like a toplevel statement in the compiler internals because they describe immutable things:

module S = (* module *)
let x = (* value *)

As neither M, N, nor f are going to change during the execution of the program, the compiler should be clever enough to identify a unique address for f and jumping to it everytime f is called in the source code. Therefore, I am quite surprised by your findings and would like to know more about this.

Note that I am just an OCaml enthusiast, and I know very few things about the internal stuff. Thanks for asking this interesting question, I’ll read the later (and more informed!) answers carefully.

At runtime, modules are compiled like records with one field per value. So your “path expressions” are just like record field accesses.

The OCaml compiler has a very predictable compilation scheme and does not try to be overly clever (at least in the non-FLAMBDA case). In particular it won’t lift the expression M.N.f outside of g to avoid the re-evaluation. If you want that, you can do it yourself:

let g =
  let f = M.N.f in
  fun () ->
    let r = f x in
    ...

But really I would be surprised if this made the slightest difference performance-wise.