Using constructors defined in a recursive module outside of it

I’m implementing a simple stack machine. It has two main mutually recursive concepts Instruction and Value. And I want to write something like that:

module type SMConcept = sig
  type t
  val dump : t -> string 
end

module rec Instruction : SMConcept = struct
  type t =  
  | Id
  | Quote of { v : Value.t } 
  | ...

  val dump = (* trivial implementation *)
end 

and Value : SMConcept = struct
  type t = 
  | Unit
  | Clos of { v : Value.t ; p : Instruction.t }
  | ... 

  val dump = (* trivial implementation *) 
end

My problem is that I can’t use constructors for Instruction.t and Value.t outside these modules. In particular, I need to create and eliminate with pattern-matching machine’s instructions in the interpreter’s implementation. I understand that it’s because the signature of SMConcept makes the type t abstract. But it’s strange for me to implement the interpreter in the Instruction module. And I can’t do without signatures since recursive modules don’t work without them. Also, these types are very different, so it’s impossible to write a type definition in SMConcept. And the last limitation is I would like to do without using polymorphic variants.

These are probably problems with my architecture, but I’m interested to know
is it possible to implement what is described above. :sweat_smile:

I understand that it’s because the signature of SMConcept makes the type t abstract.

Your core problem is here. The SMConcept module type will always remove the variants and prevent you from constructing or pattern matching on them.

The solution is to just add the full types to the module signature:

module rec Instruction : sig
  type t =
    | Id
    | Quote of { v : Value.t }

  val dump : t -> string
end = struct
  type t =
    | Id
    | Quote of { v : Value.t }

  let dump = (* ... *)
end

and Value : sig
  type t =
    | Unit
    | Clos of { v : t ; p : Instruction.t }

  val dump : t -> string
end = struct
  type t =
    | Unit
    | Clos of { v : t ; p : Instruction.t }

  let dump = (* ... *)
end

But this is a bit messy in my opinion. I try to avoid recursive modules when possible.

My suggestion is to bring the types and functions to the top level of the file, and add modules after for organization:

type instruction =
  | Id
  | Quote of { v : value }

and value =
  | Unit
  | Clos of { v : value ; p : instruction }

let rec dump_instruction = (* ... *)

and dump_value = (* ... *)

module Instruction = struct
  type t = instruction =
    | Id
    | Quote of { v : value }

  let dump = dump_instruction
end

module Value = struct
  type t = value =
    | Unit
    | Clos of { v : t ; p : instruction }

  let dump = dump_value
end

The type t = instruction = ... makes Instruction.t equal to instruction while still letting you construct variants like Instruction.Id.

1 Like