mirror of
https://github.com/c-cube/ocaml-containers.git
synced 2025-12-06 11:15:31 -05:00
CCSequence now shares code with the 'sequence' library; merged the .mli so it reflects versions properly
This commit is contained in:
parent
bb64cc9a6d
commit
d638038089
2 changed files with 215 additions and 715 deletions
|
|
@ -1,691 +0,0 @@
|
|||
(*
|
||||
Copyright (c) 2013, Simon Cruanes
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer. Redistributions in binary
|
||||
form must reproduce the above copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other materials provided with
|
||||
the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*)
|
||||
|
||||
(** {1 Transient iterators, that abstract on a finite sequence of elements.} *)
|
||||
|
||||
(** Sequence abstract iterator type *)
|
||||
type 'a t = ('a -> unit) -> unit
|
||||
|
||||
type 'a sequence = 'a t
|
||||
|
||||
type (+'a, +'b) t2 = ('a -> 'b -> unit) -> unit
|
||||
(** Sequence of pairs of values of type ['a] and ['b]. *)
|
||||
|
||||
(** Build a sequence from a iter function *)
|
||||
let from_iter f = f
|
||||
|
||||
(** Call the function repeatedly until it returns None. This
|
||||
sequence is transient, use {!persistent} if needed! *)
|
||||
let from_fun f =
|
||||
fun k ->
|
||||
let rec next () =
|
||||
match f () with
|
||||
| None -> ()
|
||||
| Some x -> (k x; next ())
|
||||
in next ()
|
||||
|
||||
let empty = fun k -> ()
|
||||
|
||||
let singleton x = fun k -> k x
|
||||
|
||||
(** Infinite sequence of the same element *)
|
||||
let repeat x = fun k -> while true do k x done
|
||||
|
||||
(** [iterate f x] is the infinite sequence (x, f(x), f(f(x)), ...) *)
|
||||
let iterate f x =
|
||||
let rec iterate k x = k x; iterate k (f x) in
|
||||
from_iter (fun k -> iterate k x)
|
||||
|
||||
(** Sequence that calls the given function to produce elements *)
|
||||
let forever f =
|
||||
let rec forever k = k (f ()); forever k in
|
||||
from_iter forever
|
||||
|
||||
(** Cycle forever through the given sequence. O(n). *)
|
||||
let cycle s = fun k -> while true do s k; done
|
||||
|
||||
(** Consume the sequence, passing all its arguments to the function *)
|
||||
let iter f seq = seq f
|
||||
|
||||
(** Iterate on elements and their index in the sequence *)
|
||||
let iteri f seq =
|
||||
let r = ref 0 in
|
||||
let k x =
|
||||
f !r x;
|
||||
incr r
|
||||
in seq k
|
||||
|
||||
(** Fold over elements of the sequence, consuming it *)
|
||||
let fold f init seq =
|
||||
let r = ref init in
|
||||
seq (fun elt -> r := f !r elt);
|
||||
!r
|
||||
|
||||
(** Fold over elements of the sequence and their index, consuming it *)
|
||||
let foldi f init seq =
|
||||
let i = ref 0 in
|
||||
let r = ref init in
|
||||
seq (fun elt ->
|
||||
r := f !r !i elt;
|
||||
incr i);
|
||||
!r
|
||||
|
||||
(** Map objects of the sequence into other elements, lazily *)
|
||||
let map f seq =
|
||||
let seq_fun' k = seq (fun x -> k (f x)) in
|
||||
seq_fun'
|
||||
|
||||
(** Map objects, along with their index in the sequence *)
|
||||
let mapi f seq =
|
||||
let seq_fun' k =
|
||||
let i = ref 0 in
|
||||
seq (fun x -> k (f !i x); incr i) in
|
||||
seq_fun'
|
||||
|
||||
(** Filter on elements of the sequence *)
|
||||
let filter p seq =
|
||||
let seq_fun' k = seq (fun x -> if p x then k x) in
|
||||
seq_fun'
|
||||
|
||||
(** Append two sequences *)
|
||||
let append s1 s2 =
|
||||
let seq_fun k = s1 k; s2 k in
|
||||
seq_fun
|
||||
|
||||
(** Concatenate a sequence of sequences into one sequence *)
|
||||
let concat s =
|
||||
from_iter (fun k ->
|
||||
(* function that is called on every sub-sequence *)
|
||||
let k_seq seq = iter k seq in
|
||||
s k_seq)
|
||||
|
||||
let flatten s = concat s
|
||||
|
||||
(** Monadic bind. It applies the function to every element of the
|
||||
initial sequence, and calls [concat]. *)
|
||||
let flatMap f seq =
|
||||
from_iter
|
||||
(fun k -> seq (fun x -> (f x) k))
|
||||
|
||||
let fmap f seq =
|
||||
from_iter
|
||||
(fun k ->
|
||||
seq (fun x -> match f x with
|
||||
| None -> ()
|
||||
| Some y -> k y))
|
||||
|
||||
(** Insert the given element between every element of the sequence *)
|
||||
let intersperse elem seq =
|
||||
fun k ->
|
||||
let first = ref true in
|
||||
seq (fun x -> (if !first then first := false else k elem); k x)
|
||||
|
||||
(** Mutable unrolled list to serve as intermediate storage *)
|
||||
module MList = struct
|
||||
type 'a node =
|
||||
| Nil
|
||||
| Cons of 'a array * int ref * 'a node ref
|
||||
|
||||
let of_seq seq =
|
||||
let start = ref Nil in
|
||||
let chunk_size = ref 8 in
|
||||
(* fill the list. prev: tail-reference from previous node *)
|
||||
let prev, cur = ref start, ref Nil in
|
||||
seq
|
||||
(fun x -> match !cur with
|
||||
| Nil ->
|
||||
let n = !chunk_size in
|
||||
if n < 4096 then chunk_size := 2 * !chunk_size;
|
||||
cur := Cons (Array.make n x, ref 1, ref Nil)
|
||||
| Cons (a,n,next) ->
|
||||
assert (!n < Array.length a);
|
||||
a.(!n) <- x;
|
||||
incr n;
|
||||
if !n = Array.length a then begin
|
||||
!prev := !cur;
|
||||
prev := next;
|
||||
cur := Nil
|
||||
end
|
||||
);
|
||||
!prev := !cur;
|
||||
!start
|
||||
|
||||
let rec iter f l = match l with
|
||||
| Nil -> ()
|
||||
| Cons (a, n, tl) ->
|
||||
for i=0 to !n - 1 do f a.(i) done;
|
||||
iter f !tl
|
||||
|
||||
let iteri f l =
|
||||
let rec iteri i f l = match l with
|
||||
| Nil -> ()
|
||||
| Cons (a, n, tl) ->
|
||||
for j=0 to !n - 1 do f (i+j) a.(j) done;
|
||||
iteri (i+ !n) f !tl
|
||||
in iteri 0 f l
|
||||
|
||||
let rec iter_rev f l = match l with
|
||||
| Nil -> ()
|
||||
| Cons (a, n, tl) ->
|
||||
iter_rev f !tl;
|
||||
for i = !n-1 downto 0 do f a.(i) done
|
||||
|
||||
let length l =
|
||||
let rec len acc l = match l with
|
||||
| Nil -> acc
|
||||
| Cons (_, n, tl) -> len (acc+ !n) !tl
|
||||
in len 0 l
|
||||
|
||||
(** Get element by index *)
|
||||
let rec get l i = match l with
|
||||
| Nil -> raise (Invalid_argument "MList.get")
|
||||
| Cons (a, n, _) when i < !n -> a.(i)
|
||||
| Cons (_, n, tl) -> get !tl (i- !n)
|
||||
|
||||
let to_seq l k = iter k l
|
||||
|
||||
let _to_next arg l =
|
||||
let cur = ref l in
|
||||
let i = ref 0 in (* offset in cons *)
|
||||
let rec get_next _ = match !cur with
|
||||
| Nil -> None
|
||||
| Cons (_, n, tl) when !i = !n ->
|
||||
cur := !tl;
|
||||
i := 0;
|
||||
get_next arg
|
||||
| Cons (a, n, _) ->
|
||||
let x = a.(!i) in
|
||||
incr i;
|
||||
Some x
|
||||
in get_next
|
||||
|
||||
let to_gen l = _to_next () l
|
||||
|
||||
let to_stream l =
|
||||
Stream.from (_to_next 42 l) (* 42=magic cookiiiiiie *)
|
||||
end
|
||||
|
||||
(** Iterate on the sequence, storing elements in a data structure.
|
||||
The resulting sequence can be iterated on as many times as needed. *)
|
||||
let persistent seq =
|
||||
let l = MList.of_seq seq in
|
||||
MList.to_seq l
|
||||
|
||||
(** Sort the sequence. Eager, O(n) ram and O(n ln(n)) time. *)
|
||||
let sort ?(cmp=Pervasives.compare) seq =
|
||||
(* use an intermediate list, then sort the list *)
|
||||
let l = fold (fun l x -> x::l) [] seq in
|
||||
let l = List.fast_sort cmp l in
|
||||
fun k -> List.iter k l
|
||||
|
||||
(** Group equal consecutive elements. *)
|
||||
let group ?(eq=fun x y -> x = y) seq =
|
||||
fun k ->
|
||||
let cur = ref [] in
|
||||
seq (fun x ->
|
||||
match !cur with
|
||||
| [] -> cur := [x]
|
||||
| (y::_) as l when eq x y ->
|
||||
cur := x::l (* [x] belongs to the group *)
|
||||
| (_::_) as l ->
|
||||
k l; (* yield group, and start another one *)
|
||||
cur := [x]);
|
||||
(* last list *)
|
||||
if !cur <> [] then k !cur
|
||||
|
||||
(** Remove consecutive duplicate elements. Basically this is
|
||||
like [fun seq -> map List.hd (group seq)]. *)
|
||||
let uniq ?(eq=fun x y -> x = y) seq =
|
||||
fun k ->
|
||||
let has_prev = ref false
|
||||
and prev = ref (Obj.magic 0) in (* avoid option type, costly *)
|
||||
seq (fun x ->
|
||||
if !has_prev && eq !prev x
|
||||
then () (* duplicate *)
|
||||
else begin
|
||||
has_prev := true;
|
||||
prev := x;
|
||||
k x
|
||||
end)
|
||||
|
||||
(** Sort the sequence and remove duplicates. Eager, same as [sort] *)
|
||||
let sort_uniq ?(cmp=Pervasives.compare) seq =
|
||||
let seq' = sort ~cmp seq in
|
||||
uniq ~eq:(fun x y -> cmp x y = 0) seq'
|
||||
|
||||
(** Cartesian product of the sequences. *)
|
||||
let product outer inner =
|
||||
let inner = persistent inner in
|
||||
from_iter
|
||||
(fun k ->
|
||||
outer (fun x ->
|
||||
inner (fun y -> k (x,y))))
|
||||
|
||||
(** [join ~join_row a b] combines every element of [a] with every
|
||||
element of [b] using [join_row]. If [join_row] returns None, then
|
||||
the two elements do not combine. Assume that [b] allows for multiple
|
||||
iterations. *)
|
||||
let join ~join_row s1 s2 =
|
||||
fun k ->
|
||||
s1 (fun a ->
|
||||
s2 (fun b ->
|
||||
match join_row a b with
|
||||
| None -> ()
|
||||
| Some c -> k c)) (* yield the combination of [a] and [b] *)
|
||||
|
||||
(** [unfoldr f b] will apply [f] to [b]. If it
|
||||
yields [Some (x,b')] then [x] is returned
|
||||
and unfoldr recurses with [b']. *)
|
||||
let unfoldr f b =
|
||||
let rec unfold k b = match f b with
|
||||
| None -> ()
|
||||
| Some (x, b') -> k x; unfold k b'
|
||||
in
|
||||
from_iter (fun k -> unfold k b)
|
||||
|
||||
(** Sequence of intermediate results *)
|
||||
let scan f acc seq =
|
||||
from_iter
|
||||
(fun k ->
|
||||
k acc;
|
||||
let acc = ref acc in
|
||||
seq (fun elt -> let acc' = f !acc elt in k acc'; acc := acc'))
|
||||
|
||||
let max ?(lt=fun x y -> x < y) seq =
|
||||
let ret = ref None in
|
||||
seq (fun x -> match !ret with
|
||||
| None -> ret := Some x
|
||||
| Some y -> if lt y x then ret := Some x);
|
||||
!ret
|
||||
|
||||
let min ?(lt=fun x y -> x < y) seq =
|
||||
let ret = ref None in
|
||||
seq (fun x -> match !ret with
|
||||
| None -> ret := Some x
|
||||
| Some y -> if lt x y then ret := Some x);
|
||||
!ret
|
||||
|
||||
exception ExitSequence
|
||||
|
||||
(** Take at most [n] elements from the sequence *)
|
||||
let take n seq =
|
||||
let count = ref 0 in
|
||||
if n = 0 then empty
|
||||
else fun k ->
|
||||
try
|
||||
seq (fun x ->
|
||||
incr count;
|
||||
k x;
|
||||
if !count = n then raise ExitSequence)
|
||||
with ExitSequence -> ()
|
||||
|
||||
(** Drop the [n] first elements of the sequence *)
|
||||
let drop n seq =
|
||||
let count = ref 0 in
|
||||
fun k -> seq
|
||||
(fun x -> if !count >= n then k x else incr count)
|
||||
|
||||
(** Reverse the sequence. O(n) memory. *)
|
||||
let rev seq =
|
||||
let l = MList.of_seq seq in
|
||||
from_iter (fun k -> MList.iter_rev k l)
|
||||
|
||||
(** Do all elements satisfy the predicate? *)
|
||||
let for_all p seq =
|
||||
try
|
||||
seq (fun x -> if not (p x) then raise ExitSequence);
|
||||
true
|
||||
with ExitSequence -> false
|
||||
|
||||
(** Exists there some element satisfying the predicate? *)
|
||||
let exists p seq =
|
||||
try
|
||||
seq (fun x -> if p x then raise ExitSequence);
|
||||
false
|
||||
with ExitSequence -> true
|
||||
|
||||
(** How long is the sequence? *)
|
||||
let length seq =
|
||||
let r = ref 0 in
|
||||
seq (fun _ -> incr r);
|
||||
!r
|
||||
|
||||
(** Is the sequence empty? *)
|
||||
let is_empty seq =
|
||||
try seq (fun _ -> raise ExitSequence); true
|
||||
with ExitSequence -> false
|
||||
|
||||
(** {2 Transform a sequence} *)
|
||||
|
||||
let empty2 =
|
||||
fun k -> ()
|
||||
|
||||
let is_empty2 seq2 =
|
||||
try ignore (seq2 (fun _ _ -> raise ExitSequence)); true
|
||||
with ExitSequence -> false
|
||||
|
||||
let length2 seq2 =
|
||||
let r = ref 0 in
|
||||
seq2 (fun _ _ -> incr r);
|
||||
!r
|
||||
|
||||
let zip seq2 =
|
||||
fun k -> seq2 (fun x y -> k (x,y))
|
||||
|
||||
let unzip seq =
|
||||
fun k -> seq (fun (x,y) -> k x y)
|
||||
|
||||
(** Zip elements of the sequence with their index in the sequence *)
|
||||
let zip_i seq =
|
||||
fun k ->
|
||||
let r = ref 0 in
|
||||
seq (fun x -> let n = !r in incr r; k n x)
|
||||
|
||||
let fold2 f acc seq2 =
|
||||
let acc = ref acc in
|
||||
seq2 (fun x y -> acc := f !acc x y);
|
||||
!acc
|
||||
|
||||
let iter2 f seq2 =
|
||||
seq2 f
|
||||
|
||||
let map2 f seq2 =
|
||||
fun k -> seq2 (fun x y -> k (f x y))
|
||||
|
||||
(** [map2_2 f g seq2] maps each [x, y] of seq2 into [f x y, g x y] *)
|
||||
let map2_2 f g seq2 =
|
||||
fun k -> seq2 (fun x y -> k (f x y) (g x y))
|
||||
|
||||
(** {2 Basic data structures converters} *)
|
||||
|
||||
let to_list seq = List.rev (fold (fun y x -> x::y) [] seq)
|
||||
|
||||
let to_rev_list seq = fold (fun y x -> x :: y) [] seq
|
||||
(** Get the list of the reversed sequence (more efficient) *)
|
||||
|
||||
let of_list l = from_iter (fun k -> List.iter k l)
|
||||
|
||||
let to_array seq =
|
||||
let l = MList.of_seq seq in
|
||||
let n = MList.length l in
|
||||
if n = 0
|
||||
then [||]
|
||||
else begin
|
||||
let a = Array.make n (MList.get l 0) in
|
||||
MList.iteri (fun i x -> a.(i) <- x) l;
|
||||
a
|
||||
end
|
||||
|
||||
let of_array a =
|
||||
fun k ->
|
||||
for i = 0 to Array.length a - 1 do
|
||||
k (Array.unsafe_get a i)
|
||||
done
|
||||
|
||||
let of_array_i a =
|
||||
fun k ->
|
||||
for i = 0 to Array.length a - 1 do
|
||||
k (i, Array.unsafe_get a i)
|
||||
done
|
||||
|
||||
let of_array2 a =
|
||||
fun k ->
|
||||
for i = 0 to Array.length a - 1 do
|
||||
k i (Array.unsafe_get a i)
|
||||
done
|
||||
|
||||
(** [array_slice a i j] Sequence of elements whose indexes range
|
||||
from [i] to [j] *)
|
||||
let array_slice a i j =
|
||||
assert (i >= 0 && j < Array.length a);
|
||||
fun k ->
|
||||
for idx = i to j do
|
||||
k a.(idx); (* iterate on sub-array *)
|
||||
done
|
||||
|
||||
(** Sequence of elements of a stream (usable only once) *)
|
||||
let of_stream s =
|
||||
let seq k = Stream.iter k s in
|
||||
from_iter seq
|
||||
|
||||
(** Convert to a stream. The sequence is made persistent. *)
|
||||
let to_stream seq =
|
||||
let l = MList.of_seq seq in
|
||||
MList.to_stream l
|
||||
|
||||
(** Push elements of the sequence on the stack *)
|
||||
let to_stack s seq = iter (fun x -> Stack.push x s) seq
|
||||
|
||||
(** Sequence of elements of the stack (same order as [Stack.iter]) *)
|
||||
let of_stack s = from_iter (fun k -> Stack.iter k s)
|
||||
|
||||
(** Push elements of the sequence into the queue *)
|
||||
let to_queue q seq = iter (fun x -> Queue.push x q) seq
|
||||
|
||||
(** Sequence of elements contained in the queue, FIFO order *)
|
||||
let of_queue q = from_iter (fun k -> Queue.iter k q)
|
||||
|
||||
let hashtbl_add h seq =
|
||||
iter (fun (k,v) -> Hashtbl.add h k v) seq
|
||||
|
||||
let hashtbl_replace h seq =
|
||||
iter (fun (k,v) -> Hashtbl.replace h k v) seq
|
||||
|
||||
let to_hashtbl seq =
|
||||
let h = Hashtbl.create 3 in
|
||||
hashtbl_replace h seq;
|
||||
h
|
||||
|
||||
let to_hashtbl2 seq2 =
|
||||
let h = Hashtbl.create 3 in
|
||||
seq2 (fun k v -> Hashtbl.replace h k v);
|
||||
h
|
||||
|
||||
let of_hashtbl h =
|
||||
from_iter (fun k -> Hashtbl.iter (fun a b -> k (a, b)) h)
|
||||
|
||||
let of_hashtbl2 h =
|
||||
fun k -> Hashtbl.iter k h
|
||||
|
||||
let hashtbl_keys h =
|
||||
from_iter (fun k -> Hashtbl.iter (fun a b -> k a) h)
|
||||
|
||||
let hashtbl_values h =
|
||||
from_iter (fun k -> Hashtbl.iter (fun a b -> k b) h)
|
||||
|
||||
let of_str s = from_iter (fun k -> String.iter k s)
|
||||
|
||||
let to_str seq =
|
||||
let b = Buffer.create 64 in
|
||||
iter (fun c -> Buffer.add_char b c) seq;
|
||||
Buffer.contents b
|
||||
|
||||
let of_in_channel ic =
|
||||
from_iter (fun k ->
|
||||
try while true do
|
||||
let c = input_char ic in k c
|
||||
done with End_of_file -> ())
|
||||
|
||||
(** Copy content of the sequence into the buffer *)
|
||||
let to_buffer seq buf =
|
||||
iter (fun c -> Buffer.add_char buf c) seq
|
||||
|
||||
(** Iterator on integers in [start...stop] by steps 1 *)
|
||||
let int_range ~start ~stop =
|
||||
fun k ->
|
||||
for i = start to stop do k i done
|
||||
|
||||
let int_range_dec ~start ~stop =
|
||||
fun k ->
|
||||
for i = start downto stop do k i done
|
||||
|
||||
(** Convert the given set to a sequence. The set module must be provided. *)
|
||||
let of_set (type s) (type v) m set =
|
||||
let module S = (val m : Set.S with type t = s and type elt = v) in
|
||||
from_iter
|
||||
(fun k -> S.iter k set)
|
||||
|
||||
(** Convert the sequence to a set, given the proper set module *)
|
||||
let to_set (type s) (type v) m seq =
|
||||
let module S = (val m : Set.S with type t = s and type elt = v) in
|
||||
fold
|
||||
(fun set x -> S.add x set)
|
||||
S.empty seq
|
||||
|
||||
type 'a gen = unit -> 'a option
|
||||
|
||||
let of_gen g =
|
||||
(* consume the generator to build a MList *)
|
||||
let rec iter1 k = match g () with
|
||||
| None -> ()
|
||||
| Some x -> k x; iter1 k
|
||||
in
|
||||
let l = MList.of_seq iter1 in
|
||||
MList.to_seq l
|
||||
|
||||
let to_gen seq =
|
||||
let l = MList.of_seq seq in
|
||||
MList.to_gen l
|
||||
|
||||
(** {2 Functorial conversions between sets and sequences} *)
|
||||
|
||||
module Set = struct
|
||||
module type S = sig
|
||||
include Set.S
|
||||
val of_seq : elt sequence -> t
|
||||
val to_seq : t -> elt sequence
|
||||
end
|
||||
|
||||
(** Create an enriched Set module from the given one *)
|
||||
module Adapt(X : Set.S) = struct
|
||||
let to_seq set = from_iter (fun k -> X.iter k set)
|
||||
|
||||
let of_seq seq = fold (fun set x -> X.add x set) X.empty seq
|
||||
|
||||
include X
|
||||
end
|
||||
|
||||
(** Functor to build an extended Set module from an ordered type *)
|
||||
module Make(X : Set.OrderedType) = struct
|
||||
module MySet = Set.Make(X)
|
||||
include Adapt(MySet)
|
||||
end
|
||||
end
|
||||
|
||||
(** {2 Conversion between maps and sequences.} *)
|
||||
|
||||
module Map = struct
|
||||
module type S = sig
|
||||
include Map.S
|
||||
val to_seq : 'a t -> (key * 'a) sequence
|
||||
val of_seq : (key * 'a) sequence -> 'a t
|
||||
val keys : 'a t -> key sequence
|
||||
val values : 'a t -> 'a sequence
|
||||
end
|
||||
|
||||
(** Adapt a pre-existing Map module to make it sequence-aware *)
|
||||
module Adapt(M : Map.S) = struct
|
||||
let to_seq m = from_iter (fun k -> M.iter (fun x y -> k (x,y)) m)
|
||||
|
||||
let of_seq seq = fold (fun m (k,v) -> M.add k v m) M.empty seq
|
||||
|
||||
let keys m = from_iter (fun k -> M.iter (fun x _ -> k x) m)
|
||||
|
||||
let values m = from_iter (fun k -> M.iter (fun _ y -> k y) m)
|
||||
|
||||
include M
|
||||
end
|
||||
|
||||
(** Create an enriched Map module, with sequence-aware functions *)
|
||||
module Make(V : Map.OrderedType) : S with type key = V.t = struct
|
||||
module M = Map.Make(V)
|
||||
include Adapt(M)
|
||||
end
|
||||
end
|
||||
|
||||
(** {2 Infinite sequences of random values} *)
|
||||
|
||||
let random_int bound = forever (fun () -> Random.int bound)
|
||||
|
||||
let random_bool = forever Random.bool
|
||||
|
||||
let random_float bound = forever (fun () -> Random.float bound)
|
||||
|
||||
(** Sequence of choices of an element in the array *)
|
||||
let random_array a =
|
||||
assert (Array.length a > 0);
|
||||
let seq k =
|
||||
while true do
|
||||
let i = Random.int (Array.length a) in
|
||||
k a.(i);
|
||||
done in
|
||||
from_iter seq
|
||||
|
||||
let random_list l = random_array (Array.of_list l)
|
||||
|
||||
(** {2 Infix functions} *)
|
||||
|
||||
module Infix = struct
|
||||
let (--) i j = int_range ~start:i ~stop:j
|
||||
|
||||
let (--^) i j = int_range_dec ~start:i ~stop:j
|
||||
end
|
||||
|
||||
include Infix
|
||||
|
||||
(** {2 Pretty printing of sequences} *)
|
||||
|
||||
(** Pretty print a sequence of ['a], using the given pretty printer
|
||||
to print each elements. An optional separator string can be provided. *)
|
||||
let print ?(start="") ?(stop="") ?(sep=", ") pp_elt formatter seq =
|
||||
let first = ref true in
|
||||
Format.pp_print_string formatter start;
|
||||
iter
|
||||
(fun x ->
|
||||
(if !first then first := false
|
||||
else begin
|
||||
Format.pp_print_string formatter sep;
|
||||
Format.pp_print_cut formatter ();
|
||||
end);
|
||||
pp_elt formatter x)
|
||||
seq;
|
||||
Format.pp_print_string formatter stop;
|
||||
()
|
||||
|
||||
let pp ?(start="") ?(stop="") ?(sep=", ") pp_elt buf seq =
|
||||
let first = ref true in
|
||||
Buffer.add_string buf start;
|
||||
iter
|
||||
(fun x ->
|
||||
if !first then first := false else Buffer.add_string buf sep;
|
||||
pp_elt buf x)
|
||||
seq;
|
||||
Buffer.add_string buf stop;
|
||||
()
|
||||
|
||||
let to_string ?start ?stop ?sep pp_elt seq =
|
||||
let buf = Buffer.create 25 in
|
||||
pp ?start ?stop ?sep pp_elt buf seq;
|
||||
Buffer.contents buf
|
||||
1
core/CCSequence.ml
Symbolic link
1
core/CCSequence.ml
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../sequence/sequence.ml
|
||||
|
|
@ -23,16 +23,16 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*)
|
||||
|
||||
(** {1 Transient iterators, that abstract on a finite sequence of elements.} *)
|
||||
(** {1 Simple and Efficient Iterators} *)
|
||||
|
||||
(** The iterators are designed to allow easy transfer (mappings) between data
|
||||
structures, without defining n^2 conversions between the n types. The
|
||||
structures, without defining [n^2] conversions between the [n] types. The
|
||||
implementation relies on the assumption that a sequence can be iterated
|
||||
on as many times as needed; this choice allows for high performance
|
||||
of many combinators. However, for transient iterators, the {!persistent}
|
||||
function is provided, storing elements of a transient iterator
|
||||
in memory; the iterator can then be used several times (See further).
|
||||
|
||||
|
||||
Note that some combinators also return sequences (e.g. {!group}). The
|
||||
transformation is computed on the fly every time one iterates over
|
||||
the resulting sequence. If a transformation performs heavy computation,
|
||||
|
|
@ -42,7 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
until their result is iterated on. For instance, if one calls {!map}
|
||||
on a sequence, one gets a new sequence, but nothing else happens until
|
||||
this new sequence is used (by folding or iterating on it).
|
||||
|
||||
|
||||
If a sequence is built from an iteration function that is {b repeatable}
|
||||
(i.e. calling it several times always iterates on the same set of
|
||||
elements, for instance List.iter or Map.iter), then
|
||||
|
|
@ -53,8 +53,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
of this memory structure, cheaply and repeatably. *)
|
||||
|
||||
type +'a t = ('a -> unit) -> unit
|
||||
(** Sequence iterator type, representing a finite sequence of values
|
||||
of type ['a] that one can iterate on. *)
|
||||
(** A sequence of values of type ['a]. If you give it a function ['a -> unit]
|
||||
it will be applied to every element of the sequence successively. *)
|
||||
|
||||
type +'a sequence = 'a t
|
||||
|
||||
|
|
@ -76,12 +76,34 @@ val empty : 'a t
|
|||
val singleton : 'a -> 'a t
|
||||
(** Singleton sequence, with exactly one element. *)
|
||||
|
||||
val doubleton : 'a -> 'a -> 'a t
|
||||
(** Sequence with exactly two elements
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val cons : 'a -> 'a t -> 'a t
|
||||
(** [cons x l] yields [x], then yields from [l].
|
||||
Same as [append (singleton x) l]
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
|
||||
val snoc : 'a t -> 'a -> 'a t
|
||||
(** Same as {!cons} but yields the element after iterating on [l]
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val return : 'a -> 'a t
|
||||
(** Synonym to {!singleton}
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val pure : 'a -> 'a t
|
||||
(** Synonym to {!singleton}
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val repeat : 'a -> 'a t
|
||||
(** Infinite sequence of the same element. You may want to look
|
||||
at {!take} if you iterate on it. *)
|
||||
at {!take} and the likes if you iterate on it. *)
|
||||
|
||||
val iterate : ('a -> 'a) -> 'a -> 'a t
|
||||
(** [iterate f x] is the infinite sequence (x, f(x), f(f(x)), ...) *)
|
||||
(** [iterate f x] is the infinite sequence [x, f(x), f(f(x)), ...] *)
|
||||
|
||||
val forever : (unit -> 'b) -> 'b t
|
||||
(** Sequence that calls the given function to produce elements.
|
||||
|
|
@ -97,7 +119,8 @@ val cycle : 'a t -> 'a t
|
|||
(** {2 Consume a sequence} *)
|
||||
|
||||
val iter : ('a -> unit) -> 'a t -> unit
|
||||
(** Consume the sequence, passing all its arguments to the function *)
|
||||
(** Consume the sequence, passing all its arguments to the function.
|
||||
Basically [iter f seq] is just [seq f]. *)
|
||||
|
||||
val iteri : (int -> 'a -> unit) -> 'a t -> unit
|
||||
(** Iterate on elements and their index in the sequence *)
|
||||
|
|
@ -120,6 +143,15 @@ val for_all : ('a -> bool) -> 'a t -> bool
|
|||
val exists : ('a -> bool) -> 'a t -> bool
|
||||
(** Exists there some element satisfying the predicate? *)
|
||||
|
||||
val mem : ?eq:('a -> 'a -> bool) -> 'a -> 'a t -> bool
|
||||
(** Is the value a member of the sequence?
|
||||
@param eq the equality predicate to use (default [(=)])
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val find : ('a -> 'b option) -> 'a t -> 'b option
|
||||
(** Find the first element on which the function doesn't return [None]
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val length : 'a t -> int
|
||||
(** How long is the sequence? Forces the sequence. *)
|
||||
|
||||
|
|
@ -145,18 +177,43 @@ val flatMap : ('a -> 'b t) -> 'a t -> 'b t
|
|||
(** Monadic bind. Intuitively, it applies the function to every element of the
|
||||
initial sequence, and calls {!concat}. *)
|
||||
|
||||
val flat_map : ('a -> 'b t) -> 'a t -> 'b t
|
||||
(** Alias to {!flatMap} with a more explicit name
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val fmap : ('a -> 'b option) -> 'a t -> 'b t
|
||||
(** Specialized version of {!flatMap} for options. *)
|
||||
|
||||
val filter_map : ('a -> 'b option) -> 'a t -> 'b t
|
||||
(** Alias to {!fmap} with a more explicit name
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val intersperse : 'a -> 'a t -> 'a t
|
||||
(** Insert the single element between every element of the sequence *)
|
||||
|
||||
(** {2 Caching} *)
|
||||
|
||||
val persistent : 'a t -> 'a t
|
||||
(** Iterate on the sequence, storing elements in a data structure.
|
||||
(** Iterate on the sequence, storing elements in an efficient internal structure..
|
||||
The resulting sequence can be iterated on as many times as needed.
|
||||
{b Note}: calling persistent on an already persistent sequence
|
||||
will still make a new copy of the sequence! *)
|
||||
|
||||
val persistent_lazy : 'a t -> 'a t
|
||||
(** Lazy version of {!persistent}. When calling [persistent_lazy s],
|
||||
a new sequence [s'] is immediately returned (without actually consuming
|
||||
[s]) in constant time; the first time [s'] is iterated on,
|
||||
it also consumes [s] and caches its content into a inner data
|
||||
structure that will back [s'] for future iterations.
|
||||
|
||||
{b warning}: on the first traversal of [s'], if the traversal
|
||||
is interrupted prematurely ({!take}, etc.) then [s'] will not be
|
||||
memorized, and the next call to [s'] will traverse [s] again.
|
||||
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
(** {2 Misc} *)
|
||||
|
||||
val sort : ?cmp:('a -> 'a -> int) -> 'a t -> 'a t
|
||||
(** Sort the sequence. Eager, O(n) ram and O(n ln(n)) time.
|
||||
It iterates on elements of the argument sequence immediately,
|
||||
|
|
@ -173,9 +230,14 @@ val uniq : ?eq:('a -> 'a -> bool) -> 'a t -> 'a t
|
|||
like [fun seq -> map List.hd (group seq)]. *)
|
||||
|
||||
val product : 'a t -> 'b t -> ('a * 'b) t
|
||||
(** Cartesian product of the sequences. The first one is transformed
|
||||
by calling [persistent] on it, so that it can be traversed
|
||||
several times (outer loop of the product) *)
|
||||
(** Cartesian product of the sequences. When calling [product a b],
|
||||
the caller {b MUST} ensure that [b] can be traversed as many times
|
||||
as required (several times), possibly by calling {!persistent} on it
|
||||
beforehand. *)
|
||||
|
||||
val product2 : 'a t -> 'b t -> ('a, 'b) t2
|
||||
(** Binary version of {!product}. Same requirements.
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val join : join_row:('a -> 'b -> 'c option) -> 'a t -> 'b t -> 'c t
|
||||
(** [join ~join_row a b] combines every element of [a] with every
|
||||
|
|
@ -183,7 +245,7 @@ val join : join_row:('a -> 'b -> 'c option) -> 'a t -> 'b t -> 'c t
|
|||
the two elements do not combine. Assume that [b] allows for multiple
|
||||
iterations. *)
|
||||
|
||||
val unfoldr : ('b -> ('a * 'b) option) -> 'b -> 'a t
|
||||
val unfoldr : ('b -> ('a * 'b) option) -> 'b -> 'a t
|
||||
(** [unfoldr f b] will apply [f] to [b]. If it
|
||||
yields [Some (x,b')] then [x] is returned
|
||||
and unfoldr recurses with [b']. *)
|
||||
|
|
@ -200,13 +262,32 @@ val min : ?lt:('a -> 'a -> bool) -> 'a t -> 'a option
|
|||
(** Min element of the sequence, using the given comparison function.
|
||||
see {!max} for more details. *)
|
||||
|
||||
val head : 'a t -> 'a option
|
||||
(** First element, if any, otherwise [None]
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val head_exn : 'a t -> 'a
|
||||
(** First element, if any, fails
|
||||
@raise Invalid_argument if the sequence is empty
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val take : int -> 'a t -> 'a t
|
||||
(** Take at most [n] elements from the sequence. Works on infinite
|
||||
sequences. *)
|
||||
|
||||
val take_while : ('a -> bool) -> 'a t -> 'a t
|
||||
(** Take elements while they satisfy the predicate, then stops iterating.
|
||||
Will work on an infinite sequence [s] if the predicate is false for at
|
||||
least one element of [s].
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val drop : int -> 'a t -> 'a t
|
||||
(** Drop the [n] first elements of the sequence. Lazy. *)
|
||||
|
||||
val drop_while : ('a -> bool) -> 'a t -> 'a t
|
||||
(** Predicate version of {!drop}
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val rev : 'a t -> 'a t
|
||||
(** Reverse the sequence. O(n) memory and time, needs the
|
||||
sequence to be finite. The result is persistent and does
|
||||
|
|
@ -239,15 +320,22 @@ val map2_2 : ('a -> 'b -> 'c) -> ('a -> 'b -> 'd) -> ('a, 'b) t2 -> ('c, 'd) t2
|
|||
(** {2 Basic data structures converters} *)
|
||||
|
||||
val to_list : 'a t -> 'a list
|
||||
(** Convert the sequence into a list. Preserves order of elements.
|
||||
This function is tail-recursive, but consumes 2*n memory.
|
||||
If order doesn't matter to you, consider {!to_rev_list}. *)
|
||||
|
||||
val to_rev_list : 'a t -> 'a list
|
||||
(** Get the list of the reversed sequence (more efficient than {!to_list}) *)
|
||||
|
||||
val of_list : 'a list -> 'a t
|
||||
|
||||
val to_opt : 'a t -> 'a option
|
||||
(** Alias to {!head}
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val to_array : 'a t -> 'a array
|
||||
(** Convert to an array. Currently not very efficient because
|
||||
and intermediate list is used. *)
|
||||
an intermediate list is used. *)
|
||||
|
||||
val of_array : 'a array -> 'a t
|
||||
|
||||
|
|
@ -260,6 +348,10 @@ val array_slice : 'a array -> int -> int -> 'a t
|
|||
(** [array_slice a i j] Sequence of elements whose indexes range
|
||||
from [i] to [j] *)
|
||||
|
||||
val of_opt : 'a option -> 'a t
|
||||
(** Iterate on 0 or 1 values.
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val of_stream : 'a Stream.t -> 'a t
|
||||
(** Sequence of elements of a stream (usable only once) *)
|
||||
|
||||
|
|
@ -304,10 +396,20 @@ val hashtbl_values : ('a, 'b) Hashtbl.t -> 'b t
|
|||
val of_str : string -> char t
|
||||
val to_str : char t -> string
|
||||
|
||||
val concat_str : string t -> string
|
||||
(** Concatenate strings together, eagerly.
|
||||
Also see {!intersperse} to add a separator.
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
exception OneShotSequence
|
||||
(** Raised when the user tries to iterate several times on
|
||||
a transient iterator *)
|
||||
|
||||
val of_in_channel : in_channel -> char t
|
||||
(** Iterates on characters of the input (can block when one
|
||||
iterates over the sequence). If you need to iterate
|
||||
several times on this sequence, use {!persistent}. *)
|
||||
several times on this sequence, use {!persistent}.
|
||||
@raise OneShotSequence when used more than once. *)
|
||||
|
||||
val to_buffer : char t -> Buffer.t -> unit
|
||||
(** Copy content of the sequence into the buffer *)
|
||||
|
|
@ -327,6 +429,7 @@ val to_set : (module Set.S with type elt = 'a and type t = 'b) -> 'a t -> 'b
|
|||
(** Convert the sequence to a set, given the proper set module *)
|
||||
|
||||
type 'a gen = unit -> 'a option
|
||||
type 'a klist = unit -> [`Nil | `Cons of 'a * 'a klist]
|
||||
|
||||
val of_gen : 'a gen -> 'a t
|
||||
(** Traverse eagerly the generator and build a sequence from it *)
|
||||
|
|
@ -334,6 +437,12 @@ val of_gen : 'a gen -> 'a t
|
|||
val to_gen : 'a t -> 'a gen
|
||||
(** Make the sequence persistent (O(n)) and then iterate on it. Eager. *)
|
||||
|
||||
val of_klist : 'a klist -> 'a t
|
||||
(** Iterate on the lazy list *)
|
||||
|
||||
val to_klist : 'a t -> 'a klist
|
||||
(** Make the sequence persistent and then iterate on it. Eager. *)
|
||||
|
||||
(** {2 Functorial conversions between sets and sequences} *)
|
||||
|
||||
module Set : sig
|
||||
|
|
@ -341,11 +450,17 @@ module Set : sig
|
|||
include Set.S
|
||||
val of_seq : elt sequence -> t
|
||||
val to_seq : t -> elt sequence
|
||||
|
||||
val to_list : t -> elt list
|
||||
(** @since NEXT_RELEASE *)
|
||||
|
||||
val of_list : elt list -> t
|
||||
(** @since NEXT_RELEASE *)
|
||||
end
|
||||
|
||||
(** Create an enriched Set module from the given one *)
|
||||
module Adapt(X : Set.S) : S with type elt = X.elt and type t = X.t
|
||||
|
||||
|
||||
(** Functor to build an extended Set module from an ordered type *)
|
||||
module Make(X : Set.OrderedType) : S with type elt = X.t
|
||||
end
|
||||
|
|
@ -359,6 +474,12 @@ module Map : sig
|
|||
val of_seq : (key * 'a) sequence -> 'a t
|
||||
val keys : 'a t -> key sequence
|
||||
val values : 'a t -> 'a sequence
|
||||
|
||||
val to_list : 'a t -> (key * 'a) list
|
||||
(** @since NEXT_RELEASE *)
|
||||
|
||||
val of_list : (key * 'a) list -> 'a t
|
||||
(** @since NEXT_RELEASE *)
|
||||
end
|
||||
|
||||
(** Adapt a pre-existing Map module to make it sequence-aware *)
|
||||
|
|
@ -390,26 +511,95 @@ val random_list : 'a list -> 'a t
|
|||
|
||||
module Infix : sig
|
||||
val (--) : int -> int -> int t
|
||||
(** [a -- b] is the range of integers from [a] to [b], both included,
|
||||
in increasing order. It will therefore be empty if [a > b]. *)
|
||||
|
||||
val (--^) : int -> int -> int t
|
||||
(** [a --^ b] is the range of integers from [b] to [a], both included,
|
||||
in decreasing order (starts from [a]).
|
||||
It will therefore be empty if [a < b]. *)
|
||||
|
||||
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
|
||||
(** Monadic bind (infix version of {!flat_map}
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val (>|=) : 'a t -> ('a -> 'b) -> 'b t
|
||||
(** Infix version of {!map}
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val (<*>) : ('a -> 'b) t -> 'a t -> 'b t
|
||||
(** Applicative operator (product+application)
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
val (<+>) : 'a t -> 'a t -> 'a t
|
||||
(** Concatenation of sequences
|
||||
@since NEXT_RELEASE *)
|
||||
end
|
||||
|
||||
include module type of Infix
|
||||
|
||||
|
||||
(** {2 Pretty printing of sequences} *)
|
||||
|
||||
val print : ?start:string -> ?stop:string -> ?sep:string ->
|
||||
(Format.formatter -> 'a -> unit) ->
|
||||
val pp_seq : ?sep:string -> (Format.formatter -> 'a -> unit) ->
|
||||
Format.formatter -> 'a t -> unit
|
||||
(** Pretty print a sequence of ['a], using the given pretty printer
|
||||
to print each elements. An optional separator string can be provided. *)
|
||||
|
||||
val pp : ?start:string -> ?stop:string -> ?sep:string ->
|
||||
(Buffer.t -> 'a -> unit) ->
|
||||
Buffer.t -> 'a t -> unit
|
||||
val pp_buf : ?sep:string -> (Buffer.t -> 'a -> unit) ->
|
||||
Buffer.t -> 'a t -> unit
|
||||
(** Print into a buffer *)
|
||||
|
||||
val to_string : ?start:string -> ?stop:string -> ?sep:string ->
|
||||
(Buffer.t -> 'a -> unit) -> 'a t -> string
|
||||
val to_string : ?sep:string -> ('a -> string) -> 'a t -> string
|
||||
(** Print into a string *)
|
||||
|
||||
(** {2 Basic IO}
|
||||
|
||||
Very basic interface to manipulate files as sequence of chunks/lines. The
|
||||
sequences take care of opening and closing files properly; every time
|
||||
one iterates over a sequence, the file is opened/closed again.
|
||||
|
||||
Example: copy a file ["a"] into file ["b"], removing blank lines:
|
||||
|
||||
{[
|
||||
Sequence.(IO.lines_of "a" |> filter (fun l-> l<> "") |> IO.write_lines "b");;
|
||||
]}
|
||||
|
||||
By chunks of [4096] bytes:
|
||||
|
||||
{[
|
||||
Sequence.IO.(chunks_of ~size:4096 "a" |> write_to "b");;
|
||||
]}
|
||||
|
||||
@since NEXT_RELEASE *)
|
||||
|
||||
module IO : sig
|
||||
val lines_of : ?mode:int -> ?flags:open_flag list ->
|
||||
string -> string t
|
||||
(** [lines_of filename] reads all lines of the given file. It raises the
|
||||
same exception as would opening the file and read from it, except
|
||||
from [End_of_file] (which is caught). The file is {b always} properly
|
||||
closed.
|
||||
Every time the sequence is iterated on, the file is opened again, so
|
||||
different iterations might return different results
|
||||
@param mode default [0o644]
|
||||
@param flags default: [[Open_rdonly]] *)
|
||||
|
||||
val chunks_of : ?mode:int -> ?flags:open_flag list -> ?size:int ->
|
||||
string -> string t
|
||||
(** Read chunks of the given [size] from the file. The last chunk might be
|
||||
smaller. Behaves like {!lines_of} regarding errors and options.
|
||||
Every time the sequence is iterated on, the file is opened again, so
|
||||
different iterations might return different results *)
|
||||
|
||||
val write_to : ?mode:int -> ?flags:open_flag list ->
|
||||
string -> string t -> unit
|
||||
(** [write_to filename seq] writes all strings from [seq] into the given
|
||||
file. It takes care of opening and closing the file.
|
||||
@param mode default [0o644]
|
||||
@param flags used by [open_out_gen]. Default: [[Open_creat;Open_wronly]]. *)
|
||||
|
||||
val write_lines : ?mode:int -> ?flags:open_flag list ->
|
||||
string -> string t -> unit
|
||||
(** Same as {!write_to}, but intercales ['\n'] between each string *)
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue