David 天宇 Wong
David 天宇 Wong

Reputation: 4187

In OCaml, what does aliasing a module do exactly?

In OCaml, to bring another module in scope you can use open. But what about code like this:

module A = struct
  include B.C

  module D = B.E
end

Does this create an entirely new module called A that has nothing to do with the modules created by B? Or are the types in B equivalent to this new structure and can a type in A.t can be used interchangeably with a type in B.C.t for example?

Especially, comparing to Rust I believe this is very different from writing something like

pub mod a {
  pub use b::c::*;
  pub use b::e as d;
}

Upvotes: 1

Views: 992

Answers (1)

ivg
ivg

Reputation: 35210

Yes, module A = struct include B.C end creates an entirely new module and exports all definitions from B.C. All abstract types and data types that are imported from B.C are explicitly related to that module.

In other words, suppose you have

module Inner = struct
  type imp = Foo
  type t = int
end

so when we import Inner we can access the Inner definitions,

module A = struct
  include Inner
  let x : imp = Foo
  let 1 : t = 1
end

and the Foo constructor in A belongs to the same type as the Foo constructor in the Inner module so that the following typechecks,

A.x = Inner.Foo

In other words, include is not a mere copy-paste, but something like this,

module A = struct 
  (* include Inner expands to *)
  type imp = Inner.imp = Foo
  type t = Inner.t = int
end

This operation of preserving type equalities is formally called strengthening and always applied when OCaml infers module type. In other words, the type system never forgets the type sharing constraints and the only way to remove them is to explicitly specify the module type that doesn't expose the sharing constraints (or use the module type of construct, see below).

For example, if we will define a module type

module type S = sig 
  type imp = Foo
  type t = int
end

then

module A = struct 
  include (Inner : S)
end

will generate a new type foo, so A.Foo = Inner.Foo will no longer type check. The same could be achieved with the module type of construct that explicitly disables module type strengthening,

module A = struct
  include (Inner : module type of Inner)
end

will again produce A.Foo that is distinct from Inner.Foo. Note that type t will be still compatible in all implementation as it is a manifest type and A.t is equal to Inner.t not via a sharing constraint but since both are equal to int.

Now, you might probably have the question, what is the difference between,

module A = Inner

and

module A = struct include Inner end

The answer is simple. Semantically they are equivalent. Moreover, the former is not a module alias as you might think. Both are module definitions. And both will define a new module A with exactly the same module type.

A module alias is a feature that exists on the (module) type level, i.e., in the signatures, e.g.,

module Main : sig 
  module A = Inner (* now this is the module alias *)
end = struct 
  module A = Inner
end

So what the module alias is saying, on the module level, is that A is not only has the same type as Inner but it is exactly the Inner module. Which opens to the compiler and toolchain a few opportunities. For example, the compiler may eschew module copying as well as enable module packing.

But all this has nothing to do with the observed semantics and especially with the typing. If we will forget about the explicit equality (that is again used mostly for more optimal module packing, e.g., in dune) then the following definition of the module A

module Main = struct 
  module A = Inner
end

is exactly the same as the above that was using the module aliasing. Anything that was typed with the previous definition will be typed with the new definition (modulo module type aliases). It is as strong. And the following is as strong,

module Main = struct 
  module A = struct include Inner end
end

and even the following,

module Main : sig 
 module A : sig 
   type imp = Impl.imp = Foo
   type t = Impl.t = int
 end
end = struct 
  module A = Impl
end

Upvotes: 5

Related Questions