Merge branch 'master' into stable for 1.1

This commit is contained in:
Simon Cruanes 2017-03-03 16:18:26 +01:00
commit 9fb319966b
17 changed files with 559 additions and 462 deletions

View file

@ -1,14 +1,9 @@
S src/core
S src/data/
S src/io
S src/iter/
S src/advanced/
S src/lwt/
S src/sexp/
S src/threads/
S src/misc
S src/string
S src/bigarray
S benchs
S examples
S tests
@ -22,8 +17,5 @@ PKG result
PKG threads
PKG threads.posix
PKG lwt
PKG bigarray
PKG sequence
PKG gen
PKG qcheck
FLG -w +a -w -4 -w -44

View file

@ -1,33 +1,21 @@
#use "topfind";;
#thread
#require "result";;
#require "bigarray";;
#require "unix";;
#require "sequence";;
#directory "_build/src/core";;
#directory "_build/src/misc";;
#directory "_build/src/pervasives/";;
#directory "_build/src/string";;
#directory "_build/src/io";;
#directory "_build/src/unix";;
#directory "_build/src/iter";;
#directory "_build/src/data";;
#directory "_build/src/advanced/";;
#directory "_build/src/sexp";;
#directory "_build/src/bigarray/";;
#directory "_build/src/threads";;
#directory "_build/src/top/";;
#directory "_build/tests/";;
#load "containers.cma";;
#load "containers_iter.cma";;
#load "containers_data.cma";;
#load "containers_advanced.cma";;
#load "containers_io.cma";;
#load "containers_unix.cma";;
#load "containers_sexp.cma";;
#load "containers_string.cma";;
#load "containers_pervasives.cma";;
#load "containers_bigarray.cma";;
#load "containers_top.cma";;
#thread;;
#load "containers_thread.cma";;

View file

