Module KTDequeSource

KTDeque - Rocq-extracted persistent real-time deque

A purely functional deque extracted from the Rocq development of the Kaplan-Tarjan real-time design. Every value of type kChain is an immutable snapshot; an operation returns a new snapshot sharing structure with the old one, so you can fork the deque, mutate one branch, and the other branch is unaffected.

This module is the OCaml extraction of the Rocq formalisation, hosted at https://github.com/yurug/kaplan2. The public theorem bundle for the extracted kt4 operation family lives in rocq/KTDeque/DequePtr/PublicTheorems.v. It packages sequence correctness, regularity preservation, bounded green_of_red_k dispatch, sufficient no-Fail preconditions, and a numeric bridge from the dispatch bound to the packet one-repair constants. A closed counterexample shows the reusable totality-state predicate is not itself push-closed without level/future-repair closure; the theorem bundle now also exposes a level-aware public-state candidate for level-0 inputs. It also derives the Green-tail and nested red-packet repair witnesses from shape + levels. Preserving that candidate, deriving the remaining bottom repair witnesses, and completing the pure-to-imperative cost refinement remain release-gate obligations before making an unqualified mechanically verified WC O(1) claim. For *why* the algorithm is correct and elegant โ€” why "no two reds adjacent" delivers worst-case O(1), why packets aggregate yellow runs into a single allocation โ€” read kb/spec/why-bounded-cascade.md first.

Quick start

  open KTDeque

  let push_or_fail x q =
    match push_kt2 (Coq_E.base x) q with
    | Some q' -> q'
    | None    -> failwith "regularity violated"

  let q = empty_kchain
  let q = push_or_fail 1 q
  let q = push_or_fail 2 q
  (* q now holds [2; 1] *)

  let xs = kchain_to_list q  (* [2; 1] *)

See also the hello example in the project monorepo for a fully runnable program.

Public API: where to look

Use one of the two operation families and the helpers below. Everything else is an internal extraction artefact (see Internal: extraction prelude at the bottom of this file).

Operations:

Element wrapping:

Why are there so many push_* functions?

The Rocq development proves several variants of each operation so that some lemmas can be stated against the variant most convenient for that proof, then transported to the production variant via equivalence theorems. Specifically:

Use push_kt2 for clarity, push_kt4 for performance. The public theorem bundle currently targets the extracted kt4 family. The other variants are not part of the supported public API.

A note for re-extraction

This .mli file is checked into git as a snapshot in the project monorepo. The body (every type and val declaration) is verbatim Coq extraction output; the surrounding (** ... *) documentation is hand-written. When regenerating, please preserve the literate headers and per-binding doc comments.

Internal: extraction prelude

Coq's extracted module needs a small prelude of type aliases and helpers. None of this is part of the user-facing API.

Sourcetype __ = Obj.t
Sourceval app : 'a1 list -> 'a1 list -> 'a1 list
Sourcetype ('a, 'p) sigT =
  1. | ExistT of 'a * 'p
Sourceval projT1 : ('a1, 'a2) sigT -> 'a1
Sourceval projT2 : ('a1, 'a2) sigT -> 'a2
Sourcemodule Nat : sig ... end
Sourcetype 'a xpow = __
Sourceval xflat : int -> 'a1 xpow -> 'a1 list

Element abstraction

The extracted deque stores \'a Coq_E.t values, not naked \'a. A \'a Coq_E.t is a *level-l element*: at level 0 it is just an \'a; at level 1 it is a pair of two \'as; at level l it is a balanced binary tree of 2^l base values. This is what makes the deque branch binarily as it descends through nested packets (see why-bounded-cascade.md ยง1 for the structural picture).

User code only needs three of these functions:

pair and unpair are internal โ€” the deque uses them when cascading.

ElementTree is the canonical instance; the deque uses it via the alias Coq_E.

Sourcemodule ElementTree : sig ... end

Buffer (Buf5) โ€” internal storage at every level

A \'x buf5 holds 0 to 5 elements at one chain level. The buffer's *colour* is determined by its size:

See why-bounded-cascade.md ยง2 for the regularity invariant that combines these colours. User code never sees buf5 directly.

Sourcetype 'x buf5 =
  1. | B0
  2. | B1 of 'x
  3. | B2 of 'x * 'x
  4. | B3 of 'x * 'x * 'x
  5. | B4 of 'x * 'x * 'x * 'x
  6. | B5 of 'x * 'x * 'x * 'x * 'x

Internal: buffer-level operations

Building blocks for the chain-level operations above. Not part of the public API.

