package containers
A modular, clean and powerful extension of the OCaml standard library
Install
Dune Dependency
Authors
Maintainers
Sources
2.5.tar.gz
md5=e769d484d7b8bfe499496533de21038c
sha512=441e86837efaea9a130db97abd136b6e1b155a8965116bcebbda30508cabe3dbae2656422247af6630c960a1576004253caf515858a5805828ee60f468746a07
Description
README
README.adoc
= OCaml-containers 📦 = :toc: macro :source-highlighter: pygments A modular, clean and powerful extension of the OCaml standard library. https://c-cube.github.io/ocaml-containers/last/[(Jump to the current API documentation)]. 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://travis-ci.org/c-cube/ocaml-containers.svg?branch=master[alt="Build Status on Travis", link="https://travis-ci.org/c-cube/ocaml-containers"] image::https://ci.appveyor.com/api/projects/status/tftx9q8auil4cv4c?svg=true[alt="Build Status on AppVeyor", link="https://ci.appveyor.com/project/c-cube/ocaml-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 are totally independent and are prefixed with `CC` (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 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. == Migration Guide === To 2.0 - The type system should detect issues related to `print` renamed into `pp` easily. If you are lucky, a call to `sed -i 's/print/pp/g'` on the concerned files might help rename all the calls properly. - many optional arguments have become mandatory, because their default value would be a polymorphic "magic" operator such as `(=)` or `(>=)`. Now these have to be specified explicitly, but during the transition you can use `Pervasives.(=)` and `Pervasives.(>=)` as explicit arguments. - if your code contains `open Containers`, the biggest hurdle you face might be that operators have become monomorphic by default. We believe this is a useful change that prevents many subtle bugs. However, during migration and until you use proper combinators for equality (`CCEqual`), comparison (`CCOrd`), and hashing (`CCHash`), you might want to add `open Pervasives` just after the `open Containers`. See <<mono-ops,the section on monomorphic operators>> for more details. [[mono-ops]] == Monomorphic operators: why, and how? === Why shadow polymorphic operators by default? To quote @bluddy in https://github.com/c-cube/ocaml-containers/issues/196[#196]: The main problem with polymorphic comparison is that many data structures will give one result for structural comparison, and a different result for semantic comparison. The classic example is comparing maps. If you have a list of maps and try to use comparison to sort them, you'll get the wrong result: multiple map structures can represent the same semantic mapping from key to value, and comparing them in terms of structure is simply wrong. A far more pernicious bug occurs with hashtables. Identical hashtables will seem to be identical for a while, as before they've had a key clash, the outer array is likely to be the same. Once you get a key clash though, you start getting lists inside the arrays (or maps inside the arrays if you try to make a smarter hashtable) and that will cause comparison errors ie. identical hashtables will be seen as different or vice versa. Every time you use a polymorphic comparison where you're using a data type where structural comparison != semantic comparison, it's a bug. And ever time you use polymorphic comparison where the type of data being compared may vary (e.g. it's an int now, but it may be a map later), you're planting a bug for the future. See also: - https://blog.janestreet.com/the-perils-of-polymorphic-compare/ - https://blog.janestreet.com/building-a-better-compare/ === Sometimes polymorphic operators still make sense! If you just want to use polymorphic operators, it's fine! You can access them easily by using `Pervasives.(=)`, `Pervasives.max`, etc. When migrating a module, you can add `open Pervasives` on top of it to restore the default behavior. It is, however, recommended to export an `equal` function (and `compare`, and `hash`) for all the public types, even if their internal definition is just the corresponding polymorphic operator. This way, other modules can refer to `Foo.equal` and will not have to be updated the day `Foo.equal` is no longer just polymorphic equality. Another bonus is that `Hashtbl.Make(Foo)` or `Map.Make(Foo)` will just work™. === Further discussions See issues https://github.com/c-cube/ocaml-containers/issues/196[#196], https://github.com/c-cube/ocaml-containers/issues/197[#197] == Change Log See link:CHANGELOG.adoc[this file]. == Finding help - 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` - image:https://badges.gitter.im/Join%20Chat.svg[alt="Gitter", link="https://gitter.im/c-cube/ocaml-containers?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge"] == 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: [source,OCaml] ---- # #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 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/last/ for the **API documentation**. Some examples can be found link:doc/containers.adoc[there], per-version doc http://c-cube.github.io/ocaml-containers/[there]. [[build]] == Build You will need OCaml `>=` 4.02.0. === Via opam The prefered way to install is through http://opam.ocaml.org/[opam]. $ opam install containers === From Sources You need dune (formerly jbuilder). $ make To build and run tests (requires `oUnit` and https://github.com/vincent-hugot/iTeML[qtest]): $ opam install oUnit qtest $ ./configure --enable-tests --enable-unix $ make test To build the small benchmarking suite (requires https://github.com/chris00/ocaml-benchmark[benchmark]): $ opam install benchmark $ make bench $ ./benchs.native == Contributing PRs on github are very welcome (patches by email too, if you prefer so). [[first-time-contribute]] === First-Time Contributors Assuming your are in a clone of the repository: . Some dependencies are required, you'll need `opam install benchmark qcheck qtest sequence`. . run `make devel` to enable everything (including tests). . make your changes, commit, push, and open a PR. . use `make test` without moderation! It must pass before a PR is merged. There are around 900 tests right now, and new features should come with their own tests. If you feel like writing new tests, that is totally worth a PR (and my gratefulness). === General Guidelines A few guidelines to follow the philosophy of containers: - no dependencies between basic modules (even just for signatures); - add `@since` tags for new functions; - add tests if possible (using https://github.com/vincent-hugot/iTeML/[qtest]). There are numerous inline tests already, to see what it looks like search for comments starting with `(*$` in source files. === For Total Beginners Thanks for wanting to contribute! To contribute a change, here are the steps (roughly): . click "fork" on https://github.com/c-cube/ocaml-containers on the top right of the page. This will create a copy of the repository on your own github account. . click the big green "clone or download" button, with "SSH". Copy the URL (which should look like `git@github.com:<your username>/ocaml-containers.git`) into a terminal to enter the command: + [source,sh] ---- $ git clone git@github.com:<your username>/ocaml-containers.git ---- + . then, `cd` into the newly created directory. . make the changes you want. See <<first-time-contribute>> for more details about what to do in particular. . use `git add` and `git commit` to commit these changes. . `git push origin master` to push the new change(s) onto your copy of the repository . on github, open a "pull request" (PR). Et voilà ! [[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. == HOWTO (for contributors) === Make a release Beforehand, check `grep deprecated -r src` to see whether some functions can be removed. . `make test` . update version in `containers.opam` . `make update_next_tag` (to update `@since` comments; be careful not to change symlinks) . check status of modules (`{b status: foo}`) and update if required; removed deprecated functions, etc. . update `CHANGELOG.adoc` (see its end to find the right git command) . commit the changes . `make test doc` . tag, and push both to github . `opam pin add containers https://github.com/c-cube/ocaml-containers.git#<release>` . new opam package: `opam publish prepare; opam publish submit` . re-generate doc: `make doc push_doc` === List Authors `git log --format='%aN' | sort -u`
Dependencies (5)
-
ocaml
>= "4.02.0" & < "4.08.0"
- uchar
-
result
< "1.5"
- dune-configurator
- dune
Dev Dependencies (7)
Used by (54)
- api-watch
- archsat
-
batsat
>= "0.7"
- bytepdf
-
calculon
>= "0.3" & < "0.7"
- calli
-
daypack-lib
< "0.0.6"
- deadlock
-
decoders
>= "0.3.0"
- decoders-bencode
- decoders-cbor
-
decoders-ezjsonm
>= "0.3.0"
- decoders-ezxmlm
- decoders-jsonm
- decoders-msgpck
- decoders-sexplib
-
decoders-yojson
>= "0.5.0"
- dscheck
-
electrod
< "0.4.1"
- embedded_ocaml_templates
-
euler
< "0.2"
-
gdbprofiler
< "0.4"
-
hll
>= "2.7"
- jose
- labrys
-
libzipperposition
< "2.0"
-
logtk
>= "1.5.1" & < "2.0"
- lua_pattern
-
mastodon-archive-viewer
< "0.4.0"
-
msat
>= "0.8" & < "0.8.3"
-
msat-bin
< "0.8.3"
- nanomsg
- nosetup
-
nsq
= "0.2.4"
-
nunchaku
>= "0.4"
- obatcher
- oidc
-
opass
>= "2.15"
-
oseq
!= "0.4.1"
-
pds
>= "5.38"
- ppx_cstubs
-
redis-lwt
>= "0.4" & < "0.7"
-
redis-sync
>= "0.4" & < "0.7"
-
regenerate
< "0.2"
- revops
-
smbc
>= "0.4.2"
-
soupault
< "2.7.0"
- stitch
-
tsort
< "2.0.0"
-
websocket
>= "2.0.0" & < "2.7"
- ws
-
yices2_bindings
< "0.2"
-
zipperposition
>= "1.5" & < "2.0"
-
zipperposition-tools
< "2.0"
Conflicts (1)
-
sequence
< "0.5"
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
On This Page