A lightweight, modular standard library extension, string library, and interfaces to various libraries (bigarrays, unix, etc.) BSD license.
OCaml Other

README.adoc

OCaml-containers

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.

Build Status

Quick Summary

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 with CC (for "containers-core" or "companion-cube" because I’m megalomaniac). This part should be usable and should work. For instance, CCList contains functions and lists including safe versions of map and append. It also provides a drop-in replacement to the standard library, in the module Containers (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 in containers.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 of Mutex, 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.

Change Log

See this file.

Finding help

Use

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! :-)

License

This code is free, under the BSD license.

Contents

See the documentation and the tutorial below for a gentle introduction.

Documentation

Some examples can be found there.

API documentation by version:

Build

You will need OCaml >= 4.01.0.

Via opam

The prefered way to install is through opam.

$ opam install containers

From Sources

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

Contributing

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).

Powered by OASIS

Tutorial

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";;

Basics

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"

New types: CCVector, CCHeap, CCResult

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 as map or flat_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 type t and a partial order leq on t.

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 new result type from the standard library (or from the retrocompatibility package on opam) and provides many combinators for dealing with result.

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]

IO helpers

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.

To go further: containers.data

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])

Common Type Definitions

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 than gen, 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 standard result type, supported in CCResult.

klist

'a klist = unit → [`Nil | `Cons of 'a * 'a klist] is a lazy list without memoization, used as a persistent iterator. The reference module is CCKList (in containers.iter).

printer

'a printer = Format.formatter → 'a → unit is a pretty-printer to be used with the standard module Format. In particular, in many cases, "foo: %a" Foo.print foo will type-check.

Extended Documentation

See the extended documentation for more examples.