@ -1,5 +1,26 @@
= Changelog
== 1.1
**bugfixes**:
- fix bug in `CCGraph` (in DFS traversal)
- fix bug in `CCOpt.filter` (close #100)
**new features**:
- add `CCHeap.to_seq_sorted`
- add `CCHeap.to_list_sorted`
- add `CCIO.File.walk_l`
**cleanup and doc**:
- remove dead code
- new test for `CCPool`
- new test and small readme section on `CCParse`
- remove CCError from tutorial
- merge tutorial into readme, cleanup
== 1.0
See https://github.com/c-cube/ocaml-containers/issues/84 for an overview.

View file

@ -2,11 +2,23 @@
:toc: macro
:source-highlighter: pygments
What is _containers_? (take a look at the link:TUTORIAL.adoc[tutorial]!
or the http://cedeela.fr/~simon/software/containers[current documentation])
In `containers` and `containers.data`, all modules abide by
_pay for what you use_: only modules that are used are linked (there are no
cross-module dependencies).
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.
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
library (in 'src/core/', packaged under `containers` in ocamlfind. Modules
@ -18,33 +30,26 @@ cross-module dependencies).
`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
* `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;
- A sub-library with complicated abstractions, `containers.advanced` (with
a LINQ-like query module, batch operations using GADTs, and others).
* `containers.iter` with list-like and tree-like iterators;
- Utilities around the `unix` library in `containers.unix` (mainly to spawn
sub-processes)
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.
image:https://ci.cedeela.fr/buildStatus/icon?job=containers[alt="Build Status", link="http://ci.cedeela.fr/job/containers/"]
toc::[]
image::media/logo.png[logo]
== Change Log
See link:CHANGELOG.adoc[this file].
== Finding help
- *new*: http://lists.ocaml.org/listinfo/containers-users[Mailing List]
- http://lists.ocaml.org/listinfo/containers-users[Mailing List]
the address is mailto:containers-users@lists.ocaml.org[]
- the https://github.com/c-cube/ocaml-containers/wiki[github wiki]
- on IRC, ask `companion_cube` on `#ocaml@freenode.net`
@ -52,7 +57,7 @@ See link:CHANGELOG.adoc[this file].
== Use
Start with the link:TUTORIAL.adoc[tutorial]
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
@ -79,141 +84,29 @@ If you have comments, requests, or bugfixes, please share them! :-)
This code is free, under the BSD license.
The logo (`media/logo.png`) is
CC-SA3 http://en.wikipedia.org/wiki/File:Hypercube.svg[wikimedia].
== Contents
The library contains a <<core,Core part>> that mostly extends the stdlib
and adds a few very common structures (heap, vector), and sub-libraries
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:: (DEPRECATED) `'a or_error = [`Error of string | `Ok of 'a]` is a error type
that is used in other libraries, too. It is now deprecated and
replaced with `('a, string) Result.result`, 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).
Documentation http://cedeela.fr/~simon/software/containers[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`
=== Misc
The library has moved to https://github.com/c-cube/containers-misc .
=== Others
`containers.lwt` has moved to https://github.com/c-cube/containers-lwt .
[[build]]
See http://c-cube.github.io/ocaml-containers/[the documentation]
and <<tutorial,the tutorial below>> for a gentle introduction.
== Documentation
In general, see http://c-cube.github.io/ocaml-containers/ or
http://cedeela.fr/~simon/software/containers
http://cedeela.fr/~simon/software/containers for the **API documentation**.
by version:
Some examples can be found link:doc/containers.adoc[there].
API documentation by version:
- http://c-cube.github.io/ocaml-containers/dev/[dev branch]
- http://c-cube.github.io/ocaml-containers/0.17/[0.17]
- http://c-cube.github.io/ocaml-containers/1.0/[1.0]
- http://c-cube.github.io/ocaml-containers/0.19/[0.19]
- http://c-cube.github.io/ocaml-containers/0.17/[0.17]
[[build]]
== Build
You will need OCaml `>=` 4.00.0.
You will need OCaml `>=` 4.01.0.
=== Via opam
@ -251,3 +144,297 @@ A few guidelines:
- 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/"]
[[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`, `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:
[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.
=== Extended Documentation
See link:doc/containers.adoc[the extended documentation] for more examples.

View file

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

2
_oasis
View file

@ -1,6 +1,6 @@
OASISFormat: 0.4
Name: containers
Version: 1.0
Version: 1.1
Homepage: https://github.com/c-cube/ocaml-containers
Authors: Simon Cruanes
License: BSD-2-clause

77
doc/containers.adoc Normal file
View file

@ -0,0 +1,77 @@
= OCaml-containers =
:toc: macro
:source-highlighter: pygments
This document contains more information on some modules of Containers.
toc::[]
== Hash combinators: `CCHash`
Although OCaml provides polymorphic hash tables (`('a,'b) Hashtbl.t`)
using the polymorphic equality `(=)` and hash `Hashtbl.hash`, it is often
safer and more efficient to use `Hashtbl.Make` (or the extended `CCHashtbl.Make`)
with custom equality and hash functions.
`CCHash` provides combinators for writing hash functions:
[source,OCaml]
----
# module H = CCHash;;
# let hash1 : (int * bool) list H.t = H.(list (pair int bool));;
# hash1 [1, true; 2, false; 3, true];;
- : int = 636041136
(* the function hashes the whole value, can be costly *)
# hash1 CCList.(1 -- 1000 |> map (fun i->i, i mod 2 = 0));;
- : int = 845685523
# hash1 CCList.(1 -- 1001 |> map (fun i->i, i mod 2 = 0));;
- : int = 381026697
----
The polymorphic hash function is still present, as `CCHash.poly`.
The functions `CCHash.list_comm` and `CCHash.array_comm` allow to hash
lists and arrays while ignoring the order of elements: all permutations
of the input will have the same hash.
== Parser Combinator: `CCParse`
:toc: macro
:source-highlighter: pygments
The module `CCParse` defines basic parser combinators on strings.
Adapting https://github.com/inhabitedtype/angstrom#usage[angstrom's tutorial example] gives the following snippet.
Note that backtracking is explicit in `CCParse`, hence
the use of `try_` to allow it in some places.
Explicit memoization with `memo` and `fix_memo` is also possible.
[source,OCaml]
----
open CCParse.Infix;;
module P = CCParse;;
let parens p = P.try_ (P.char '(') *> p <* P.char ')' ;;
let add = P.char '+' *> P.return (+) ;;
let sub = P.char '-' *> P.return (-) ;;
let mul = P.char '*' *> P.return ( * ) ;;
let div = P.char '/' *> P.return ( / ) ;;
let integer =
P.chars1_if (function '0'..'9'->true|_->false) >|= int_of_string ;;
let chainl1 e op =
P.fix (fun r ->
e >>= fun x -> P.try_ (op <*> P.return x <*> r) <|> P.return x) ;;
let expr : int P.t =
P.fix (fun expr ->
let factor = parens expr <|> integer in
let term = chainl1 factor (mul <|> div) in
chainl1 term (add <|> sub)) ;;
P.parse_string expr "4*1+2";; (* Ok 6 *)
P.parse_string expr "4*(1+2)";; (* Ok 12 *)
----

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

View file

@ -26,15 +26,7 @@ end
| [] -> true
| x::((y::_) as l') -> x <= y && is_sorted l'
let extract_list heap =
let rec recurse acc h =
if H.is_empty h
then List.rev acc
else
let h', x = H.take_exn h in
recurse (x::acc) h'
in
recurse [] heap
let extract_list = H.to_list_sorted
*)
(*$R
@ -77,6 +69,15 @@ end
)
*)
(*$QR
Q.(list_of_size Gen.(return 1_000) int) (fun l ->
(* put elements into a heap *)
let h = H.of_seq (Sequence.of_list l) in
let l' = H.to_seq_sorted h |> Sequence.to_list in
is_sorted l'
)
*)
module type S = sig
type elt
type t
@ -133,17 +134,31 @@ module type S = sig
are now [add_seq], [add_gen], [add_klist]) *)
val to_list : t -> elt list
(** Return the elements of the heap, in no particular order. *)
val add_list : t -> elt list -> t (** @since 0.16 *)
val to_list_sorted : t -> elt list
(** Return the elements in increasing order
@since 1.1 *)
val add_list : t -> elt list -> t
(** Add the elements of the list to the heap. An element occurring several
times will be added that many times to the heap.
@since 0.16 *)
val of_list : elt list -> t
(** [of_list l = add_list empty l] *)
val add_seq : t -> elt sequence -> t (** @since 0.16 *)
(** Similar to {!add_list} *)
val of_seq : elt sequence -> t
val to_seq : t -> elt sequence
val to_seq_sorted : t -> elt sequence
(** Iterate on the elements, in increasing order
@since 1.1 *)
val add_klist : t -> elt klist -> t (** @since 0.16 *)
val of_klist : elt klist -> t
@ -251,6 +266,13 @@ module Make(E : PARTIAL_ORD) : S with type elt = E.t = struct
x::aux (aux acc l) r
in aux [] h
let to_list_sorted heap =
let rec recurse acc h = match take h with
| None -> List.rev acc
| Some (h',x) -> recurse (x::acc) h'
in
recurse [] heap
let add_list h l = List.fold_left add h l
let of_list l = add_list empty l
@ -264,6 +286,13 @@ module Make(E : PARTIAL_ORD) : S with type elt = E.t = struct
let to_seq h k = iter k h
let to_seq_sorted heap =
let rec recurse h k = match take h with
| None -> ()
| Some (h',x) -> k x; recurse h' k
in
fun k -> recurse heap k
let rec add_klist h l = match l() with
| `Nil -> h
| `Cons (x, l') ->

View file

@ -71,6 +71,11 @@ module type S = sig
are now [add_seq], [add_gen], [add_klist]) *)
val to_list : t -> elt list
(** Return the elements of the heap, in no particular order. *)
val to_list_sorted : t -> elt list
(** Return the elements in increasing order
@since 1.1 *)
val add_list : t -> elt list -> t
(** Add the elements of the list to the heap. An element occurring several
@ -87,6 +92,10 @@ module type S = sig
val to_seq : t -> elt sequence
val to_seq_sorted : t -> elt sequence
(** Iterate on the elements, in increasing order
@since 1.1 *)
val add_klist : t -> elt klist -> t (** @since 0.16 *)
val of_klist : elt klist -> t

View file

@ -320,6 +320,14 @@ module File = struct
)
*)
let walk_l d =
let l = ref [] in
let g = walk d in
let rec aux () = match g() with
| None -> !l
| Some x -> l := x :: !l; aux ()
in aux ()
type walk_item = [`File | `Dir] * t
let read_dir ?(recurse=false) d =

