Link: http://pleac.sourceforge.net/pleac_ocaml/packagesetc.html ======================================================================== PLEAC-Objective CAML | ||
---|---|---|
Prev | Next |
12. Packages, Libraries, and Modules
Introduction
(* When an OCaml source file is compiled, it becomes a module. The name
of the module is the capitalized form of the filename. For example,
if the source file is "my_module.ml", the module name is "My_module".
Modules can also be created explicitly within a source file. If
"my_module.ml" contains "module Foo = struct ... end", a module named
"My_module.Foo" will be created.
Here is an example of the definition and use of two modules within a
single source file: *)
module Alpha = struct
let name = "first"
end
module Omega = struct
let name = "last"
end
let () =
Printf.printf "Alpha is %s, Omega is %s.\n"
Alpha.name Omega.name
(* Alpha is first, Omega is last. *)
(*-----------------------------*)
(* The "#use" and "#load" commands are known as toplevel directives.
They can only be used while interacting with the interpreter or from
scripts that are run using the "ocaml" program. *)
(* "#use" loads a source file into the current scope. *)
#use "FileHandle.ml";;
(* "#load" loads a module from a compiled bytecode file. This has the
same effect as including this file during bytecode compilation. *)
#load "FileHandle.cmo";;
(* "#load" can be used with libraries as well as modules. Bytecode
libraries use an extension of ".cma". *)
#load "library.cma";;
(* The "open" statement can be used in any source file. It allows any
values defined within a module to be used without being prefixed by
the module name. *)
open FileHandle
(* Modules form a hierarchy; submodules can be opened in a similar
fashion by prefixing them with the parent module's name. *)
open Cards.Poker
(* It is often convenient to use Gerd Stolpmann's "findlib" system,
which makes it considerably easier to load libraries into the
interpreter. *)
# #use "topfind";;
- : unit = ()
Findlib has been successfully loaded. Additional directives:
#require "package";; to load a package
#list;; to list the available packages
#camlp4o;; to load camlp4 (standard syntax)
#camlp4r;; to load camlp4 (revised syntax)
#predicates "p,q,...";; to set these predicates
Topfind.reset();; to force that packages will be reloaded
#thread;; to enable threads
- : unit = ()
# #require "extlib";;
/usr/lib/ocaml/3.10.2/extlib: added to search path
/usr/lib/ocaml/3.10.2/extlib/extLib.cma: loaded
(* The above use of "#require" has the same effect as typing the
following: *)
#directory "+extlib";;
#load "extLib.cma";;
(* More information on the "findlib" system is available here:
http://projects.camlcity.org/projects/findlib.html
The "#directory" directive above is built into OCaml and allows
you to add additional directories to the path that is searched
when loading modules. You can use a prefix of '+' to indicate that
the directory is under the standard library path, which is usually
something like "/usr/lib/ocaml/3.10.2/".
Modules can be easily aliased using assignment. This will also
cause the interpreter to output the module's signature, which
can be used as a quick reference. *)
# module S = ExtString.String;;
module S :
sig
val init : int -> (int -> char) -> string
val find : string -> string -> int
val split : string -> string -> string * string
val nsplit : string -> string -> string list
val join : string -> string list -> string
...
end
# S.join;;
- : string -> string list -> string = <fun>
(* Many useful libraries can be found at The Caml Hump:
http://caml.inria.fr/cgi-bin/hump.cgi *)
|
Defining a Module's Interface
(* Interfaces, also known as module types or signatures, are usually
saved in files with the same name as the corresponding module but
with a ".mli" extension. For instance, if the module is defined in
"YourModule.ml", the interface will be in "YourModule.mli". *)
(* YourModule.mli *)
val version : string
(* YourModule.ml *)
let version = "1.00"
(* As with modules, interfaces can also be defined explicitly inside
of a source file. *)
module type YourModuleSignature =
sig
val version : string
end
module YourModule : YourModuleSignature =
struct
let version = "1.00"
end
(* Signatures can also be anonymous. *)
module YourModule :
sig
val version : string
end =
struct
let version = "1.00"
end
|
Trapping Errors in require or use
(* Due to static typing, missing modules are detected at compilation
time, so this is not normally an error you can catch (or need to).
When using ocaml interactively or as an interpreter, the "#load"
directive can fail, resulting in a message like the following:
Cannot find file
|
Delaying use Until Run Time
(* Registry.ml *)
let (registry : (string, unit -> unit) Hashtbl.t) = Hashtbl.create 32
(* SomeModule.ml *)
let say_hello () = print_endline "Hello, world!"
let () = Hashtbl.replace Registry.registry "say_hello" say_hello
(* Main program *)
let filename = "SomeModule.cmo"
let funcname = "say_hello"
let () =
Dynlink.init ();
(try Dynlink.loadfile filename
with Dynlink.Error e -> failwith (Dynlink.error_message e));
(Hashtbl.find Registry.registry funcname) ()
(* Note that the Dynlink module currently supports dynamic loading of
bytecode modules only. There is a project to add support for dynamic
loading of native code which has been merged with OCaml's CVS HEAD.
Details are available at http://alain.frisch.fr/natdynlink.html *)
|
Making Variables Private to a Module
#load "str.cma";;
module Flipper :
sig
val flip_boundary : string -> string
val flip_words : string -> string
end =
struct
let separatrix = ref " " (* hidden by signature *)
let flip_boundary sep =
let prev_sep = !separatrix in
separatrix := sep;
prev_sep
let flip_words line =
let words = Str.split (Str.regexp_string !separatrix) line in
String.concat !separatrix (List.rev words)
end
|
Determining the Caller's Package
(* This is very difficult to do in OCaml due to the lack of reflection
capabilities. Determining the current module name is reasonably easy,
however, by using the __FILE__ constant exposed by camlp4's macro
extensions. *)
(*pp camlp4of *)
let __MODULE__ = String.capitalize (Filename.chop_extension __FILE__)
let () = Printf.printf "I am in module %s\n" __MODULE__
|
Automating Module Clean-Up
(* Use the built-in function, "at_exit", to schedule clean-up handlers
to run when the main program exits. *)
#load "unix.cma";;
let logfile = "/tmp/mylog"
let lf = open_out logfile
let logmsg msg =
Printf.fprintf lf "%s %d: %s\n%!"
Sys.argv.(0) (Unix.getpid ()) msg
(* Setup code. *)
let () =
logmsg "startup"
(* Clean-up code. *)
let () =
at_exit
(fun () ->
logmsg "shutdown";
close_out lf)
|
Keeping Your Own Module Directory
(* To add a directory to the module include path, pass the "-I" option
to any of the compiler tools. For example, if you have a module in
~/ocamllib called Utils with a filename of utils.cmo, you can build
against this module with the following: *)
$ ocamlc -I ~/ocamllib utils.cmo test.ml -o test
(* Within the toplevel interpreter, and from ocaml scripts, you can use
the "#directory" directive to add directories to the include path: *)
#directory "/home/myuser/ocamllib";;
#load "utils.cmo";;
(* In both cases, prefixing the include directory with a '+' indicates
that the directory should be found relative to the standard include
path. *)
#directory "+pcre";;
#load "pcre.cma";;
(* If you have findlib installed, you can print out the include path by
typing "ocamlfind printconf path" at the command line. *)
$ ocamlfind printconf path
/usr/local/lib/ocaml/3.10.2
/usr/lib/ocaml/3.10.2
/usr/lib/ocaml/3.10.2/METAS
(* Instead of keeping a directory of ".cmo" (or ".cmx") files, you may
prefer to build a library (".cma" for bytecode, ".cmxa" for native).
This will pack all of your modules into a single file that is easy to
use during compilation: *)
$ ocamlc -a slicer.cmo dicer.cmo -o tools.cma
$ ocamlc tools.cma myprog.ml -o myprog
|
Preparing a Module for Distribution
(* The easiest way to prepare a library for distribution is to build with
OCamlMakefile and include a META file for use with findlib.
OCamlMakefile is available here:
http://www.ocaml.info/home/ocaml_sources.html#OCamlMakefile
findlib is available here:
http://projects.camlcity.org/projects/findlib.html *)
(* Put the following in a file called "Makefile" and edit to taste: *)
OCAMLMAKEFILE = OCamlMakefile
RESULT = mylibrary
SOURCES = mylibrary.mli mylibrary.ml
PACKS = pcre
all: native-code-library byte-code-library
install: libinstall
uninstall: libuninstall
include $(OCAMLMAKEFILE)
(* Put the following in a file called "META" and edit to taste: *)
name = "mylibrary"
version = "1.0.0"
description = "My library"
requires = "pcre"
archive(byte) = "mylibrary.cma"
archive(native) = "mylibrary.cmxa"
(* Now you can build bytecode and native libraries with "make" and
install them into the standard library location with "make install".
If you make a change, you will have to "make uninstall" before you
can "make install" again. Once a library is installed, it's simple
to use: *)
$ ledit ocaml
Objective Caml version 3.10.2
# #use "topfind";;
- : unit = ()
Findlib has been successfully loaded. Additional directives:
#require "package";; to load a package
#list;; to list the available packages
#camlp4o;; to load camlp4 (standard syntax)
#camlp4r;; to load camlp4 (revised syntax)
#predicates "p,q,...";; to set these predicates
Topfind.reset();; to force that packages will be reloaded
#thread;; to enable threads
- : unit = ()
# #require "mylibrary";;
/usr/lib/ocaml/3.10.2/pcre: added to search path
/usr/lib/ocaml/3.10.2/pcre/pcre.cma: loaded
/usr/local/lib/ocaml/3.10.2/mylibrary: added to search path
/usr/local/lib/ocaml/3.10.2/mylibrary/mylibrary.cma: loaded
(* To compile against your new library, use the "ocamlfind" tool as a
front-end to "ocamlc" and "ocamlopt": *)
$ ocamlfind ocamlc -package mylibrary myprogram.ml -o myprogram
$ ocamlfind ocamlopt -package mylibrary myprogram.ml -o myprogram
|
Speeding Module Loading with SelfLoader
(* OCaml supports native compilation. If module load time is an issue,
it's hard to find a better solution than "ocamlopt". If compilation
is slow as well, try "ocamlopt.opt", which is the natively-compiled
native compiler. *)
|
Speeding Up Module Loading with Autoloader
(* This recipe is not relevant or applicable to OCaml. *)
|
Overriding Built-In Functions
#load "unix.cma";;
(* The Unix module returns the time as a float. Using a local module
definition and an "include", we can override this function to return
an int32 instead. (This is a bit silly, but it illustrates the basic
technique. *)
module Unix = struct
include Unix
let time () = Int32.of_float (time ())
end
(* Use the locally modified Unix.time function. *)
let () =
let start = Unix.time () in
while true do
Printf.printf "%ld\n" (Int32.sub (Unix.time ()) start)
done
(* Operators can also be locally modified. Here, we'll temporarily
define '-' as int32 subtraction. *)
let () =
let ( - ) = Int32.sub in
let start = Unix.time () in
while true do
Printf.printf "%ld\n" (Unix.time () - start)
done
|
Reporting Errors and Warnings Like Built-Ins
(* There are two built-in functions that raise standard exceptions.
Many standard library functions use these. "invalid_arg" raises
an Invalid_argument exception, which takes a string parameter: *)
let even_only n =
if n land 1 <> 0 (* one way to test *)
then invalid_arg (string_of_int n);
(* ... *)
()
(* "failwith" raises a Failure exception, which also takes a string
parameter (though it is typically used to identify the name of
the function as opposed to the argument). *)
let even_only n =
if n mod 2 <> 0 (* here's another *)
then failwith "even_only";
(* ... *)
()
(* In most cases, it is preferable to define your own exceptions. *)
exception Not_even of int
let even_only n =
if n land 1 <> 0 then raise (Not_even n);
(* ... *)
()
(* OCaml does not provide a facility for emitting warnings. You can
write to stderr, which may be an acceptable substitute. *)
let even_only n =
let n =
if n land 1 <> 0 (* test whether odd number *)
then (Printf.eprintf "%d is not even, continuing\n%!" n; n + 1)
else n in
(* ... *)
()
|
Referring to Packages Indirectly
(* Generally, it is best to use tables of functions, possibly with
Dynlink, to delay the choice of module and function until runtime.
It is however possible--though inelegant--to (ab)use the toplevel
for this purpose. *)
open Printf
(* Toplevel evaluator. Not type-safe. *)
let () = Toploop.initialize_toplevel_env ()
let eval text = let lexbuf = (Lexing.from_string text) in
let phrase = !Toploop.parse_toplevel_phrase lexbuf in
ignore (Toploop.execute_phrase false Format.std_formatter phrase)
let get name = Obj.obj (Toploop.getvalue name)
let set name value = Toploop.setvalue name (Obj.repr value)
(* Some module and value names, presumably not known until runtime. *)
let modname = "Sys"
let varname = "ocaml_version"
let aryname = "argv"
let funcname = "getenv"
(* Use the toplevel to evaluate module lookups dynamically. *)
let () =
eval (sprintf "let (value : string) = %s.%s;;" modname varname);
print_endline (get "value");
eval (sprintf "let (values : string array) = %s.%s;;" modname aryname);
Array.iter print_endline (get "values");
eval (sprintf "let (func : string -> string) = %s.%s;;" modname funcname);
print_endline ((get "func") "HOME");
|
Using h2ph to Translate C #include Files
(* There are several tools for translating C header files to OCaml
bindings, many of which can be found at The Caml Hump:
http://caml.inria.fr/cgi-bin/hump.en.cgi?sort=0&browse=42
Of the available tools, "ocamlffi" (also known as simply "FFI") seems
to work best at accomplishing the task of parsing header files, but
it has not been maintained in many years and cannot handle the deep
use of preprocessor macros in today's Unix headers. As a result, it is
often necessary to create a header file by hand, and so long as this
is required, better results can be achieved with Xavier Leroy's
CamlIDL tool. CamlIDL can be found here:
http://caml.inria.fr/pub/old_caml_site/camlidl/
The following recipes will use CamlIDL. First, we'll wrap the Unix
"gettimeofday" system call by writing the following to a file named
"time.idl": *)
quote(C,"#include
|
Using h2xs to Make a Module with C Code
(* Building libraries with C code is much easier with the aid of
OCamlMakefile. The following Makefile is all it takes to build
the "time" library from the previous recipe: *)
OCAMLMAKEFILE = OCamlMakefile
RESULT = time
SOURCES = time.idl
NOIDLHEADER = yes
all: byte-code-library native-code-library
include $(OCAMLMAKEFILE)
(* Now, a simple "make" will perform the code generation with camlidl
and produce static and dynamic libraries for bytecode and native
compilation. Furthermore, "make top" will build a custom toplevel
interpreter called "time.top" with the Time module built in: *)
$ ./time.top
Objective Caml version 3.10.2
# Time.gettimeofday None;;
- : int * Time.timeval =
(0, {Time.tv_sec = 1217483550l; Time.tv_usec = 645204l})
(* With the addition of a "META" file combined with the "libinstall"
and "libuninstall" targets, this library can be installed to the
standard location for use in other projects. See recipe 12.8,
"Preparing a Module for Distribution", for an example. *)
|
Documenting Your Module with Pod
(** Documentation for OCaml programs can be generated with the ocamldoc
tool, included in the standard distribution. Special comments like
this one begin with two asterisks which triggers ocamldoc to
include them in the documentation. The first special comment in a
module becomes the main description for that module. *)
(** Comments can be placed before variables... *)
val version : string
(** ...functions... *)
val cons : 'a -> 'a list -> 'a list
(** ...types... *)
type choice = Yes | No | Maybe of string
(* ... and other constructs like classes, class types, modules, and
module types. Simple comments like this one are ignored. *)
(** {2 Level-two headings look like this} *)
(** Text in [square brackets] will be formatted using a monospace font,
ideal for identifiers and other bits of code. Text written in curly
braces with a bang in front {!Like.this} will be hyperlinked to the
corresponding definition. *)
(* To generate HTML documentation, use a command like the following: *)
$ ocamldoc -html -d destdir Module1.mli Module1.ml ...
(* To generate Latex documentation, use a command like the following: *)
$ ocamldoc -latex -d destdir Module1.mli Module1.ml ...
(* If you use OCamlMakefile, you can type "make doc" to build HTML and
PDF documentation for your entire project. You may want to customize
the OCAMLDOC and DOC_FILES variables to suit your needs. *)
|
Building and Installing a CPAN Module
(* Installing a module from The Caml Hump differs from project to
project, since it is not as standardized as CPAN. However, in most
cases, "make" and "make install" do what you expect. Here's how to
install easy-format, which can be found on the Hump at the following
URL: http://caml.inria.fr/cgi-bin/hump.en.cgi?contrib=651 *)
$ tar xzf easy-format.tar.gz
$ cd easy-format
$ make
ocamlc -c easy_format.mli
ocamlc -c -dtypes easy_format.ml
touch bytecode
ocamlc -c easy_format.mli
ocamlopt -c -dtypes easy_format.ml
touch nativecode
$ sudo make install
[sudo] password for root: ........
echo "version = \"1.0.0\"" > META; cat META.tpl >> META
INSTALL_FILES="META easy_format.cmi easy_format.mli"; \
if test -f bytecode; then \
INSTALL_FILES="$INSTALL_FILES easy_format.cmo "; \
fi; \
if test -f nativecode; then \
INSTALL_FILES="$INSTALL_FILES easy_format.cmx easy_format.o"; \
fi; \
ocamlfind install easy-format $INSTALL_FILES
Installed /usr/local/lib/ocaml/3.10.2/easy-format/easy_format.o
Installed /usr/local/lib/ocaml/3.10.2/easy-format/easy_format.cmx
Installed /usr/local/lib/ocaml/3.10.2/easy-format/easy_format.cmo
Installed /usr/local/lib/ocaml/3.10.2/easy-format/easy_format.mli
Installed /usr/local/lib/ocaml/3.10.2/easy-format/easy_format.cmi
Installed /usr/local/lib/ocaml/3.10.2/easy-format/META
|
Example: Module Template
(* Some.ml *)
module Module =
struct
(* set the version for version checking *)
let version = "0.01"
(* initialize module globals (accessible as Some.Module.var1 *)
let var1 = ref ""
let hashit = Hashtbl.create 0
(* file-private lexicals go here *)
let priv_var = ref ""
let secret_hash = Hashtbl.create 0
(* here's a file-private function *)
let priv_func () =
(* stuff goes here. *)
()
(* make all your functions, whether exported or not *)
let func1 () = (* ... *) ()
let func2 () = (* ... *) ()
let func3 a b = (* ... *) ()
let func4 h = (* ... *) ()
(* module clean-up code here *)
let () =
at_exit
(fun () ->
(* ... *)
())
end
(* Some.mli *)
module Module :
sig
val version : string
val var1 : string ref
val hashit : (string, string) Hashtbl.t
(* priv_var, secret_hash, and priv_func are omitted,
making them private and inaccessible... *)
val func1 : unit -> unit
val func2 : unit -> unit
val func3 : 'a -> 'b -> unit
val func4 : (string, string) Hashtbl.t -> unit
end
|
Program: Finding Versions and Descriptions of Installed Modules
(* Use "findlib". You can use the "ocamlfind" program to get a list of
installed libraries from the command line: *)
$ ocamlfind list
benchmark (version: 0.6)
bigarray (version: [distributed with Ocaml])
cairo (version: n/a)
cairo.lablgtk2 (version: n/a)
calendar (version: 2.0.2)
camlimages (version: 2.2.0)
camlimages.graphics (version: n/a)
camlimages.lablgtk2 (version: n/a)
camlp4 (version: [distributed with Ocaml])
camlp4.exceptiontracer (version: [distributed with Ocaml])
camlp4.extend (version: [distributed with Ocaml])
...
(* You can also use the "#list" directive from the interpreter: *)
$ ledit ocaml
Objective Caml version 3.10.2
# #use "topfind";;
- : unit = ()
Findlib has been successfully loaded. Additional directives:
#require "package";; to load a package
#list;; to list the available packages
#camlp4o;; to load camlp4 (standard syntax)
#camlp4r;; to load camlp4 (revised syntax)
#predicates "p,q,...";; to set these predicates
Topfind.reset();; to force that packages will be reloaded
#thread;; to enable threads
- : unit = ()
# #list;;
benchmark (version: 0.6)
bigarray (version: [distributed with Ocaml])
cairo (version: n/a)
cairo.lablgtk2 (version: n/a)
...
|
Không có nhận xét nào:
Đăng nhận xét