The short answer
In OCaml, transparent ascription (M : S) hides any value, type, or module not named in S, but it keeps the identity of every type that S does mention — so if S exposes type t = int, the outside world still knows M.t = int. Opaque ascription (M :> S) goes further: it makes every type in S abstract, severing the connection between M.t and its definition. The decision criterion is therefore not "hide or show" but "does client code legitimately need to know what this type is?"
Criteria for choosing opaque ascription
Choose :> when all of the following hold:
- The type's representation is an implementation detail you want freedom to change (e.g., an association list today, a
Map tomorrow). - Invariants are maintained by construction functions, and pattern-matching from outside would let clients break them.
- No client needs to marshal, compare, or construct values of the type except through the module's API.
Keep transparent : when the type's equality with a concrete representation is part of the contract — for example, a module whose t is deliberately int so clients can use arithmetic, or internal functor instantiations where type equalities must propagate for the code to type-check at all. This is the main reason flipping the default to opaque would break real codebases: a lot of existing code silently depends on those propagated equalities, and the failure mode is a type error far from the ascription site.
Private types: the middle ground
Before reaching for full opacity, consider private types, which answer the second part of your question directly. A signature can expose type t = private int or a private record:
module type S = sig
type t = private { id : int; name : string }
val make : id:int -> name:string -> t
end
module M : S = struct
type t = { id : int; name : string }
let make ~id ~name = { id; name }
end
Under transparent ascription, the private modifier is still enforced by the signature: external code can read m.M.id and pattern-match, but cannot construct or mutate the record — construction is only possible inside the module. So transparent ascription plus private fields already gives you read-only external access with write encapsulation, without paying the full cost of opacity. Opaque ascription on the same signature would additionally hide the fact that t is a record at all, removing even read access to the fields.
Practical verification
Two quick checks confirm what an ascription actually exposes:
- Ask the toplevel or
ocamlc -i for the inferred interface of the ascribed module and confirm which type equalities survive. - Write a small client module that attempts the access you want to forbid (constructing the record, or relying on
t = int) and confirm it fails to compile.
ocamlc -i my_module.ml # shows the effective signature
Caveats
One nuance worth flagging for review: opaque ascription is applied at the ascription site, so the same implementation can be exposed transparently through one alias and opaquely through another — encapsulation is only as strong as the most permissive alias you publish. Also note this answer reflects long-standing OCaml behavior (4.x, including the private-type machinery introduced in 4.01); the "change the default" discussion is a language-design proposal, not shipped behavior, so verify against your compiler version before relying on any change.