Repeated 'module type' declarations in .mli and .ml

Hello,
I have read another thread about this. The following declaration is repeated in the

.mli and .ml. The line module RADIXOp : RADIXOperator is only in the .mli.

open Types

module type RADIXOperator = sig
type 'a radix_node = 'a
include  module type of MakeRadixNode ( struct type 'a t = 'a radix_node end )
val new_node4 : unit → meta * node_type * bytes list * node array
val new_node16 : unit → meta * node_type * bytes  list * node array
val add_child : bytes → meta * node_type * bytes   list * node array → 
node →  meta * node_type * bytes   list * node array
val insert_tree :  tree → Bytes.t list →  int64 → node
val search_after_terminating : node → Bytes.t list  → int → int64 option

end

module RADIXOp : RADIXOperator

Should this be duplicated ? Is my code not adhering to the standards ?

I haven’t shown the full ADT as this compiles and works as expected.


module type RadixNode = sig
type 'a t
end

 module MakeRadixNode ( RadixNode : RadixNode ) = struct
 ...
 end

Thanks.

There’s nothing wrong with your code.

If you want to avoid repeating the module type declaration, you can put it in a separate module without interface, eg: radix_operator_intf.ml. This is a commony used pattern: eg all the *_intf.ml files in base/src at master · janestreet/base · GitHub.

Cheers,
Nicolas

1 Like

To add a bit on top of Nicolas’ answer, I would also recommend this article about the “intf trick” that I found still relevant five years after its writing date.

3 Likes

Both references helped and I have a simple .mli now.

open Types

module type Intf = sig
module RADIXOp : RADIXOperator
end

And the emacs trick is useful.

Appendix B of that blog post (which is quite nice, thank you for linking) includes this snippet:

include (val (failwith "TODO") : Stack_intf.Intf)

which just about breaks my brain. Can someone walk me through how this even parses?

(val EXPR : MTY) casts an expression EXPR as a module of type MTY, then include includes the module in the current module. This passes the build and fails at runtime, exactly what is desired during the initial rapid development phase.

1 Like

It worth to note that it works because failwith (like exit) can return any type:

val failwith : string -> 'a

because it breaks the flow and the returned value has no sense. So the compiler accepts to cast (I’m not sure which term to use here) the result of failwith into a new module.

2 Likes

Thanks. I didn’t realize 'a could be unified with module types as well. Clever trick!

More precisely, first class module types are a normal type of values. In other words, in

module type T = sig end
type u = (module T)
let f (x: (module T)) (y:u) z = [x; y; z]

(module T) is a type not a module type, and the type of the value z is inferred to be (module T).

4 Likes

Alternatively, you can create an interface without a module if you’re just defining module types and types. Simply create an .mli file without an .ml and add (modules_without_implementation my_module) to your dune file.

I find it a bit cleaner than a standalone .ml since its clear it contains only interfaces and no code, but it has some drawbacks (can’t declare exceptions, can’t include it anywhere…).