View file

@ -202,6 +202,11 @@ module File : sig
symlinks, etc.)
@raise Sys_error in case of error (e.g. permission denied) during iteration *)
val walk_l : t -> walk_item list
(** Same as {!walk} but returns a list (therefore it's eager and might
take some time on large directories)
@since 1.1 *)
val show_walk_item : walk_item -> string
val with_temp :

View file

@ -13,8 +13,6 @@ let map_or ~default f = function
| None -> default
| Some x -> f x
let maybe f default = map_or ~default f
let is_some = function
| None -> false
| Some _ -> true
@ -69,7 +67,13 @@ let map2 f o1 o2 = match o1, o2 with
let filter p = function
| Some x as o when p x -> o
| o -> o
| _ -> None
(*$=
None (filter ((=) 0) (Some 1))
(Some 0) (filter ((=) 0) (Some 0))
None (filter (fun _ -> true) None)
*)
let if_ p x = if p x then Some x else None
@ -89,10 +93,6 @@ let fold f acc o = match o with
| None -> acc
| Some x -> f acc x
let get default x = match x with
| None -> default
| Some y -> y
let get_or ~default x = match x with
| None -> default
| Some y -> y

View file

@ -23,7 +23,7 @@ val float : float t
(** {2 Lexicographic Combination} *)
val (<?>) : int -> ('a t * 'a * 'a) -> int
(** [c1 @@? (ord, x, y)] returns the same as [c1] if [c1] is not [0];
(** [c1 <?> (ord, x, y)] returns the same as [c1] if [c1] is not [0];
otherwise it uses [ord] to compare the two values [x] and [y],
of type ['a].

View file

@ -118,6 +118,34 @@
*)
(*$R
let open CCParse.Infix in
let module P = CCParse in
let parens p = P.try_ (P.char '(') *> p <* P.char ')' in
let add = P.char '+' *> P.return (+) in
let sub = P.char '-' *> P.return (-) in
let mul = P.char '*' *> P.return ( * ) in
let div = P.char '/' *> P.return ( / ) in
let integer =
P.chars1_if (function '0'..'9'->true|_->false) >|= int_of_string in
let chainl1 e op =
P.fix (fun r ->
e >>= fun x -> P.try_ (op <*> P.return x <*> r) <|> P.return x) in
let expr : int P.t =
P.fix (fun expr ->
let factor = parens expr <|> integer in
let term = chainl1 factor (mul <|> div) in
chainl1 term (add <|> sub)) in
assert_equal (Ok 6) (P.parse_string expr "4*1+2");
assert_equal (Ok 12) (P.parse_string expr "4*(1+2)");
()
*)
type 'a or_error = ('a, string) Result.result
type line_num = int

View file

@ -252,16 +252,16 @@ module Traverse = struct
bag.push (`Enter (v, []));
while not (bag.is_empty ()) do
match bag.pop () with
| `Enter (x, path) ->
if not (tags.get_tag x) then (
| `Enter (v, path) ->
if not (tags.get_tag v) then (
let num = !n in
incr n;
tags.set_tag x;
k (`Enter (x, num, path));
bag.push (`Exit x);
tags.set_tag v;
k (`Enter (v, num, path));
bag.push (`Exit v);
Seq.iter
(fun (e,v') -> bag.push (`Edge (v,e,v',(v,e,v') :: path)))
(graph x);
(graph v);
)
| `Exit x -> k (`Exit x)
| `Edge (v,e,v', path) ->
@ -286,6 +286,21 @@ module Traverse = struct
} in
dfs_tag ?eq ~tags ~graph seq
end
(*$R
let l =
Traverse.Event.dfs ~graph:divisors_graph (Sequence.return 345614)
|> Sequence.to_list in
let expected =
[`Enter (345614, 0, []); `Edge (345614, (), 172807, `Forward);
`Enter (172807, 1, [(345614, (), 172807)]); `Edge (172807, (), 1, `Forward);
`Enter (1, 2, [(172807, (), 1); (345614, (), 172807)]); `Exit 1; `Exit 172807;
`Edge (345614, (), 2, `Forward); `Enter (2, 3, [(345614, (), 2)]);
`Edge (2, (), 1, `Cross); `Exit 2; `Edge (345614, (), 1, `Cross);
`Exit 345614]
in
assert_equal expected l
*)
end
(** {2 Cycles} *)

View file

@ -498,6 +498,19 @@ module Make(P : PARAM) = struct
OUnit.assert_raises Exit (fun () -> Fut.get l')
*)
(*$R
let rec fib x = if x<2 then 1 else fib (x-1)+fib(x-2) in
let l =
CCList.(1--10_000)
|> List.rev_map
(fun x-> Fut.make (fun () -> Thread.yield(); fib (x mod 30)))
|> Fut.(map_l (fun x->x>|= fun x->x+1))
in
OUnit.assert_bool "not done" (Fut.state l = Waiting);
let l' = Fut.get l in
OUnit.assert_equal 10_000 (List.length l');
*)
let choose_
: type a. a t array_or_list -> a t
= fun aol ->