A modular, clean and powerful extension of the OCaml standard library.
Containers is an extension of OCaml’s standard library (under BSD license)
focused on data structures, combinators and iterators, without dependencies on
unix, str or num. Every module is independent and is prefixed with 'CC' in the
global namespace. Some modules extend the stdlib (e.g. CCList provides safe
map/fold_right/append, and additional functions on lists).
Alternatively, open Containers
will bring enhanced versions of the standard
modules into scope.
Containers is:
-
A usable, reasonably well-designed library that extends OCaml’s standard library (in 'src/core/', packaged under
containers
in ocamlfind. Modules are totally independent and are prefixed withCC
(for "containers-core" or "companion-cube" because I’m a megalomaniac). This part should be usable and should work. For instance,CCList
contains functions and lists including safe versions ofmap
andappend
. It also provides a drop-in replacement to the standard library, in the moduleContainers
(intended to be opened, replaces some stdlib modules with extended ones). -
Several small additional libraries that complement it:
-
containers.data
with additional data structures that don’t have an equivalent in the standard library; -
containers.iter
with list-like and tree-like iterators;
-
-
Utilities around the
unix
library incontainers.unix
(mainly to spawn sub-processes easily and deal with resources safely) -
A lightweight S-expression printer and streaming parser in
containers.sexp
-
A library for threaded programming in
containers.thread
, including a blocking queue, semaphores, an extension ofMutex
, and thread-pool based futures.
Some of the modules have been moved to their own repository (e.g. sequence
,
gen
, qcheck
) and are on opam for great fun and profit.
See this file.
-
Mailing List the address is [email protected]
-
the github wiki
-
on IRC, ask
companion_cube
on#[email protected]
You might start with the Tutorial to get a picture of how to use the library.
You can either build and install the library (see Build), or just copy files to your own project. The last solution has the benefits that you don’t have additional dependencies nor build complications (and it may enable more inlining). Since modules have a friendly license and are mostly independent, both options are easy.
In a toplevel, using ocamlfind:
# #use "topfind";;
# #require "containers";;
# CCList.flat_map;;
- : ('a -> 'b list) -> 'a list -> 'b list = <fun>
# open Containers;; (* optional *)
# List.flat_map ;;
- : ('a -> 'b list) -> 'a list -> 'b list = <fun>
If you have comments, requests, or bugfixes, please share them! :-)
See the documentation and the tutorial below for a gentle introduction.
In general, see http://c-cube.github.io/ocaml-containers/ or http://cedeela.fr/~simon/software/containers for the API documentation.
Some examples can be found there.
API documentation by version:
You will need OCaml >=
4.01.0.
On the branch master
you will need oasis
to build the library. On the
branch stable
it is not necessary.
$ make
To build and run tests (requires oUnit
and qtest):
$ opam install oUnit qtest $ ./configure --enable-tests --enable-unix $ make test
To build the small benchmarking suite (requires benchmark):
$ opam install benchmark $ make bench $ ./benchs.native
PRs on github are welcome (patches by email too, if you prefer so).
A few guidelines:
-
no dependencies between basic modules (even just for signatures);
-
add
@since
tags for new functions; -
add tests if possible (using
qtest
).
It is helpful to run make devel
to enable everything. Some dependencies
are required, you’ll need opam install benchmark qcheck qtest sequence
.
This tutorial contains a few examples to illustrate the features and usage of containers. We assume containers is installed and that the library is loaded, e.g. with:
#require "containers";;
We will start with a few list helpers, then look at other parts of the library, including printers, maps, etc.
(* quick reminder of this awesome standard operator *)
# (|>) ;;
- : 'a -> ('a -> 'b) -> 'b = <fun>
# open CCList.Infix;;
# let l = 1 -- 100;;
val l : int list = [1; 2; .....]
# l
|> CCList.filter_map
(fun x-> if x mod 3=0 then Some (float x) else None)
|> CCList.take 5 ;;
- : float list = [3.; 6.; 9.; 12.; 15.]
# let l2 = l |> CCList.take_while (fun x -> x<10) ;;
val l2 : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9]
(* an extension of Map.Make, compatible with Map.Make(CCInt) *)
# module IntMap = CCMap.Make(CCInt);;
(* conversions using the "sequence" type, fast iterators that are
pervasively used in containers. Combinators can be found
in the opam library "sequence". *)
# let map =
l2
|> List.map (fun x -> x, string_of_int x)
|> CCList.to_seq
|> IntMap.of_seq;;
val map : string CCIntMap.t = <abstr>
(* check the type *)
# CCList.to_seq ;;
- : 'a list -> 'a sequence = <fun>
# IntMap.of_seq ;;
- : (int * 'a) CCMap.sequence -> 'a IntMap.t = <fun>
(* we can print, too *)
# Format.printf "@[<2>map =@ @[<hov>%a@]@]@."
(IntMap.print CCFormat.int CCFormat.string_quoted)
map;;
map =
[1 --> "1", 2 --> "2", 3 --> "3", 4 --> "4", 5 --> "5", 6 --> "6",
7 --> "7", 8 --> "8", 9 --> "9"]
- : unit = ()
(* options are good *)
# IntMap.get 3 map |> CCOpt.map (fun s->s ^ s);;
- : string option = Some "33"
Containers also contains (!) a few datatypes that are not from the standard library but that are useful in a lot of situations:
- CCVector
-
A resizable array, with a mutability parameter. A value of type
('a, CCVector.ro) CCVector.t
is an immutable vector of values of type'a
, whereas a('a, CCVector.rw) CCVector.t
is a mutable vector that can be modified. This way, vectors can be used in a quite functional way, using operations such asmap
orflat_map
, or in a more imperative way. - CCHeap
-
A priority queue (currently, leftist heaps) functorized over a module
sig val t val leq : t → t → bool
that provides a typet
and a partial orderleq
ont
. - CCResult
-
An error type for making error handling more explicit (an error monad, really, if you’re not afraid of the "M"-word). Subsumes and replaces the old
CCError
. It uses the newresult
type from the standard library (or from the retrocompatibility package on opam) and provides many combinators for dealing withresult
.
Now for a few examples:
(* create a new empty vector. It is mutable, for otherwise it would
not be very useful. *)
# CCVector.create;;
- : unit -> ('a, CCVector.rw) CCVector.t = <fun>
(* init, similar to Array.init, can be used to produce a
vector that is mutable OR immutable (see the 'mut parameter?) *)
# CCVector.init ;;
- : int -> (int -> 'a) -> ('a, 'mut) CCVector.t = <fun>c
(* use the infix (--) operator for creating a range. Notice
that v is a vector of integer but its mutability is not
decided yet. *)
# let v = CCVector.(1 -- 10);;
val v : (int, '_a) CCVector.t = <abstr>
# Format.printf "v = @[%a@]@." (CCVector.print CCInt.print) v;;
v = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
(* now let's mutate v *)
# CCVector.push v 42;;
- : unit = ()
(* now v is a mutable vector *)
# v;;
- : (int, CCVector.rw) CCVector.t = <abstr>
(* functional combinators! *)
# let v2 = v
|> CCVector.map (fun x-> x+1)
|> CCVector.filter (fun x-> x mod 2=0)
|> CCVector.rev ;;
val v2 : (int, '_a) CCVector.t = <abstr>
# Format.printf "v2 = @[%a@]@." (CCVector.print CCInt.print) v2;;
v2 = [10, 8, 6, 4, 2]
(* let's transfer to a heap *)
# module IntHeap = CCHeap.Make(struct type t = int let leq = (<=) end);;
# let h = v2 |> CCVector.to_seq |> IntHeap.of_seq ;;
val h : IntHeap.t = <abstr>
(* We can print the content of h
(printing is not necessarily in order, though) *)
# Format.printf "h = [@[%a@]]@." (IntHeap.print CCInt.print) h;;
h = [2,4,6,8,10]
(* we can remove the first element, which also returns a new heap
that does not contain it — CCHeap is a functional data structure *)
# IntHeap.take h;;
- : (IntHeap.t * int) option = Some (<abstr>, 2)
# let h', x = IntHeap.take_exn h ;;
val h' : IntHeap.t = <abstr>
val x : int = 2
(* see, 2 is removed *)
# IntHeap.to_list h' ;;
- : int list = [4; 6; 8; 10]
The core library contains a module called CCIO
that provides useful
functions for reading and writing files. It provides functions that
make resource handling easy, following
the pattern with_resource : resource → (access → 'a) → 'a
where
the type access
is a temporary handle to the resource (e.g.,
imagine resource
is a file name and access
a file descriptor).
Calling with_resource r f
will access r
, give the result to f
,
compute the result of f
and, whether f
succeeds or raises an
error, it will free the resource.
Consider for instance:
# CCIO.with_out "/tmp/foobar"
(fun out_channel ->
CCIO.write_lines_l out_channel ["hello"; "world"]);;
- : unit = ()
This just opened the file '/tmp/foobar', creating it if it didn’t exist,
and wrote two lines in it. We did not have to close the file descriptor
because with_out
took care of it. By the way, the type signatures are:
val with_out :
?mode:int -> ?flags:open_flag list ->
string -> (out_channel -> 'a) -> 'a
val write_lines_l : out_channel -> string list -> unit
So we see the pattern for with_out
(which opens a function in write
mode and gives its functional argument the corresponding file descriptor).
Note
|
you should never let the resource escape the
scope of the with_resource call, because it will not be valid outside.
OCaml’s type system doesn’t make it easy to forbid that so we rely
on convention here (it would be possible, but cumbersome, using
a record with an explicitely quantified function type).
|
Now we can read the file again:
# let lines = CCIO.with_in "/tmp/foobar" CCIO.read_lines_l ;;
val lines : string list = ["hello"; "world"]
There are some other functions in CCIO
that return generators
instead of lists. The type of generators in containers
is type 'a gen = unit → 'a option
(combinators can be
found in the opam library called "gen"). A generator is to be called
to obtain successive values, until it returns None
(which means it
has been exhausted). In particular, python users might recognize
the function
# CCIO.File.walk ;;
- : string -> walk_item gen = <fun>;;
where type walk_item = [ `Dir | `File ] * string
is a path
paired with a flag distinguishing files from directories.
There is also a sub-library called containers.data
, with lots of
more specialized data-structures.
The documentation contains the API for all the modules
(see the readme); they also provide
interface to sequence
and, as the rest of containers, minimize
dependencies over other modules. To use containers.data
you need to link it,
either in your build system or by #require containers.data;;
A quick example based on purely functional double-ended queues:
# #require "containers.data";;
# #install_printer CCFQueue.print;; (* better printing of queues! *)
# let q = CCFQueue.of_list [2;3;4] ;;
val q : int CCFQueue.t = queue {2; 3; 4}
# let q2 = q |> CCFQueue.cons 1 |> CCFQueue.cons 0 ;;
val q2 : int CCFQueue.t = queue {0; 1; 2; 3; 4}
(* remove first element *)
# CCFQueue.take_front q2;;
- : (int * int CCFQueue.t) option = Some (0, queue {1; 2; 3; 4})
(* q was not changed *)
# CCFQueue.take_front q;;
- : (int * int CCFQueue.t) option = Some (2, queue {3; 4})
(* take works on both ends of the queue *)
# CCFQueue.take_back_l 2 q2;;
- : int CCFQueue.t * int list = (queue {0; 1; 2}, [3; 4])
Some structural types are used throughout the library:
- gen
-
'a gen = unit → 'a option
is an iterator type. Many combinators are defined in the opam library gen - sequence
-
'a sequence = (unit → 'a) → unit
is also an iterator type. It is easier to define on data structures thangen
, but it a bit less powerful. The opam library sequence can be used to consume and produce values of this type. - error
-
'a or_error = ('a, string) result = Error of string | Ok of 'a
using the standardresult
type, supported inCCResult
. - klist
-
'a klist = unit → [`Nil | `Cons of 'a * 'a klist]
is a lazy list without memoization, used as a persistent iterator. The reference module isCCKList
(incontainers.iter
). - printer
-
'a printer = Format.formatter → 'a → unit
is a pretty-printer to be used with the standard moduleFormat
. In particular, in many cases,"foo: %a" Foo.print foo
will type-check.
See the extended documentation for more examples.