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