Sourceval buf5_size : 'a1 buf5 -> int
Sourceval buf5_seq : ('a1 -> 'a2 list) -> 'a1 buf5 -> 'a2 list
Sourceval buf5_push_naive : 'a1 -> 'a1 buf5 -> 'a1 buf5 option
Sourceval buf5_inject_naive : 'a1 buf5 -> 'a1 -> 'a1 buf5 option
Sourceval buf5_pop_naive : 'a1 buf5 -> ('a1 * 'a1 buf5) option
Sourceval buf5_eject_naive : 'a1 buf5 -> ('a1 buf5 * 'a1) option
Sourcemodule E : sig ... end

Packet and Chain โ€” the recursive shape

\'a packet is a *yellow run*: a nest of PNode pre inner suf layers terminated by Hole. In the C runtime, an entire packet is allocated as a single contiguous block โ€” that aggregation is what keeps a chain repair O(1) instead of O(yellow-run-length).

\'a chain is the older "implicit colour" representation, kept for cross-checks. The kChain type below is the production representation with explicit colour tags. Use kChain for new code.

Sourcetype 'a packet =
  1. | Hole
  2. | PNode of 'a E.t buf5 * 'a packet * 'a E.t buf5
Sourcetype 'a chain =
  1. | Ending of 'a E.t buf5
  2. | ChainCons of 'a packet * 'a chain

Internal: chain-level building blocks (Chain, not kChain)

These work on the older implicit-colour chain type. The _full / _rec families are *recursive* (O(log n) per call) โ€” they are proof artefacts used as targets for some Rocq lemmas, NOT for end-user consumption. See why-bounded-cascade.md ยง5 for the proof-artefact-vs-production-code distinction.

For the production WC O(1) ops, use the kt2 or kt4 family above.

Sourceval buf_seq_E : 'a1 E.t buf5 -> 'a1 list
Sourceval packet_seq : 'a1 packet -> 'a1 list -> 'a1 list
Sourceval chain_seq : 'a1 chain -> 'a1 list
Sourceval chain_to_list : 'a1 chain -> 'a1 list
Sourceval empty_chain : 'a1 chain
Sourceval push_chain : 'a1 E.t -> 'a1 chain -> 'a1 chain option
Sourceval inject_chain : 'a1 chain -> 'a1 E.t -> 'a1 chain option
Sourceval pop_chain : 'a1 chain -> ('a1 E.t * 'a1 chain) option
Sourceval eject_chain : 'a1 chain -> ('a1 chain * 'a1 E.t) option
Sourceval make_red_push_chain : 'a1 chain -> 'a1 chain option
Sourceval make_red_inject_chain : 'a1 chain -> 'a1 chain option
Sourceval push_chain_full : 'a1 E.t -> 'a1 chain -> 'a1 chain option

Proof artefact: recursive cascade, O(log n) per call. Not for production use โ€” use push_kt2 or push_kt4 instead.

Sourceval inject_chain_full : 'a1 chain -> 'a1 E.t -> 'a1 chain option

Proof artefact: see push_chain_full.

Sourceval push_chain_rec : 'a1 E.t -> 'a1 chain -> 'a1 chain option

Proof artefact: see push_chain_full.

Sourceval inject_chain_rec : 'a1 chain -> 'a1 E.t -> 'a1 chain option

Proof artefact.

Sourceval pop_chain_rec : 'a1 chain -> ('a1 E.t * 'a1 chain) option

Proof artefact.

Sourceval eject_chain_rec : 'a1 chain -> ('a1 chain * 'a1 E.t) option

Proof artefact.

Sourceval make_green_pop_chain : 'a1 chain -> ('a1 E.t * 'a1 chain) option
Sourceval make_green_eject_chain : 'a1 chain -> ('a1 chain * 'a1 E.t) option
Sourceval pop_chain_full : 'a1 chain -> ('a1 E.t * 'a1 chain) option

Proof artefact.

Sourceval eject_chain_full : 'a1 chain -> ('a1 chain * 'a1 E.t) option

Proof artefact.

Colours and regularity tags

Sourcetype color =
  1. | Green
  2. | Yellow
  3. | Red
    (*

    Buffer / packet colour. See why-bounded-cascade.md ยง2 for the regularity invariant ("no two Reds adjacent") that this type encodes.

    *)
Sourceval buf_color : 'a1 buf5 -> color

buf_color b is the colour of b determined by its size: B0/B5 Red, B1/B4 Yellow, B2/B3 Green.

Sourceval color_meet : color -> color -> color

color_meet c1 c2 is the meet under Red >> Yellow >> Green; used to derive a packet's outer colour from its prefix and suffix buffer colours.

Coq_E is the deque's chosen ELEMENT instance โ€” a transparent alias for ElementTree. All public deque ops accept and return \'a Coq_E.t; user code typically goes through Coq_E.base / Coq_E.to_list only. The implementation under Coq_E is the same as under ElementTree (both are extractions of the same Rocq module), but the verified ops reference Coq_E specifically, so use it for type-equality with the deque's API surface.

Sourcemodule Coq_E : sig ... end

Internal: colour-dispatched buffer-level helpers

Used by the chain-level ops above. green_* are partial on Red-sized inputs; yellow_* tolerate one direction of Yellow.

Sourceval green_push : 'a1 -> 'a1 buf5 -> 'a1 buf5 option
Sourceval green_inject : 'a1 buf5 -> 'a1 -> 'a1 buf5 option
Sourceval yellow_push : 'a1 -> 'a1 buf5 -> 'a1 buf5 option
Sourceval yellow_inject : 'a1 buf5 -> 'a1 -> 'a1 buf5 option
Sourceval green_pop : 'a1 buf5 -> ('a1 * 'a1 buf5) option
Sourceval green_eject : 'a1 buf5 -> ('a1 buf5 * 'a1) option
Sourceval yellow_pop : 'a1 buf5 -> ('a1 * 'a1 buf5) option
Sourceval yellow_eject : 'a1 buf5 -> ('a1 buf5 * 'a1) option

Internal: small-buffer constructors and decomposers

Build B2/B3 buffers from optional plus-pair forms; case-split a buffer into "underflow / ok / overflow" branches. Used inside make_small and the concat helpers.

Sourceval prefix23 : 'a1 option -> ('a1 * 'a1) -> 'a1 buf5
Sourceval suffix23 : ('a1 * 'a1) -> 'a1 option -> 'a1 buf5
Sourceval suffix12 : 'a1 -> 'a1 option -> 'a1 buf5
Sourcetype 'x bdecomp_pre =
  1. | BD_pre_underflow of 'x option
  2. | BD_pre_ok of 'x buf5
  3. | BD_pre_overflow of 'x buf5 * 'x * 'x
Sourcetype 'x bdecomp_suf =
  1. | BD_suf_underflow of 'x option
  2. | BD_suf_ok of 'x buf5
  3. | BD_suf_overflow of 'x buf5 * 'x * 'x
Sourceval prefix_decompose : 'a1 buf5 -> 'a1 bdecomp_pre
Sourceval suffix_decompose : 'a1 buf5 -> 'a1 bdecomp_suf
Sourcetype 'x bsandwich =
  1. | BS_alone of 'x option
  2. | BS_sandwich of 'x * 'x buf5 * 'x
Sourceval buffer_unsandwich : 'a1 buf5 -> 'a1 bsandwich
Sourceval prefix_rot : 'a1 -> 'a1 buf5 -> 'a1 buf5 * 'a1
Sourceval suffix_rot : 'a1 buf5 -> 'a1 -> 'a1 * 'a1 buf5
Sourceval buffer_halve : 'a1 buf5 -> 'a1 option * ('a1 * 'a1) buf5

Internal: cross-buffer concat helpers

{green,}_prefix_concat (b1, b2) redistributes elements between two adjacent buffers so the result has acceptable colours. Used inside green_of_red Cases 2 and 3.

Sourceval green_prefix_concat : 'a1 Coq_E.t buf5 -> 'a1 Coq_E.t buf5 -> ('a1 Coq_E.t buf5 * 'a1 Coq_E.t buf5) option
Sourceval green_suffix_concat : 'a1 Coq_E.t buf5 -> 'a1 Coq_E.t buf5 -> ('a1 Coq_E.t buf5 * 'a1 Coq_E.t buf5) option
Sourceval prefix_concat : 'a1 Coq_E.t buf5 -> 'a1 Coq_E.t buf5 -> ('a1 Coq_E.t buf5 * 'a1 Coq_E.t buf5) option
Sourceval suffix_concat : 'a1 Coq_E.t buf5 -> 'a1 Coq_E.t buf5 -> ('a1 Coq_E.t buf5 * 'a1 Coq_E.t buf5) option

Internal: rebalance primitives

make_small (the depth-1 collapse), green_of_red (the three- case red-to-green fix), ensure_green (the fire-or-noop dispatch). These are the heart of the WC-O(1) bound โ€” see why-bounded-cascade.md ยง2-ยง3. Public callers should use push_kt2 / push_kt4 etc., which compose these primitives correctly.

Sourceval buffer_push_chain : 'a1 Coq_E.t -> 'a1 Coq_E.t buf5 -> 'a1 chain
Sourceval buffer_inject_chain : 'a1 Coq_E.t buf5 -> 'a1 Coq_E.t -> 'a1 chain
Sourceval pair_one : ('a1 Coq_E.t * 'a1 Coq_E.t) -> 'a1 Coq_E.t option
Sourceval pair_each_buf : ('a1 Coq_E.t * 'a1 Coq_E.t) buf5 -> 'a1 Coq_E.t buf5 option
Sourceval mk_ending_from_options : 'a1 Coq_E.t option -> ('a1 Coq_E.t * 'a1 Coq_E.t) option -> 'a1 Coq_E.t option -> 'a1 chain
Sourceval make_small : 'a1 Coq_E.t buf5 -> 'a1 Coq_E.t buf5 -> 'a1 Coq_E.t buf5 -> 'a1 chain option
Sourceval green_of_red : 'a1 chain -> 'a1 chain option
Sourceval pkt_outer_color : 'a1 packet -> color
Sourceval ensure_green : 'a1 chain -> 'a1 chain option
Sourceval make_yellow : 'a1 Coq_E.t buf5 -> 'a1 packet -> 'a1 Coq_E.t buf5 -> 'a1 chain -> 'a1 chain option
Sourceval make_red : 'a1 Coq_E.t buf5 -> 'a1 packet -> 'a1 Coq_E.t buf5 -> 'a1 chain -> 'a1 chain option

Internal: superseded kt family

An early version with implicit colours, kept for proof cross-checks. Use the kt2/kt4 families above instead.

Sourceval push_kt : 'a1 Coq_E.t -> 'a1 chain -> 'a1 chain option
Sourceval inject_kt : 'a1 chain -> 'a1 Coq_E.t -> 'a1 chain option
Sourceval pop_kt : 'a1 chain -> ('a1 Coq_E.t * 'a1 chain) option
Sourceval eject_kt : 'a1 chain -> ('a1 chain * 'a1 Coq_E.t) option

Public API: kt2 family (option-returning, recommended)

The verified worst-case O(1) deque, with option return types that integrate with monadic let*. All four ops:

Sequence semantics (proved in OpsKTSeq.v):

Cost (proved in Footprint.v): each op runs in worst-case O(1) primitive heap operations.

Sourcetype 'a kChain =
  1. | KEnding of 'a Coq_E.t buf5
  2. | KCons of color * 'a packet * 'a kChain
    (*

    A KT-style chain. KEnding b is a single-buffer chain (typically a small deque); KCons c p tl adjoins a packet p tagged with color c to a tail chain. The colour tag is contextual, not derivable from buffer sizes alone โ€” see OpsKT.v header for why.

    *)
Sourceval empty_kchain : 'a1 kChain

The empty deque. kchain_to_list empty_kchain = [].

Sourceval chain_to_kchain_g : 'a1 chain -> 'a1 kChain

Internal: lift a (Green) chain into the colour-tagged kChain. All resulting KCons cells are tagged Green.

Sourceval kchain_to_chain : 'a1 kChain -> 'a1 chain

Internal: drop colour tags.

Sourceval kchain_to_list : 'a1 kChain -> 'a1 list

kchain_to_list q is the abstract sequence of q, from front to back. This is the *specification* of every operation: each op's effect on kchain_to_list is what its _seq lemma in OpsKTSeq.v proves.

Sourceval green_of_red_k : 'a1 kChain -> 'a1 kChain option

Internal: convert a Red-headed chain to a Green-headed chain in O(1). The three structural cases are described in why-bounded-cascade.md ยง2 and OpsKT.v's green_of_red section.

Sourceval ensure_green_k : 'a1 kChain -> 'a1 kChain option

Internal: if the chain top is tagged Red, fire green_of_red_k; otherwise, return as-is. The WC-O(1) repair primitive โ€” every public op ends with at most one ensure_green_k call.

Sourceval make_yellow_k : 'a1 Coq_E.t buf5 -> 'a1 packet -> 'a1 Coq_E.t buf5 -> 'a1 kChain -> 'a1 kChain option

Internal: prepend a Yellow-coloured packet to a chain, repairing the result via ensure_green_k if necessary.

Sourceval make_red_k : 'a1 Coq_E.t buf5 -> 'a1 packet -> 'a1 Coq_E.t buf5 -> 'a1 kChain -> 'a1 kChain option

Internal: prepend a Red-coloured packet, then repair via ensure_green_k (which fires green_of_red_k).

Sourceval push_kt2 : 'a1 Coq_E.t -> 'a1 kChain -> 'a1 kChain option

push_kt2 x q prepends element x to the front of q. Returns Some q' with kchain_to_list q' = Coq_E.to_list x @ kchain_to_list q on success.

Worst-case O(1). See OpsKTSeq.v push_kt2_seq for the proof.

Use Coq_E.base v to wrap a base value v : \'a for this call.

Sourceval inject_kt2 : 'a1 kChain -> 'a1 Coq_E.t -> 'a1 kChain option

inject_kt2 q x appends element x to the back of q. Mirror of push_kt2. Worst-case O(1).

Sourceval pop_kt2 : 'a1 kChain -> ('a1 Coq_E.t * 'a1 kChain) option

pop_kt2 q removes and returns the front element of q. Returns None if q is empty. Worst-case O(1).

The returned element x : \'a Coq_E.t is at level 0 by the sequence-preservation theorem; use Coq_E.to_list x to recover the underlying \'a.

Sourceval eject_kt2 : 'a1 kChain -> ('a1 kChain * 'a1 Coq_E.t) option

eject_kt2 q removes and returns the back element of q. Mirror of pop_kt2. Worst-case O(1).

Optimized variants: kt3 family

The kt3 variants are operationally equivalent to kt2 but inline yellow_wrap / ensure_green_k / make_red_k into the body of each op and special-case the common "tail is not Red" path. Same return types as kt2; same correctness theorems (proved by _kt3_seq equivalences in OpsKTSeq.v). Smaller constant factor in extracted OCaml code.

Sourceval yellow_wrap : 'a1 Coq_E.t buf5 -> 'a1 packet -> 'a1 Coq_E.t buf5 -> 'a1 kChain -> 'a1 kChain option

Internal: equivalent to make_yellow_k with ensure_green_k inlined and the Yellow fast path special-cased.

Sourceval push_kt3 : 'a1 Coq_E.t -> 'a1 kChain -> 'a1 kChain option

push_kt3 = push_kt2 semantically; faster constant factor.

Sourceval inject_kt3 : 'a1 kChain -> 'a1 Coq_E.t -> 'a1 kChain option

inject_kt3 = inject_kt2 semantically.

Sourceval pop_kt3 : 'a1 kChain -> ('a1 Coq_E.t * 'a1 kChain) option

pop_kt3 = pop_kt2 semantically.

Sourceval eject_kt3 : 'a1 kChain -> ('a1 kChain * 'a1 Coq_E.t) option

eject_kt3 = eject_kt2 semantically.

Public API: kt4 family (no-option, allocation-optimal)

The kt4 variants replace the nested option (X * Y) return with a flat 2-constructor sum type: push_result for push_kt4 / inject_kt4, pop_result for pop_kt4 / eject_kt4.

OCaml extracts option (X * Y) as a Some block holding a pointer to an (X, Y) tuple block โ€” two allocations per successful op. A flat sum is one block. Hot loops that bench in the 25โ€“80 ns/op range are allocation-bound; this saves one allocation and is the family used by the bench's headline numbers.

Same correctness as kt2: the _kt4 = _kt2 equivalence is proved at the bottom of OpsKTSeq.v.

Sourcetype 'a push_result =
  1. | PushFail
  2. | PushOk of 'a kChain
    (*

    Result of push_kt4 / inject_kt4. PushFail is the analogue of None (regularity-invariant violation; cannot happen on inputs reachable from empty_kchain); PushOk q' is the analogue of Some q'.

    *)
Sourcetype 'a pop_result =
  1. | PopFail
  2. | PopOk of 'a Coq_E.t * 'a kChain
    (*

    Result of pop_kt4 / eject_kt4. PopFail is "deque is empty"; PopOk (x, q') is the popped element plus the remaining deque.

    *)
Sourceval yellow_wrap_pr : 'a1 Coq_E.t buf5 -> 'a1 packet -> 'a1 Coq_E.t buf5 -> 'a1 kChain -> 'a1 push_result

Internal: yellow_wrap with the push_result return type.

Sourceval push_kt4 : 'a1 Coq_E.t -> 'a1 kChain -> 'a1 push_result

push_kt4 x q: same semantics as push_kt2, allocation-optimal return. Worst-case O(1).

Sourceval inject_kt4 : 'a1 kChain -> 'a1 Coq_E.t -> 'a1 push_result

inject_kt4: same semantics as inject_kt2, allocation-optimal.

Sourceval pop_kt4 : 'a1 kChain -> 'a1 pop_result

pop_kt4: same semantics as pop_kt2, allocation-optimal.

Sourceval eject_kt4 : 'a1 kChain -> 'a1 pop_result

eject_kt4: same semantics as eject_kt2, allocation-optimal.