mirror of
https://github.com/c-cube/ocaml-containers.git
synced 2026-01-29 04:14:51 -05:00
more radical change: merge tutorial into readme, cleanup
This commit is contained in:
parent
8d671f03a1
commit
7603a78970
2 changed files with 316 additions and 393 deletions
434
README.adoc
434
README.adoc
|
|
@ -2,11 +2,23 @@
|
||||||
:toc: macro
|
:toc: macro
|
||||||
:source-highlighter: pygments
|
:source-highlighter: pygments
|
||||||
|
|
||||||
What is _containers_? (take a look at the link:TUTORIAL.adoc[tutorial]!
|
A modular, clean and powerful extension of the OCaml standard library.
|
||||||
or the http://c-cube.github.io/ocaml-containers/[current documentation])
|
|
||||||
In `containers` and `containers.data`, all modules abide by
|
Containers is an extension of OCaml's standard library (under BSD license)
|
||||||
_pay for what you use_: only modules that are used are linked (there are no
|
focused on data structures, combinators and iterators, without dependencies on
|
||||||
cross-module dependencies).
|
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.
|
||||||
|
|
||||||
|
image:https://ci.cedeela.fr/buildStatus/icon?job=containers[alt="Build Status", link="http://ci.cedeela.fr/job/containers/"]
|
||||||
|
|
||||||
|
toc::[]
|
||||||
|
|
||||||
|
== Quick Summary
|
||||||
|
|
||||||
|
Containers is:
|
||||||
|
|
||||||
- A usable, reasonably well-designed library that extends OCaml's standard
|
- A usable, reasonably well-designed library that extends OCaml's standard
|
||||||
library (in 'src/core/', packaged under `containers` in ocamlfind. Modules
|
library (in 'src/core/', packaged under `containers` in ocamlfind. Modules
|
||||||
|
|
@ -18,11 +30,9 @@ cross-module dependencies).
|
||||||
`Containers` (intended to be opened, replaces some stdlib modules
|
`Containers` (intended to be opened, replaces some stdlib modules
|
||||||
with extended ones).
|
with extended ones).
|
||||||
- Several small additional libraries that complement it:
|
- Several small additional libraries that complement it:
|
||||||
|
* `containers.data` with additional data structures that don't have an
|
||||||
containers.data:: with additional data structures that don't have an
|
|
||||||
equivalent in the standard library;
|
equivalent in the standard library;
|
||||||
containers.iter:: with list-like and tree-like iterators;
|
* `containers.iter` with list-like and tree-like iterators;
|
||||||
|
|
||||||
- Utilities around the `unix` library in `containers.unix` (mainly to spawn
|
- Utilities around the `unix` library in `containers.unix` (mainly to spawn
|
||||||
sub-processes easily and deal with resources safely)
|
sub-processes easily and deal with resources safely)
|
||||||
- A lightweight S-expression printer and streaming parser in `containers.sexp`
|
- A lightweight S-expression printer and streaming parser in `containers.sexp`
|
||||||
|
|
@ -33,10 +43,6 @@ cross-module dependencies).
|
||||||
Some of the modules have been moved to their own repository (e.g. `sequence`,
|
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.
|
`gen`, `qcheck`) and are on opam for great fun and profit.
|
||||||
|
|
||||||
image:https://ci.cedeela.fr/buildStatus/icon?job=containers[alt="Build Status", link="http://ci.cedeela.fr/job/containers/"]
|
|
||||||
|
|
||||||
toc::[]
|
|
||||||
|
|
||||||
== Change Log
|
== Change Log
|
||||||
|
|
||||||
See link:CHANGELOG.adoc[this file].
|
See link:CHANGELOG.adoc[this file].
|
||||||
|
|
@ -80,111 +86,8 @@ This code is free, under the BSD license.
|
||||||
|
|
||||||
== Contents
|
== Contents
|
||||||
|
|
||||||
The library contains a <<core,Core part>> that mostly extends the stdlib
|
See http://c-cube.github.io/ocaml-containers/[the documentation]
|
||||||
and adds a few very common structures (heap, vector), and sub-libraries
|
and <<tutorial,the tutorial below>> for a gentle introduction.
|
||||||
that deal with either more specific things, or require additional dependencies.
|
|
||||||
|
|
||||||
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 https://github.com/c-cube/gen[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 https://github.com/c-cube/sequence[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.
|
|
||||||
|
|
||||||
[[core]]
|
|
||||||
=== Core Modules (extension of the standard library)
|
|
||||||
|
|
||||||
the core library, `containers`, now depends on
|
|
||||||
https://github.com/mjambon/cppo[cppo] and `base-bytes` (provided
|
|
||||||
by ocamlfind).
|
|
||||||
|
|
||||||
http://c-cube.github.io/ocaml-containers/[Documentation here].
|
|
||||||
|
|
||||||
- `CCHeap`, a purely functional heap structure
|
|
||||||
- `CCVector`, a growable array (pure OCaml, no C) with mutability annotations
|
|
||||||
- `CCList`, functions on lists, including tail-recursive implementations of `map` and `append` and many other things
|
|
||||||
- `CCArray`, utilities on arrays
|
|
||||||
- `CCArray_slice`, array slices
|
|
||||||
- `CCHashtbl`, `CCMap` extensions of the standard modules `Hashtbl` and `Map`
|
|
||||||
- `CCInt`
|
|
||||||
- `CCString` (basic string operations)
|
|
||||||
- `CCPair` (cartesian products)
|
|
||||||
- `CCOpt` (options, very useful)
|
|
||||||
- `CCFun` (function combinators)
|
|
||||||
- `CCBool`
|
|
||||||
- `CCFloat`
|
|
||||||
- `CCOrd` (combinators for total orderings)
|
|
||||||
- `CCRandom` (combinators for random generators)
|
|
||||||
- `CCHash` (hashing combinators)
|
|
||||||
- `CCResult` (monadic error handling, very useful)
|
|
||||||
- `CCIO`, basic utilities for IO (channels, files)
|
|
||||||
- `CCInt64,` utils for `int64`
|
|
||||||
- `CCChar`, utils for `char`
|
|
||||||
- `CCFormat`, pretty-printing utils around `Format`
|
|
||||||
|
|
||||||
=== Containers.data
|
|
||||||
|
|
||||||
- `CCBitField`, bitfields embedded in integers
|
|
||||||
- `CCCache`, memoization caches, LRU, etc.
|
|
||||||
- `CCFlatHashtbl`, a flat (open-addressing) hashtable functorial implementation
|
|
||||||
- `CCTrie`, a prefix tree
|
|
||||||
- `CCHashTrie`, a map where keys are hashed and put in a trie by hash
|
|
||||||
- `CCMultimap` and `CCMultiset`, functors defining persistent structures
|
|
||||||
- `CCFQueue`, a purely functional double-ended queue structure
|
|
||||||
- `CCBV`, mutable bitvectors
|
|
||||||
- `CCHashSet`, mutable set
|
|
||||||
- `CCPersistentHashtbl` and `CCPersistentArray`, a semi-persistent array and hashtable
|
|
||||||
(similar to https://www.lri.fr/~filliatr/ftp/ocaml/ds/parray.ml.html[persistent arrays])
|
|
||||||
- `CCMixmap`, `CCMixtbl`, `CCMixset`, containers of universal types (heterogenous containers)
|
|
||||||
- `CCRingBuffer`, a double-ended queue on top of an array-like structure,
|
|
||||||
with batch operations
|
|
||||||
- `CCIntMap`, map specialized for integer keys based on Patricia Trees,
|
|
||||||
with fast merges
|
|
||||||
- `CCGraph`, a small collection of graph algorithms
|
|
||||||
- `CCBitField`, a type-safe implementation of bitfields that fit in `int`
|
|
||||||
- `CCWBTree`, a weight-balanced tree, implementing a map interface
|
|
||||||
- `CCRAL`, a random-access list structure, with `O(1)` cons/hd/tl and `O(ln(n))`
|
|
||||||
access to elements by their index.
|
|
||||||
- `CCImmutArray`, immutable interface to arrays
|
|
||||||
|
|
||||||
=== Containers.unix
|
|
||||||
|
|
||||||
- `CCUnix`, utils for `Unix`
|
|
||||||
|
|
||||||
=== Containers.sexp
|
|
||||||
|
|
||||||
A small S-expression library.
|
|
||||||
|
|
||||||
- `CCSexp`, a small S-expression library
|
|
||||||
|
|
||||||
=== Containers.iter
|
|
||||||
|
|
||||||
Iterators:
|
|
||||||
|
|
||||||
- `CCKList`, a persistent iterator structure (akin to a lazy list, without memoization)
|
|
||||||
- `CCKTree`, an abstract lazy tree structure
|
|
||||||
|
|
||||||
=== Thread
|
|
||||||
|
|
||||||
In the library `containers.thread`, for preemptive system threads:
|
|
||||||
|
|
||||||
- `CCFuture`, a set of tools for preemptive threading, including a thread pool,
|
|
||||||
monadic futures, and MVars (concurrent boxes)
|
|
||||||
- `CCLock`, values protected by locks
|
|
||||||
- `CCSemaphore`, a simple implementation of semaphores
|
|
||||||
- `CCThread` basic wrappers for `Thread`
|
|
||||||
|
|
||||||
[[build]]
|
|
||||||
|
|
||||||
== Documentation
|
== Documentation
|
||||||
|
|
||||||
|
|
@ -198,6 +101,7 @@ by version:
|
||||||
- http://c-cube.github.io/ocaml-containers/0.19/[0.19]
|
- http://c-cube.github.io/ocaml-containers/0.19/[0.19]
|
||||||
- http://c-cube.github.io/ocaml-containers/0.17/[0.17]
|
- http://c-cube.github.io/ocaml-containers/0.17/[0.17]
|
||||||
|
|
||||||
|
[[build]]
|
||||||
== Build
|
== Build
|
||||||
|
|
||||||
You will need OCaml `>=` 4.01.0.
|
You will need OCaml `>=` 4.01.0.
|
||||||
|
|
@ -238,3 +142,297 @@ A few guidelines:
|
||||||
- add tests if possible (using `qtest`).
|
- add tests if possible (using `qtest`).
|
||||||
|
|
||||||
Powered by image:http://oasis.forge.ocamlcore.org/oasis-badge.png[alt="OASIS", style="border: none;", link="http://oasis.forge.ocamlcore.org/"]
|
Powered by image:http://oasis.forge.ocamlcore.org/oasis-badge.png[alt="OASIS", style="border: none;", link="http://oasis.forge.ocamlcore.org/"]
|
||||||
|
|
||||||
|
[[tutorial]]
|
||||||
|
== 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:
|
||||||
|
|
||||||
|
[source,OCaml]
|
||||||
|
----
|
||||||
|
#require "containers";;
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Basics
|
||||||
|
|
||||||
|
We will start with a few list helpers, then look at other parts of
|
||||||
|
the library, including printers, maps, etc.
|
||||||
|
|
||||||
|
[source,OCaml]
|
||||||
|
----
|
||||||
|
|
||||||
|
(* 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`, `CCError`, `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`.
|
||||||
|
CCError::
|
||||||
|
An error type for making error handling more explicit (an error monad,
|
||||||
|
really, if you're not afraid of the "M"-word). It is similar to the
|
||||||
|
more recent `CCResult`, but works with polymorphic variants for
|
||||||
|
compatibility with the numerous libraries that use the same type,
|
||||||
|
that is, `type ('a, 'b) CCError.t = [`Ok of 'a | `Error of 'b]`.
|
||||||
|
CCResult::
|
||||||
|
It uses the new `result` type from the standard library (or from
|
||||||
|
the retrocompatibility package on opam), and presents an interface
|
||||||
|
similar to `CCError`. In an indeterminate amount of time, it will
|
||||||
|
totally replace `CCError`.
|
||||||
|
|
||||||
|
Now for a few examples:
|
||||||
|
|
||||||
|
[source,OCaml]
|
||||||
|
----
|
||||||
|
|
||||||
|
(* 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:
|
||||||
|
|
||||||
|
[source,OCaml]
|
||||||
|
----
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
[source,OCaml]
|
||||||
|
----
|
||||||
|
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:
|
||||||
|
|
||||||
|
[source,OCaml]
|
||||||
|
----
|
||||||
|
# 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
|
||||||
|
|
||||||
|
[source,OCaml]
|
||||||
|
----
|
||||||
|
# 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 link:README.adoc[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:
|
||||||
|
|
||||||
|
[source,OCaml]
|
||||||
|
----
|
||||||
|
# #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 https://github.com/c-cube/gen[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 https://github.com/c-cube/sequence[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.
|
||||||
|
|
|
||||||
275
TUTORIAL.adoc
275
TUTORIAL.adoc
|
|
@ -1,275 +0,0 @@
|
||||||
= Tutorial
|
|
||||||
:source-highlighter: pygments
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
[source,OCaml]
|
|
||||||
----
|
|
||||||
#require "containers";;
|
|
||||||
----
|
|
||||||
|
|
||||||
== Basics
|
|
||||||
|
|
||||||
We will start with a few list helpers, then look at other parts of
|
|
||||||
the library, including printers, maps, etc.
|
|
||||||
|
|
||||||
[source,OCaml]
|
|
||||||
----
|
|
||||||
|
|
||||||
(* 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`, `CCError`, `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`.
|
|
||||||
CCError::
|
|
||||||
An error type for making error handling more explicit (an error monad,
|
|
||||||
really, if you're not afraid of the "M"-word). It is similar to the
|
|
||||||
more recent `CCResult`, but works with polymorphic variants for
|
|
||||||
compatibility with the numerous libraries that use the same type,
|
|
||||||
that is, `type ('a, 'b) CCError.t = [`Ok of 'a | `Error of 'b]`.
|
|
||||||
CCResult::
|
|
||||||
It uses the new `result` type from the standard library (or from
|
|
||||||
the retrocompatibility package on opam), and presents an interface
|
|
||||||
similar to `CCError`. In an indeterminate amount of time, it will
|
|
||||||
totally replace `CCError`.
|
|
||||||
|
|
||||||
Now for a few examples:
|
|
||||||
|
|
||||||
[source,OCaml]
|
|
||||||
----
|
|
||||||
|
|
||||||
(* 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:
|
|
||||||
|
|
||||||
[source,OCaml]
|
|
||||||
----
|
|
||||||
# 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:
|
|
||||||
|
|
||||||
[source,OCaml]
|
|
||||||
----
|
|
||||||
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:
|
|
||||||
|
|
||||||
[source,OCaml]
|
|
||||||
----
|
|
||||||
# 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
|
|
||||||
|
|
||||||
[source,OCaml]
|
|
||||||
----
|
|
||||||
# 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 link:README.adoc[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:
|
|
||||||
|
|
||||||
[source,OCaml]
|
|
||||||
----
|
|
||||||
# #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])
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue