mirror of
https://github.com/c-cube/ocaml-containers.git
synced 2025-12-05 19:00:31 -05:00
718 lines
16 KiB
OCaml
718 lines
16 KiB
OCaml
(* This file is free software, part of containers. See file "license" for more details. *)
|
|
|
|
(** {1 Growable, mutable vector} *)
|
|
|
|
type rw = [ `RW ]
|
|
type ro = [ `RO ]
|
|
type 'a iter = ('a -> unit) -> unit
|
|
type 'a gen = unit -> 'a option
|
|
type 'a equal = 'a -> 'a -> bool
|
|
type 'a ord = 'a -> 'a -> int
|
|
type 'a printer = Format.formatter -> 'a -> unit
|
|
|
|
type ('a, 'mut) t = {
|
|
mutable size: int;
|
|
mutable vec: 'a array;
|
|
}
|
|
(** A vector of 'a. *)
|
|
|
|
type 'a vector = ('a, rw) t
|
|
type 'a ro_vector = ('a, ro) t
|
|
|
|
external as_float_arr : 'a array -> float array = "%identity"
|
|
external as_obj_arr : 'a array -> Obj.t array = "%identity"
|
|
|
|
let fill_with_junk_ (a : _ array) i len : unit =
|
|
if Obj.(tag (repr a) = double_array_tag) then
|
|
Array.fill (as_float_arr a) i len 0.
|
|
else
|
|
Array.fill (as_obj_arr a) i len (Obj.repr ())
|
|
|
|
let freeze v = { size = v.size; vec = v.vec }
|
|
let freeze_copy v = { size = v.size; vec = Array.sub v.vec 0 v.size }
|
|
let create () = { size = 0; vec = [||] }
|
|
|
|
let create_with ?(capacity = 128) x =
|
|
let vec = Array.make capacity x in
|
|
fill_with_junk_ vec 0 capacity;
|
|
{ size = 0; vec }
|
|
|
|
let return x = { size = 1; vec = [| x |] }
|
|
let make n x = { size = n; vec = Array.make n x }
|
|
let init n f = { size = n; vec = Array.init n f }
|
|
|
|
(* is the underlying array empty? *)
|
|
let[@inline] array_is_empty_ v = Array.length v.vec = 0
|
|
|
|
(* next capacity, if current one is [n] *)
|
|
let[@inline] next_grow_ n = min Sys.max_array_length (n + (n lsr 1) + 2)
|
|
|
|
(* resize the underlying array using x to temporarily fill the array *)
|
|
let resize_ v newcapacity x =
|
|
assert (newcapacity >= v.size);
|
|
assert (not (array_is_empty_ v));
|
|
let new_vec = Array.make newcapacity x in
|
|
Array.blit v.vec 0 new_vec 0 v.size;
|
|
fill_with_junk_ new_vec v.size (newcapacity - v.size);
|
|
v.vec <- new_vec;
|
|
()
|
|
|
|
(* grow the array, using [x] as a filler if required *)
|
|
let grow_with_ v ~filler:x =
|
|
if array_is_empty_ v then (
|
|
let len = 4 in
|
|
v.vec <- Array.make len x;
|
|
(* do not really use [x], it was just for knowing the type *)
|
|
fill_with_junk_ v.vec 0 len
|
|
) else (
|
|
let n = Array.length v.vec in
|
|
let size = next_grow_ n in
|
|
if size = n then invalid_arg "vec: can't grow any further";
|
|
resize_ v size v.vec.(0)
|
|
)
|
|
|
|
(* v is not empty; ensure it has at least [size] slots.
|
|
|
|
Use a doubling-size strategy so that calling many times [ensure] will
|
|
behave well *)
|
|
let ensure_assuming_not_empty_ v ~size =
|
|
if size > Sys.max_array_length then
|
|
invalid_arg "vec.ensure: size too big"
|
|
else if size < Array.length v.vec then
|
|
()
|
|
(* nothing to do *)
|
|
else (
|
|
let n = ref (Array.length v.vec) in
|
|
while !n < size do
|
|
n := next_grow_ !n
|
|
done;
|
|
resize_ v !n v.vec.(0)
|
|
)
|
|
|
|
let ensure_with ~init v size =
|
|
if array_is_empty_ v then (
|
|
v.vec <- Array.make size init;
|
|
fill_with_junk_ v.vec 0 size
|
|
) else
|
|
ensure_assuming_not_empty_ v ~size
|
|
|
|
let ensure v size =
|
|
if not (array_is_empty_ v) then ensure_assuming_not_empty_ v ~size
|
|
|
|
let[@inline] clear v = v.size <- 0
|
|
|
|
let clear_and_reset v =
|
|
v.size <- 0;
|
|
v.vec <- [||]
|
|
|
|
(* TODO*)
|
|
(*
|
|
let v = create() in
|
|
let a = Weak.create 1 in
|
|
push v ("hello"^"world");
|
|
Weak.set a 0 (Some (get v 0));
|
|
Gc.full_major(); Gc.compact();
|
|
assert_bool "is alive" (Weak.check a 0);
|
|
Gc.full_major(); Gc.compact();
|
|
assert_equal None (Weak.get a 0);
|
|
*)
|
|
|
|
let[@inline] is_empty v = v.size = 0
|
|
|
|
[@@@ifge 4.13]
|
|
|
|
let[@inline] push_unsafe_ v x =
|
|
Sys.opaque_identity (Array.unsafe_set v.vec v.size x);
|
|
v.size <- v.size + 1
|
|
|
|
[@@@else_]
|
|
|
|
let[@inline never] push_unsafe_ v x =
|
|
Array.unsafe_set v.vec v.size x;
|
|
v.size <- v.size + 1
|
|
|
|
[@@@endif]
|
|
|
|
let push v x =
|
|
if v.size = Array.length v.vec then grow_with_ v ~filler:x;
|
|
push_unsafe_ v x
|
|
|
|
let resize_with v f size =
|
|
if size < 0 then invalid_arg "Vec.resize_with";
|
|
if Array.length v.vec = 0 then (
|
|
let new_vec = Array.init size f in
|
|
v.vec <- new_vec;
|
|
v.size <- size
|
|
) else (
|
|
ensure_assuming_not_empty_ v ~size;
|
|
let { size = cur_size; vec } = v in
|
|
for i = cur_size to size - 1 do
|
|
Array.unsafe_set vec i (f i)
|
|
done;
|
|
assert (size <= Array.length v.vec);
|
|
v.size <- size
|
|
)
|
|
|
|
let resize_with_init v ~init size =
|
|
if size < 0 then invalid_arg "Vec.resize_with_init";
|
|
if Array.length v.vec = 0 then (
|
|
let vec = Array.make size init in
|
|
v.vec <- vec;
|
|
v.size <- size
|
|
) else (
|
|
ensure_assuming_not_empty_ v ~size;
|
|
(* nothing will change [v] *)
|
|
for i = v.size to size - 1 do
|
|
Array.unsafe_set v.vec i init
|
|
done;
|
|
v.size <- size
|
|
)
|
|
|
|
(** Add all elements of b to a *)
|
|
let append a b =
|
|
if array_is_empty_ a then
|
|
if array_is_empty_ b then
|
|
()
|
|
else (
|
|
a.vec <- Array.copy b.vec;
|
|
a.size <- b.size
|
|
)
|
|
else (
|
|
ensure_assuming_not_empty_ a ~size:(a.size + b.size);
|
|
assert (Array.length a.vec >= a.size + b.size);
|
|
Array.blit b.vec 0 a.vec a.size b.size;
|
|
a.size <- a.size + b.size
|
|
)
|
|
|
|
[@@@ifge 4.13]
|
|
|
|
let[@inline] get v i =
|
|
if i < 0 || i >= v.size then invalid_arg "CCVector.get";
|
|
(* NOTE: over eager inlining seems to miscompile for int32 at least (#454) *)
|
|
Sys.opaque_identity (Array.unsafe_get v.vec i)
|
|
|
|
let[@inline] set v i x =
|
|
if i < 0 || i >= v.size then invalid_arg "CCVector.set";
|
|
Array.unsafe_set v.vec i x
|
|
|
|
[@@@else_]
|
|
|
|
let[@inline never] get v i =
|
|
if i < 0 || i >= v.size then invalid_arg "CCVector.get";
|
|
Array.unsafe_get v.vec i
|
|
|
|
let[@inline never] set v i x =
|
|
if i < 0 || i >= v.size then invalid_arg "CCVector.set";
|
|
Array.unsafe_set v.vec i x
|
|
|
|
[@@@endif]
|
|
|
|
let remove_and_shift v i =
|
|
if i < 0 || i >= v.size then invalid_arg "CCVector.remove";
|
|
(* if v.(i) not the last element, then put last element at index i *)
|
|
if i < v.size - 1 then Array.blit v.vec (i + 1) v.vec i (v.size - i - 1);
|
|
(* remove one element *)
|
|
v.size <- v.size - 1;
|
|
fill_with_junk_ v.vec v.size 1
|
|
|
|
let remove_unordered v i =
|
|
if i < 0 || i >= v.size then invalid_arg "CCVector.remove_unordered";
|
|
(* if v.(i) not the last element, then put last element at index i *)
|
|
if i < v.size - 1 then v.vec.(i) <- v.vec.(v.size - 1);
|
|
(* remove one element *)
|
|
v.size <- v.size - 1;
|
|
fill_with_junk_ v.vec v.size 1
|
|
|
|
let insert v i x =
|
|
(* Note that we can insert at i=v.size *)
|
|
if i < 0 || i > v.size then invalid_arg "CCVector.insert";
|
|
if v.size = Array.length v.vec then grow_with_ v ~filler:x;
|
|
(* Shift the following elements, then put the element at i *)
|
|
if i < v.size then Array.blit v.vec i v.vec (i + 1) (v.size - i);
|
|
v.vec.(i) <- x;
|
|
v.size <- v.size + 1
|
|
|
|
let[@inline] append_iter a i = i (fun x -> push a x)
|
|
let append_seq a seq = Seq.iter (fun x -> push a x) seq
|
|
|
|
let append_array a b =
|
|
let len_b = Array.length b in
|
|
if array_is_empty_ a then (
|
|
a.vec <- Array.copy b;
|
|
a.size <- len_b
|
|
) else (
|
|
ensure_assuming_not_empty_ a ~size:(a.size + len_b);
|
|
Array.blit b 0 a.vec a.size len_b;
|
|
a.size <- a.size + len_b
|
|
)
|
|
|
|
let append_list a b =
|
|
match b with
|
|
| [] -> ()
|
|
| x :: _ ->
|
|
(* need to push at least one elem *)
|
|
let len_a = a.size in
|
|
let len_b = List.length b in
|
|
ensure_with ~init:x a (len_a + len_b);
|
|
List.iter (push_unsafe_ a) b;
|
|
()
|
|
|
|
let rec append_gen a b =
|
|
match b () with
|
|
| None -> ()
|
|
| Some x ->
|
|
push a x;
|
|
append_gen a b
|
|
|
|
let equal eq v1 v2 =
|
|
v1.size = v2.size
|
|
&&
|
|
let n = v1.size in
|
|
let rec check i = i = n || (eq (get v1 i) (get v2 i) && check (i + 1)) in
|
|
check 0
|
|
|
|
let compare cmp v1 v2 =
|
|
let n = min v1.size v2.size in
|
|
let rec check i =
|
|
if i = n then
|
|
compare v1.size v2.size
|
|
else (
|
|
let c = cmp (get v1 i) (get v2 i) in
|
|
if c = 0 then
|
|
check (i + 1)
|
|
else
|
|
c
|
|
)
|
|
in
|
|
check 0
|
|
|
|
exception Empty
|
|
|
|
let pop_exn v =
|
|
if v.size = 0 then raise Empty;
|
|
let new_size = v.size - 1 in
|
|
v.size <- new_size;
|
|
let x = v.vec.(new_size) in
|
|
(* free last element *)
|
|
fill_with_junk_ v.vec new_size 1;
|
|
x
|
|
|
|
let pop v = try Some (pop_exn v) with Empty -> None
|
|
|
|
let[@inline] top v =
|
|
if v.size = 0 then
|
|
None
|
|
else
|
|
Some (Array.unsafe_get v.vec (v.size - 1))
|
|
|
|
let[@inline] top_exn v =
|
|
if v.size = 0 then raise Empty;
|
|
(* NOTE: over eager inlining seems to miscompile for int32 at least (#454) *)
|
|
Sys.opaque_identity (Array.unsafe_get v.vec (v.size - 1))
|
|
|
|
let[@inline] copy v = { size = v.size; vec = Array.sub v.vec 0 v.size }
|
|
|
|
let truncate v n =
|
|
let old_size = v.size in
|
|
if n < old_size then (
|
|
v.size <- n;
|
|
(* free elements by erasing them *)
|
|
fill_with_junk_ v.vec n (old_size - n)
|
|
)
|
|
|
|
let shrink_to_fit v : unit =
|
|
if v.size = 0 then
|
|
v.vec <- [||]
|
|
else if v.size < Array.length v.vec then
|
|
v.vec <- Array.sub v.vec 0 v.size
|
|
|
|
let sort' cmp v =
|
|
(* possibly copy array (to avoid junk at its end), then sort the array *)
|
|
let a =
|
|
if Array.length v.vec = v.size then
|
|
v.vec
|
|
else
|
|
Array.sub v.vec 0 v.size
|
|
in
|
|
Array.fast_sort cmp a;
|
|
v.vec <- a
|
|
|
|
let sort cmp v =
|
|
let v' = { size = v.size; vec = Array.sub v.vec 0 v.size } in
|
|
Array.sort cmp v'.vec;
|
|
v'
|
|
|
|
let uniq_sort cmp v =
|
|
sort' cmp v;
|
|
let n = v.size in
|
|
(* traverse to remove duplicates. i= current index,
|
|
j=current append index, j<=i. new_size is the size
|
|
the vector will have after removing duplicates. *)
|
|
let rec traverse prev i j =
|
|
if i >= n then
|
|
()
|
|
(* done traversing *)
|
|
else if cmp prev v.vec.(i) = 0 then (
|
|
v.size <- v.size - 1;
|
|
traverse prev (i + 1) j (* duplicate, remove it *)
|
|
) else (
|
|
v.vec.(j) <- v.vec.(i);
|
|
traverse v.vec.(i) (i + 1) (j + 1)
|
|
)
|
|
(* keep it *)
|
|
in
|
|
if v.size > 0 then traverse v.vec.(0) 1 1
|
|
(* start at 1, to get the first element in hand *)
|
|
|
|
let iter k v =
|
|
let { vec; size = n } = v in
|
|
for i = 0 to n - 1 do
|
|
k (Array.unsafe_get vec i)
|
|
done
|
|
|
|
let iteri k v =
|
|
let { vec; size = n } = v in
|
|
for i = 0 to n - 1 do
|
|
k i (Array.unsafe_get vec i)
|
|
done
|
|
|
|
let map f v =
|
|
if array_is_empty_ v then
|
|
create ()
|
|
else (
|
|
let { vec; size } = v in
|
|
let vec = Array.init size (fun i -> f (Array.unsafe_get vec i)) in
|
|
{ size; vec }
|
|
)
|
|
|
|
let mapi f v =
|
|
if array_is_empty_ v then
|
|
create ()
|
|
else (
|
|
let { vec; size } = v in
|
|
let vec = Array.init size (fun i -> f i (Array.unsafe_get vec i)) in
|
|
{ size; vec }
|
|
)
|
|
|
|
let map_in_place f v =
|
|
let { vec; size = n } = v in
|
|
for i = 0 to n - 1 do
|
|
Array.unsafe_set vec i (f (Array.unsafe_get vec i))
|
|
done
|
|
|
|
let filter_in_place p v =
|
|
let i = ref 0 in
|
|
(* cur element *)
|
|
let j = ref 0 in
|
|
(* cur insertion point *)
|
|
let n = v.size in
|
|
while !i < n do
|
|
if p v.vec.(!i) then (
|
|
(* move element i at the first empty slot.
|
|
invariant: i >= j*)
|
|
if !i > !j then v.vec.(!j) <- v.vec.(!i);
|
|
incr i;
|
|
incr j
|
|
) else
|
|
incr i
|
|
done;
|
|
(* free elements *)
|
|
fill_with_junk_ v.vec !j (v.size - !j);
|
|
v.size <- !j
|
|
|
|
let filter p v =
|
|
if array_is_empty_ v then
|
|
create ()
|
|
else (
|
|
let v' = create_with ~capacity:v.size v.vec.(0) in
|
|
iter (fun x -> if p x then push_unsafe_ v' x) v;
|
|
v'
|
|
)
|
|
|
|
let fold f acc v =
|
|
let { vec; size } = v in
|
|
let rec fold acc i =
|
|
if i = size then
|
|
acc
|
|
else (
|
|
let x = Array.unsafe_get vec i in
|
|
fold (f acc x) (i + 1)
|
|
)
|
|
in
|
|
fold acc 0
|
|
|
|
let foldi f acc v =
|
|
let { vec; size } = v in
|
|
let rec fold acc i =
|
|
if i = size then
|
|
acc
|
|
else (
|
|
let x = Array.unsafe_get vec i in
|
|
fold (f i acc x) (i + 1)
|
|
)
|
|
in
|
|
fold acc 0
|
|
|
|
let exists p v =
|
|
let n = v.size in
|
|
let rec check i =
|
|
if i = n then
|
|
false
|
|
else
|
|
p v.vec.(i) || check (i + 1)
|
|
in
|
|
check 0
|
|
|
|
let for_all p v =
|
|
let n = v.size in
|
|
let rec check i =
|
|
if i = n then
|
|
true
|
|
else
|
|
p v.vec.(i) && check (i + 1)
|
|
in
|
|
check 0
|
|
|
|
let member ~eq x v = exists (eq x) v
|
|
|
|
let find_internal_ p v =
|
|
let n = v.size in
|
|
let rec check i =
|
|
if i = n then
|
|
raise_notrace Not_found
|
|
else (
|
|
let x = v.vec.(i) in
|
|
if p x then
|
|
x
|
|
else
|
|
check (i + 1)
|
|
)
|
|
in
|
|
check 0
|
|
|
|
let find_exn p v = try find_internal_ p v with Not_found -> raise Not_found
|
|
let find p v = try Some (find_internal_ p v) with Not_found -> None
|
|
|
|
let find_map f v =
|
|
let n = v.size in
|
|
let rec search i =
|
|
if i = n then
|
|
None
|
|
else (
|
|
match f v.vec.(i) with
|
|
| None -> search (i + 1)
|
|
| Some _ as res -> res
|
|
)
|
|
in
|
|
search 0
|
|
|
|
let filter_map f v =
|
|
let v' = create () in
|
|
iter
|
|
(fun x ->
|
|
match f x with
|
|
| None -> ()
|
|
| Some y -> push v' y)
|
|
v;
|
|
v'
|
|
|
|
let filter_map_in_place f v =
|
|
let i = ref 0 in
|
|
(* cur element *)
|
|
let j = ref 0 in
|
|
(* cur insertion point *)
|
|
let n = v.size in
|
|
while !i < n do
|
|
match f v.vec.(!i) with
|
|
| None -> incr i (* drop *)
|
|
| Some y ->
|
|
(* move element i at the first empty slot.
|
|
invariant: i >= j*)
|
|
v.vec.(!j) <- y;
|
|
incr i;
|
|
incr j
|
|
done;
|
|
(* free elements *)
|
|
fill_with_junk_ v.vec !j (v.size - !j);
|
|
v.size <- !j
|
|
|
|
let flat_map f v =
|
|
let v' = create () in
|
|
iter (fun x -> iter (push v') (f x)) v;
|
|
v'
|
|
|
|
let flat_map_seq f v =
|
|
let v' = create () in
|
|
iter
|
|
(fun x ->
|
|
let seq = f x in
|
|
append_seq v' seq)
|
|
v;
|
|
v'
|
|
|
|
let flat_map_list f v =
|
|
let v' = create () in
|
|
iter
|
|
(fun x ->
|
|
let l = f x in
|
|
append_list v' l)
|
|
v;
|
|
v'
|
|
|
|
let monoid_product f a1 a2 : _ t =
|
|
let na1 = a1.size in
|
|
init (na1 * a2.size) (fun i_prod ->
|
|
let i = i_prod mod na1 in
|
|
let j = i_prod / na1 in
|
|
f a1.vec.(i) a2.vec.(j))
|
|
|
|
let ( >>= ) x f = flat_map f x
|
|
let ( >|= ) x f = map f x
|
|
|
|
let rev_in_place v =
|
|
if v.size > 0 then (
|
|
let n = v.size in
|
|
let vec = v.vec in
|
|
for i = 0 to (n - 1) / 2 do
|
|
let x = Array.unsafe_get vec i in
|
|
let y = Array.unsafe_get vec (n - i - 1) in
|
|
Array.unsafe_set vec i y;
|
|
Array.unsafe_set vec (n - i - 1) x
|
|
done
|
|
)
|
|
|
|
let rev v =
|
|
let v' = copy v in
|
|
rev_in_place v';
|
|
v'
|
|
|
|
let rev_iter f v =
|
|
let { vec; size = n } = v in
|
|
for i = n - 1 downto 0 do
|
|
f (Array.unsafe_get vec i)
|
|
done
|
|
|
|
let size v = v.size
|
|
let length v = v.size
|
|
let capacity v = Array.length v.vec
|
|
let unsafe_get_array v = v.vec
|
|
|
|
let of_iter ?(init = create ()) seq =
|
|
append_iter init seq;
|
|
init
|
|
|
|
let of_seq ?(init = create ()) seq =
|
|
append_seq init seq;
|
|
init
|
|
|
|
let to_iter v k = iter k v
|
|
|
|
let to_iter_rev v k =
|
|
let { vec; size = n } = v in
|
|
for i = n - 1 downto 0 do
|
|
k (Array.unsafe_get vec i)
|
|
done
|
|
|
|
let to_seq v =
|
|
let { size; vec } = v in
|
|
let rec aux i () =
|
|
if i >= size then
|
|
Seq.Nil
|
|
else
|
|
Seq.Cons (vec.(i), aux (i + 1))
|
|
in
|
|
aux 0
|
|
|
|
let to_seq_rev v =
|
|
let { size; vec } = v in
|
|
let rec aux i () =
|
|
if i < 0 then
|
|
Seq.Nil
|
|
else
|
|
Seq.Cons (vec.(i), aux (i - 1))
|
|
in
|
|
aux (size - 1)
|
|
|
|
let slice_iter v start len =
|
|
assert (start >= 0 && len >= 0);
|
|
fun k ->
|
|
let { size; vec } = v in
|
|
assert (start + len <= size);
|
|
for i = start to start + len - 1 do
|
|
let x = Array.unsafe_get vec i in
|
|
k x
|
|
done
|
|
|
|
let slice v = v.vec, 0, v.size
|
|
|
|
let ( -- ) i j =
|
|
if i > j then
|
|
init (i - j + 1) (fun k -> i - k)
|
|
else
|
|
init (j - i + 1) (fun k -> i + k)
|
|
|
|
let ( --^ ) i j =
|
|
if i = j then
|
|
create ()
|
|
else if i > j then
|
|
init (i - j) (fun k -> i - k)
|
|
else
|
|
init (j - i) (fun k -> i + k)
|
|
|
|
let of_array a =
|
|
if Array.length a = 0 then
|
|
create ()
|
|
else
|
|
{ size = Array.length a; vec = Array.copy a }
|
|
|
|
let of_list l =
|
|
match l with
|
|
| [] -> create ()
|
|
| [ x ] -> return x
|
|
| [ x; y ] -> { size = 2; vec = [| x; y |] }
|
|
| x :: _ ->
|
|
let v = create_with ~capacity:(List.length l) x in
|
|
List.iter (push_unsafe_ v) l;
|
|
v
|
|
|
|
let to_array v = Array.sub v.vec 0 v.size
|
|
let to_list v = List.rev (fold (fun acc x -> x :: acc) [] v)
|
|
|
|
let of_gen ?(init = create ()) g =
|
|
let rec aux g =
|
|
match g () with
|
|
| None -> init
|
|
| Some x ->
|
|
push init x;
|
|
aux g
|
|
in
|
|
aux g
|
|
|
|
let to_gen v =
|
|
let { size; vec } = v in
|
|
let i = ref 0 in
|
|
fun () ->
|
|
if !i < size then (
|
|
let x = vec.(!i) in
|
|
incr i;
|
|
Some x
|
|
) else
|
|
None
|
|
|
|
let to_string ?(start = "") ?(stop = "") ?(sep = ", ") item_to_string v =
|
|
start ^ (to_list v |> List.map item_to_string |> String.concat sep) ^ stop
|
|
|
|
let pp ?(pp_start = fun _ () -> ()) ?(pp_stop = fun _ () -> ())
|
|
?(pp_sep = fun fmt () -> Format.fprintf fmt ",@ ") pp_item fmt v =
|
|
pp_start fmt ();
|
|
iteri
|
|
(fun i x ->
|
|
if i > 0 then pp_sep fmt ();
|
|
pp_item fmt x)
|
|
v;
|
|
pp_stop fmt ()
|
|
|
|
let ( let+ ) = ( >|= )
|
|
let ( let* ) = ( >>= )
|
|
let[@inline] ( and+ ) a1 a2 = monoid_product (fun x y -> x, y) a1 a2
|
|
let ( and* ) = ( and+ )
|