Ocaml/FAQ/Packages Libraries and Modules

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск


[править] 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"
module Omega = struct
  let name = "last"
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";;
span class="sy0"> - : 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:
   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 :
    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
# S.join;;
span class="sy0"> - : 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 =
  val version : string
module YourModule : YourModuleSignature =
  let version = "1.00"
(* Signatures can also be anonymous. *)
module YourModule :
  val version : string
end =
  let version = "1.00"

[править] 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 <filename>.
   being printed to standard output. This is also not an error you can
   catch, and its occurrence will not stop the script from executing.
   It is possible to dynamically load modules and detect the failure of
   this operation with Dynlink. An example is given in the next recipe. *)

[править] 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 :
  val flip_boundary : string -> string
  val flip_words : string -> string
end =
  let separatrix = ref " "  (* hidden by signature *)
  let flip_boundary sep =
    let prev_sep = !separatrix in
    separatrix := sep;
  let flip_words line =
    let words = Str.split (Str.regexp_string !separatrix) line in
    String.concat !separatrix (List.rev words)

[править] 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 () =
    (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
(* 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:
   findlib is available here:
   http://projects.camlcity.org/projects/findlib.html *)
(* Put the following in a file called "Makefile" and edit to taste: *)
RESULT = mylibrary
SOURCES = mylibrary.mli mylibrary.ml
PACKS = pcre
all: native-code-library byte-code-library
install: libinstall
uninstall: libuninstall
(* 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";;
span class="sy0"> - : 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 ())
(* 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)
(* 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)

[править] 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:
   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:
   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 <sys/time.h>");
struct timeval {
    [int32] int tv_sec;
    [int32] int tv_usec;
struct timezone {
    int tz_minuteswest;
    int tz_dsttime;
int gettimeofday([out] struct timeval *tv, [in] struct timezone *tz);
(* We can now build three files, "time.ml", "time.mli", and
   "time_stubs.c", corresponding to the OCaml implementation, OCaml
   interface, and OCaml-to-C stubs, by running the following command: *)
$ camlidl -no-include time.idl
(* CamlIDL automatically translates the two structs defined in the IDL
   into OCaml record types and builds an external function reference
   for "gettimeofday", resulting in the following generated OCaml
   implementation in "time.ml": *)
(* File generated from time.idl *)
type timeval = {
  tv_sec: int32;
  tv_usec: int32;
and timezone = {
  tz_minuteswest: int;
  tz_dsttime: int;
external gettimeofday : timezone option -> int * timeval
        = "camlidl_time_gettimeofday"
(* Now, we can use "ocamlc -c" as a front-end to the C compiler to build
   the stubs, producing time_stubs.o. *)
$ ocamlc -c time_stubs.c
(* The OCaml source can be built and packed into a library along with
   the compiled stubs using "ocamlc -a": *)
$ ocamlc -a -custom -o time.cma time.mli time.ml time_stubs.o \
                -cclib -lcamlidl
(* Finally, we can write a simple test program to use our newly-exposed
   "gettimeofday" function. *)
(* test.ml *)
let time () =
  let res, {Time.tv_sec=seconds; tv_usec=microseconds} =
    Time.gettimeofday None in
  Int32.to_float seconds +. (Int32.to_float microseconds /. 1_000_000.)
let () = Printf.printf "%f\n" (time ())
(* Compiling this test program is straightforward. *)
$ ocamlc -o test time.cma test.ml
(* Running it produces the current time with millisecond precision. *)
$ ./test
(* The next two recipes will wrap the Unix "ioctl" function, allowing
   us to make a few low-level I/O system calls. To make things easier,
   we'll use the following Makefile (make sure you use tabs, not spaces,
   if you cut and paste this code): *)
all: jam winsz
jam: ioctl.cma jam.ml
        ocamlc -o jam ioctl.cma jam.ml
winsz: ioctl.cma winsz.ml
        ocamlc -o winsz ioctl.cma winsz.ml
ioctl.cma: ioctl.mli ioctl.ml ioctl_stubs.o
        ocamlc -a -custom -o ioctl.cma ioctl.mli ioctl.ml ioctl_stubs.o \
                -cclib -lcamlidl
ioctl_stubs.o: ioctl_stubs.c
        ocamlc -c ioctl_stubs.c
ioctl.mli ioctl.ml ioctl_stubs.c: ioctl.idl
        camlidl -no-include ioctl.idl
        rm -f *.cma *.cmi *.cmo *.c *.o ioctl.ml ioctl.mli jam winsz
(* ioctl.idl: *)
quote(C,"#include <sys/ioctl.h>");
enum ioctl {
int ioctl([in] int fd,
          [in] enum ioctl request,
          [in, out, string] char *argp);
(* jam - stuff characters down STDIN's throat *)
(* Simulate input on a given terminal. *)
let jam ?(tty=0) s =
    (fun c -> ignore (Ioctl.ioctl tty Ioctl.TIOCSTI (String.make 1 c))) s
(* Stuff command-line arguments into STDIN. *)
let () = jam (String.concat " " (List.tl (Array.to_list (Sys.argv))))
(* winsz - find x and y for chars and pixels *)
(* Decode a little-endian short integer from a string and offset. *)
let decode_short s i =
  Char.code s.[i] lor Char.code s.[i + 1] lsl 8
(* Read and display the window size. *)
let () =
  let winsize = String.make 8 '\000' in
  ignore (Ioctl.ioctl 0 Ioctl.TIOCGWINSZ winsize);
  let row = decode_short winsize 0 in
  let col = decode_short winsize 2 in
  let xpixel = decode_short winsize 4 in
  let ypixel = decode_short winsize 6 in
  Printf.printf "(row,col) = (%d,%d)" row col;
  if xpixel <> 0 || ypixel <> 0
  then Printf.printf "  (xpixel,ypixel) = (%d,%d)" xpixel ypixel;
  print_newline ()

[править] 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: *)
RESULT = time
SOURCES = time.idl
all: byte-code-library native-code-library
(* 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;;
span class="sy0"> - : 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 =
  (* 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 () =
      (fun () ->
         (* ... *)
(* Some.mli *)
module Module :
  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

[править] 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";;
span class="sy0"> - : 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)