Scope
After a module M has been defined, you can access the names within it
using the . operator. For example:
# module M = struct let x = 42 end;;
module M : sig val x : int end
# M.x;;
- : int = 42
You can also bring all of the definitions of a module into the current scope using
open. Continuing our example above:
# x;;
Error: Unbound value x
# open M;;
# x;;
- : int = 42
Opening a module is like writing a local definition for each name defined
in the module. open String, for example, brings all the definitions
from the String module into scope, and has an effect similar to the
following on the local namespace:
let length = String.length
let get = String.get
let lowercase_ascii = String.lowercase_ascii
...
If there are types, exceptions, or modules defined in a module, those also are
brought into scope with open. For example, if we're given this module:
module M = struct
let x = 42
type t = bool
exception E
module N = struct
let y = 0
end
end
then open M would have an effect similar to the following:
let x = M.x
type t = M.t
type exn += E = M.E
module N = M.N
(If the line with exn is mysterious, don't worry about it; it makes use
of extensible variants, which we aren't covering. It might help to know
that exception E is syntactic sugar for type exn += E, which is to say
that it extends the type exn, which is an extensible variant, with
a new constructor E.)
Stdlib.
There is a special module called Stdlib that is
automatically opened in every OCaml program. It contains the "built-in"
functions and operators, as we've seen before. You therefore never need
to prefix any of the names it defines with Stdlib., though you
could do so if you ever needed to unambiguously identify a name from it.
Opening a module in a limited scope
If two modules both define the same name, and you open both of them, what does that name mean? For example:
module M = struct let x = 42 end
module N = struct let x = "bigred" end
open M
open N
(* what is [x]? an [int] or a [string]? *)
The answer is that any names defined later shadow names defined earlier. So in
the local namespace above, x is a string.
If you're using many third-party modules inside your code, chances are you'll have at least
one collision like this. Often it will be with a standard higher-order function like
map that is defined in many library modules. So it's generally good practice not to
open all the modules you're going to use at the top of a .ml file.
(This is perhaps different than how you're used to working with (e.g.) Java,
where you might import many packages with *.)
Instead, it's good to restrict the scope in which you open modules. There are a couple ways of doing that.
Inside any expression you can locally open a module, such that the module's names are in scope only in the rest of that expression. The syntax for this is
let open M in e; insideeall the names fromMare in scope. This is useful for (e.g.) opening a module in the body of a function:(* without [open] *) let f x = let y = List.filter ((>) 0) x in ... (* many more lines of code that use [List.] a lot *) (* with [open] *) let f x = let open List in (* [filter] is now bound to [List.filter] *) let y = filter ((>) 0) x in ... (* many more lines of code that now can omit [List.] *)There is a syntactic sugar for the above:
M.(e). Again, insideeall the names fromMare in scope. This is useful for briefly usingMin a short expression:(* remove surrounding whitespace from [s] and convert it to lower case *) let s = "BigRed " let s' = s |> String.trim |> String.lowercase_ascii (*long way*) let s' = String.(s |> trim |> lowercase_ascii) (*shorter way*)