kjo
kjo

Reputation: 35331

How to omit "open ..."?

Suppose I have some file frobozz.ml whose first line is:

open Foo

I understand that if I get rid of this line1, frobozz.ml may no longer compile, unless I also replace some identifiers X in the rest of its content with their "fully qualified" form Foo.X.

Is there an efficient/safe way (as opposed to compile-and-see, i.e. trial-and-error) to determine which identifiers X in frobozz.ml I would need to do this to (if I delete its open Foo line)?

(Sorry for the stupid question: I'm very new with OCaml.)


1 Why do I want to do get rid of the open ... lines? Three reasons: 1) at the moment, adding a line of the form open Foo results in a compiler error at a line that used to compile fine before making that addition; I'm hoping that by avoiding the open Foo line altogether I will be able to compile the file; 2) at this stage in my learning of OCaml, that is, until I become more familiar with the contents of the various packages/modules I think it would be more instructive to work with the full qualified names, and thus keep the provenance of everything completely clear, even if it means typing inconveniently long identifiers; 3) in principle, I think namespaces are great; I don't like to work with unqualified identifiers whenever there is the risk of inadvertent shadowing (of a previously defined identifier by a latter definition), since "shadowing bugs" can be really difficult to catch; if I want to reduce typing (or simply make my code look less cluttered), and if my language supports it, I prefer to alias the namespace to some short string; e.g. in Python, of the following three possibilities supported by the language, I prefer the last one:

import foo.bar.baz

...

foo.bar.baz.initialize()  # a lot of typing/clutter


from foo.bar.baz import initialize

...

initialize()  # risk of insidious shadowing bugs


import foo.bar.baz as fbb

...

fbb.initialize()  # safer, IMO

Granted, the last form does not completely eliminate the risk of shadowing bugs, in Python at least, module aliases can shadow each other (as well as unaliased module names) within a file; i.e. the following produces no warnings

import foo.bar.baz as fbb

...

import fro.bnitz.bong as fbb

...

fbb.initialize()

But this risk is mitigated by the Python convention of putting all import statements at the top of the file, which makes it easy to be aware of what aliases are already in use.

I don't know if OCaml supports such aliasing (or its "moral equivalent"). On the other hand, OCaml, unlike Python, my already have built-in safeguards against inadvertent shadowing of identifiers, which reduces the risk of working with the unqualified identifiers.

Upvotes: 3

Views: 690

Answers (3)

I don't know of a ready-made tool to do this tranformation, but that doesn't mean there isn't one. The compiler however does provide a key tool for this. Compile your program with the -annot option. This produces a .annot file for each source file that contains information about various things inferred by the compiler, including types and bindings.

Here it is at work on a sample file.

open Str
let foo = regexp "foo"
let regexp x = 42
let shadow = regexp "foo"

Here are the parts of the .annot file corresponding to the two uses of the regexp identifier in expressions, the first being from the Str module and the second from the local redefinition.

"a.ml" 2 9 19 "a.ml" 2 9 25
type(
  string -> Str.regexp
)
ident(
  ext_ref Str.regexp
)
"a.ml" 4 50 63 "a.ml" 4 50 69
type(
  string -> int
)
ident(
  int_ref regexp "a.ml" 3 32 36 "a.ml" 3 32 42
)

The format of the .annot file isn't documented, but you can look at the source code and at various programs that parse it, including the Emacs mode shipped with OCaml. You may be able to reuse code from OCamlSpotter.


Regarding defining shortcuts for a module, the equivalent of Python's import foo.bar.baz as fbb is module Fbb = Foo.Bar.Baz. In OCaml up to version 4.01, the alias isn't equivalent for to the original in all contexts, because equality on abstract types is based on the module paths leading to the types, and OCaml only tracks type equalities, not module equalities. For example:

module S = String;;
fun (x : S.t) -> (x : String.t);;  (*fine: S.t is the same type as String.t*)
module StringSet = Set.Make(String);;
module SSet = Set.Make(S);;
fun (x : StringSet.elt) -> (x : SSet.elt);;  (*still fine, still the same type*)
fun (x : StringSet.t) -> (x : SSet.t);;  (*incompatible types*)

The abstract types StringSet.t = Set.Make(String).t and SSet.t = Set.Make(S).t are not compatible because OCaml doesn't see that S and String are the same module, it only goes as far as seeing that they happen to define the same types. Since OCaml 4.02, such aliases are tracked by the type checker and by the code generator, so I believe that Fbb and Foo.Bar.Baz can be used interchangeably.

Another difference with Python is that external modules don't need to be explicitly declared. OCaml's open is really Python's import * from; any external package can be used as a module without prior declaration.

Upvotes: 3

Leo White
Leo White

Reputation: 1151

Rather than remove the open statement, you can get OCaml to protect you from shadowing bugs. If you turn on warning 44 (added in version 4.01), then OCaml will issue a warning whenever you use an identifier that was shadowed by an open statement. This warning can be silenced by using the open! syntax.

So in your example you should use:

open! Core.Std
open Foo

and compile it using warning 44:

ocamlc -w +44 bar.ml

This will give you a warning for each identifier you use from Foo that shadows something from Core.Std.

Upvotes: 4

ivg
ivg

Reputation: 35280

Indeed, in OCaml we try not to open modules at all, and squeeze the scope of opened modules. For example, in OCaml you can open modules locally, with two different syntaxes:

 let sum x y = 
   let open Int64 in
   x + y

or, sometimes even more convenient:

 let sum x y = Int64.(x + y)

What concerning your question, I think that trial and error is a good choice. Especially if you're using good tools, like emacs and merlin. Otherwise, you can try to ask utop about the contents of the particular module:

 # #typeof "Int64";;

But beware, it can be very large.

P.S. I've updated this answer with information on how to use utop

Update

I've forgot to note, that there're cases when you should open a module. Indeed, there is a tendency in a modern OCaml programming, to ship your libraries with one particular umbrella module, that will export all that you think you should export. It is something like python's packages. Also this helps to solve problems with namespace clashes, when you're linking.

So, if you're going to use core library, then you should start all your files with this open Core.Std.

By the way, if you have troubles with this module, then you should better try to read online documentation, instead of #typeof'ing it. It is too large. The official point of reference is here, but there css sucks, so I would suggest you to use this reference, although it is outdated and unofficial.

Upvotes: 2

Related Questions