diff --git a/.merlin b/.merlin index 873a7262..09ba253e 100644 --- a/.merlin +++ b/.merlin @@ -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 diff --git a/.ocamlinit b/.ocamlinit index 25d7e5bb..bbd73502 100644 --- a/.ocamlinit +++ b/.ocamlinit @@ -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";; diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index c0114d68..65cc9a2c 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -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. diff --git a/README.adoc b/README.adoc index 62877090..23e5502b 100644 --- a/README.adoc +++ b/README.adoc @@ -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 <> to get a picture of how to use the library. You can either build and install the library (see <>), 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 <> 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 <> 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 = + +# 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 = + +(* check the type *) +# CCList.to_seq ;; +- : 'a list -> 'a sequence = +# IntMap.of_seq ;; +- : (int * 'a) CCMap.sequence -> 'a IntMap.t = + +(* we can print, too *) +# Format.printf "@[<2>map =@ @[%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 = + +(* 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 = 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 = + +# 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 = + +(* 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 = + +# 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 = + +(* 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 (, 2) + +# let h', x = IntHeap.take_exn h ;; +val h' : IntHeap.t = +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 = ;; +---- + +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. diff --git a/TUTORIAL.adoc b/TUTORIAL.adoc deleted file mode 100644 index 72aa3f65..00000000 --- a/TUTORIAL.adoc +++ /dev/null @@ -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 = - -# 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 = - -(* check the type *) -# CCList.to_seq ;; -- : 'a list -> 'a sequence = -# IntMap.of_seq ;; -- : (int * 'a) CCMap.sequence -> 'a IntMap.t = - -(* we can print, too *) -# Format.printf "@[<2>map =@ @[%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 = - -(* 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 = 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 = - -# 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 = - -(* 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 = - -# 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 = - -(* 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 (, 2) - -# let h', x = IntHeap.take_exn h ;; -val h' : IntHeap.t = -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 = ;; ----- - -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]) - ----- - diff --git a/_oasis b/_oasis index 1bc0150f..2b22996a 100644 --- a/_oasis +++ b/_oasis @@ -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 diff --git a/doc/containers.adoc b/doc/containers.adoc new file mode 100644 index 00000000..e6c564c6 --- /dev/null +++ b/doc/containers.adoc @@ -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 *) + +---- diff --git a/media/logo.png b/media/logo.png deleted file mode 100644 index df7b3d7f..00000000 Binary files a/media/logo.png and /dev/null differ diff --git a/src/core/CCHeap.ml b/src/core/CCHeap.ml index d214f2b7..1b939a86 100644 --- a/src/core/CCHeap.ml +++ b/src/core/CCHeap.ml @@ -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') -> diff --git a/src/core/CCHeap.mli b/src/core/CCHeap.mli index 3ba414f3..663ff9f4 100644 --- a/src/core/CCHeap.mli +++ b/src/core/CCHeap.mli @@ -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 diff --git a/src/core/CCIO.ml b/src/core/CCIO.ml index b9c2e2d4..f48cd127 100644 --- a/src/core/CCIO.ml +++ b/src/core/CCIO.ml @@ -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 = diff --git a/src/core/CCIO.mli b/src/core/CCIO.mli index 6319fb25..ad24ffdb 100644 --- a/src/core/CCIO.mli +++ b/src/core/CCIO.mli @@ -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 : diff --git a/src/core/CCOpt.ml b/src/core/CCOpt.ml index c4874e35..355473c2 100644 --- a/src/core/CCOpt.ml +++ b/src/core/CCOpt.ml @@ -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 diff --git a/src/core/CCOrd.mli b/src/core/CCOrd.mli index c47b075b..0734aee5 100644 --- a/src/core/CCOrd.mli +++ b/src/core/CCOrd.mli @@ -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]. diff --git a/src/core/CCParse.mli b/src/core/CCParse.mli index e47ca6f0..18aaea5e 100644 --- a/src/core/CCParse.mli +++ b/src/core/CCParse.mli @@ -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 diff --git a/src/data/CCGraph.ml b/src/data/CCGraph.ml index 252a06c5..e0cf7401 100644 --- a/src/data/CCGraph.ml +++ b/src/data/CCGraph.ml @@ -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} *) diff --git a/src/threads/CCPool.ml b/src/threads/CCPool.ml index fa5b3192..2bd4101a 100644 --- a/src/threads/CCPool.ml +++ b/src/threads/CCPool.ml @@ -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 ->