Add properties that characterize Data.Tree.AVL.Indexed.delete.#2961
Add properties that characterize Data.Tree.AVL.Indexed.delete.#2961mikedelorimier wants to merge 3 commits intoagda:masterfrom
Data.Tree.AVL.Indexed.delete.#2961Conversation
These are properties for the core delete on AVL trees and similar to the prexisting properties of `insert`. Lemmas added for `delete` are `delete⁺`, `delete-tree⁻`, `delete-key-∈⁻` and `delete-key⁻`. Together these can be used to prove `(k′ ∈ delete k t) ⇔ (k′ ≉ k × k′ ∈ t)`, which fully characterizes `delete`. `delete⁺`, `delete-tree⁻`, and `delete-key⁻` correspond to Coq.FSets.FSetInterface's `remove_2`, `remove_3`, and `remove_1`, respectively. Just like other lemmas in this file, `delete⁺`, `delete-tree⁻`, `delete-key⁻` generalize `k′ ∈ t` to `Any P t`. `delete-key-∈⁻` is essentally a helper for `delete-key⁻`, which would be difficult to prove directly. Many more lemmas were added for AVL functions that `delete` depends on. These additional lemmas characterize the functions `castʳ`, `joinˡ⁺`, `joinʳ⁺`, `joinˡ⁻`, `joinʳ⁻`, `headTail`, and `join`. Adding all this code to Properties.agda caused `make test` to overflow past the 4096 MB default heap size. This was solved by breaking Properties.agda into multiple files, with roughly one file for each AVL function. Added proofs are a similar style the preexisting proofs in Properties.agda.
JacquesCarette
left a comment
There was a problem hiding this comment.
Fantastic work. But a few things may need tweaked.
| (p : Any P t) → lookupKey p ≉ k → | ||
| Any P (proj₂ (delete k t seg)) | ||
| delete⁺ (leaf _) _ p _ = p | ||
| delete⁺ (node (k′ , v) lk′ k′u bal) (l<k , k<u) p p≉k |
There was a problem hiding this comment.
since you uniformly do with on both compare and p, I recommend that you do both of them at once.
There was a problem hiding this comment.
And, again arguably, p could be handled by 'ordinary' pattern matching clauses, with the with compare... analysis subsidiary to that? Anything to help manage the readability problem!
| Any P t₁ → Any P (proj₂ (join t₁ t₂ bal)) | ||
| join-left⁺ _ (leaf _) ∼- p = castʳ⁺ p | ||
| join-left⁺ {hʳ = suc _} t₁ t₂₃@(node _ _ _ _) bal p | ||
| with headTail t₂₃ |
There was a problem hiding this comment.
irrefutable with ?
There was a problem hiding this comment.
or even let bind the result? alternatively, use using syntax
| (r : Tree V [ kv .key ] u hʳ) → | ||
| (bal : hˡ ∼ hʳ ⊔ h) → | ||
| P kv → Any P (proj₂ (joinˡ⁺ kv l r bal)) | ||
| joinˡ⁺-here⁺ k₂ (0# , t₁) t₃ bal p = here p |
There was a problem hiding this comment.
For further clarify, don't bother naming all the things you don't use in the rhs.
| joinˡ⁺-here⁺ k₄ (1# , node k₂ t₁ t₃ ∼0) t₅ ∼- p = right (here p) | ||
| joinˡ⁺-here⁺ k₆ (1# , node⁺ k₂ t₁ k₄ t₃ t₅ bal) t₇ ∼- p = right (here p) | ||
|
|
||
| joinˡ⁺-left⁺ : ∀ {l u hˡ hʳ h} → |
There was a problem hiding this comment.
Same comment throughout this file: don't name unused arguments.
| joinˡ⁺⁻ k₂ (0# , t₁) t₃ ba = Any.toSum | ||
| joinˡ⁺⁻ k₂ (1# , t₁) t₃ ∼0 = Any.toSum | ||
| joinˡ⁺⁻ k₂ (1# , t₁) t₃ ∼+ = Any.toSum | ||
| joinˡ⁺⁻ k₄ (1# , node k₂ t₁ t₃ ∼-) t₅ ∼- = λ where |
There was a problem hiding this comment.
This sub-function is used twice -- name it?
| joinʳ⁺-here⁺ k₂ t₁ (0# , t₃) bal p = here p | ||
| joinʳ⁺-here⁺ k₂ t₁ (1# , t₃) ∼0 p = here p | ||
| joinʳ⁺-here⁺ k₂ t₁ (1# , t₃) ∼- p = here p | ||
| joinʳ⁺-here⁺ k₂ t₁ (1# , node k₄ t₃ t₅ ∼+) ∼+ p = left (here p) |
There was a problem hiding this comment.
Do these 2 cases need to be separate?
| joinʳ⁺-left⁺ k₂ t₁ (0# , t₃) bal p = left p | ||
| joinʳ⁺-left⁺ k₂ t₁ (1# , t₃) ∼0 p = left p | ||
| joinʳ⁺-left⁺ k₂ t₁ (1# , t₃) ∼- p = left p | ||
| joinʳ⁺-left⁺ k₂ t₁ (1# , node k₄ t₃ t₅ ∼+) ∼+ p = left (left p) |
There was a problem hiding this comment.
same question here: merge those 2 cases?
| private | ||
| Val = Value.family V | ||
|
|
||
| singleton⁺ : {P : Pred (K& V) p} → |
There was a problem hiding this comment.
in the next two functions, should some of the arguments be implicit?
There was a problem hiding this comment.
Not only that, but why is V a parameter to the anonymous module, whereas all the other arguments are local to the functions being defined, when those functions share the same telescope of hypotheses?
The private declaration of Val can be inlined as a telescope-level let binding, I think, or even, if shared, simply really inlined?
There was a problem hiding this comment.
This looks impressive, but reading this PR was heavy going, to say the least. FWIW, I might have preferred this PR in two stages:
- code motion of the existing proofs under
Any.Properties, which could be easily merged without having to think too hard - a separate follow-up PR which isolates what is new, both for the
Join*properties, and then also those forDeleteand the ancillary stuff
As for your own proofs, the original code in Indexed seems to have been written to rely a bit too heavily on with, as a consequence of which your own code has to do elaborate gymnastics in order to cope downstream.
Suggested simplifications strip out some, but not yet all, of such things, and hopefully provide a model for subsequent refactoring/simplification...
A subsequent refactoring might wish to reconsider the basics under Indexed, with 'with sometimes considered harmful' borne in mind... cf #2123 / #2369
| headTail-head⁻ (node {hˡ = suc _} _ t₁₂ _ _) p | ||
| with headTail t₁₂ | ||
| headTail-head⁻ (node {hˡ = suc _} _ t₁₂@(node _ _ _ _) _ _) p | ||
| | k₁ , l<k₁ , t₂ = left (headTail-head⁻ t₁₂ p) |
There was a problem hiding this comment.
This can be drastically simplified as follows
| headTail-head⁻ (node {hˡ = suc _} _ t₁₂ _ _) p | |
| with headTail t₁₂ | |
| headTail-head⁻ (node {hˡ = suc _} _ t₁₂@(node _ _ _ _) _ _) p | |
| | k₁ , l<k₁ , t₂ = left (headTail-head⁻ t₁₂ p) | |
| headTail-head⁻ {P = P} (node _ t₁₂@(node _ _ _ _) _ _) p = left (headTail-head⁻ {P = P} t₁₂ p) |
where, unfortunately, the threading of {P = P} from LHS to RHS seems necessary, unless the same let-binding trick is used to obtain as a wholesale rewrite
headTail-head⁻ : ∀ {l u h} → (t : Tree V l u (suc h)) →
let k , _ = headTail t in
P k → Any P t
headTail-head⁻ (node _ (leaf _) _ ∼+) p = here p
headTail-head⁻ (node _ (leaf _) _ ∼0) p = here p
headTail-head⁻ (node _ t₁₂@(node _ _ _ _) _ _) p = left (headTail-head⁻ t₁₂ p)There was a problem hiding this comment.
Eg as a specimen refactoring, the first two clauses could be simplified if the original defn of headTail were modified as follows:
In Height, add the lemma
0∼ : ∀ {i m} → 0 ∼ i ⊔ m → i ≡ m
0∼ ∼+ = refl
0∼ ∼0 = refland then in Indexed, change the definition of headTail instead to read
headTail : ∀ {l u h} → Tree V l u (1 + h) →
∃ λ (k : K& V) → l <⁺ [ k .key ] ×
∃ λ i → Tree V [ k .key ] u (i ⊕ h)
headTail (node k₁ (leaf l<k₁) t₂ bal) with refl ← 0∼ bal = k₁ , l<k₁ , 0# , t₂
headTail (node k₃ t₁₂@(node _ _ _ _) t₄ bal)
using (k₁ , l<k₁ , t₂) ← headTail t₁₂ = k₁ , l<k₁ , joinˡ⁻ _ k₃ t₂ t₄ balUPDATED: now part of #2968
| headTail⁺ : ∀ {l u h} (t : Tree V l u (1 + h)) → | ||
| Any P t → | ||
| P (proj₁ (headTail t)) | ||
| ⊎ Any P (proj₂ (proj₂ (proj₂ (headTail t)))) | ||
| headTail⁺ (node _ (leaf _) _ ∼+) (here p) = inj₁ p | ||
| headTail⁺ (node _ (leaf _) _ ∼+) (right p) = inj₂ p | ||
| headTail⁺ (node _ (leaf _) _ ∼0) (here p) = inj₁ p | ||
| headTail⁺ (node {hˡ = suc _} k₃ t₁₂@(node _ _ _ _) t₄ bal) (here p) | ||
| with headTail t₁₂ | ||
| ... | k₁ , l<k₁ , t₂ = inj₂ (joinˡ⁻-here⁺ k₃ t₂ t₄ bal p) | ||
| headTail⁺ (node {hˡ = suc _} k₃ t₁₂@(node _ _ _ _) t₄ bal) (left p) | ||
| with headTail t₁₂ | headTail⁺ t₁₂ p | ||
| ... | k₁ , l<k₁ , t₂ | inj₁ ph = inj₁ ph | ||
| ... | k₁ , l<k₁ , t₂ | inj₂ pt = inj₂ (joinˡ⁻-left⁺ k₃ t₂ t₄ bal pt) | ||
| headTail⁺ (node {hˡ = suc _} k₃ t₁₂@(node _ _ _ _) t₄ bal) (right p) | ||
| with headTail t₁₂ | ||
| ... | k₁ , l<k₁ , t₂ = inj₂ (joinˡ⁻-right⁺ k₃ t₂ t₄ bal p) |
There was a problem hiding this comment.
Use of with considered (sometimes) harmful... Instead, can simplify as follows
| headTail⁺ : ∀ {l u h} (t : Tree V l u (1 + h)) → | |
| Any P t → | |
| P (proj₁ (headTail t)) | |
| ⊎ Any P (proj₂ (proj₂ (proj₂ (headTail t)))) | |
| headTail⁺ (node _ (leaf _) _ ∼+) (here p) = inj₁ p | |
| headTail⁺ (node _ (leaf _) _ ∼+) (right p) = inj₂ p | |
| headTail⁺ (node _ (leaf _) _ ∼0) (here p) = inj₁ p | |
| headTail⁺ (node {hˡ = suc _} k₃ t₁₂@(node _ _ _ _) t₄ bal) (here p) | |
| with headTail t₁₂ | |
| ... | k₁ , l<k₁ , t₂ = inj₂ (joinˡ⁻-here⁺ k₃ t₂ t₄ bal p) | |
| headTail⁺ (node {hˡ = suc _} k₃ t₁₂@(node _ _ _ _) t₄ bal) (left p) | |
| with headTail t₁₂ | headTail⁺ t₁₂ p | |
| ... | k₁ , l<k₁ , t₂ | inj₁ ph = inj₁ ph | |
| ... | k₁ , l<k₁ , t₂ | inj₂ pt = inj₂ (joinˡ⁻-left⁺ k₃ t₂ t₄ bal pt) | |
| headTail⁺ (node {hˡ = suc _} k₃ t₁₂@(node _ _ _ _) t₄ bal) (right p) | |
| with headTail t₁₂ | |
| ... | k₁ , l<k₁ , t₂ = inj₂ (joinˡ⁻-right⁺ k₃ t₂ t₄ bal p) | |
| headTail⁺ : ∀ {l u h} (t : Tree V l u (suc h)) → | |
| Any P t → let k , l<k , i , t⁻ = headTail t in | |
| P k ⊎ Any P t⁻ | |
| headTail⁺ (node _ (leaf _) _ ∼+) (here p) = inj₁ p | |
| headTail⁺ (node _ (leaf _) _ ∼+) (right p) = inj₂ p | |
| headTail⁺ (node _ (leaf _) _ ∼0) (here p) = inj₁ p | |
| headTail⁺ (node k₃ t₁₂@(node _ _ _ _) t₄ bal) (here p) | |
| = let _ , _ , t⁻ = headTail t₁₂ in inj₂ (joinˡ⁻-here⁺ k₃ t⁻ t₄ bal p) | |
| headTail⁺ (node k₃ t₁₂@(node _ _ _ _) t₄ bal) (left p) | |
| = let _ , _ , t⁻ = headTail t₁₂ in Sum.map id (joinˡ⁻-left⁺ k₃ t⁻ t₄ bal) (headTail⁺ t₁₂ p) |
UPDATED to remove the implicit pattern binding of {hˡ = suc _}
| headTail-tail⁻ (node {hˡ = suc _} k₃ t₁₂@(node _ _ _ _) t₄ bal) p | ||
| with k₁ , l<k₁ , t₂ ← headTail t₁₂ in eq | ||
| -- This match on `bal` is so the termination checker sees `h` | ||
| -- decrease. | ||
| | joinˡ⁻⁻ k₃ t₂ t₄ bal p | bal | eq | ||
| ... | inj₁ pk | _ | ≡-refl = here pk | ||
| ... | inj₂ (inj₁ pl) | ∼+ | ≡-refl = left (headTail-tail⁻ t₁₂ pl) | ||
| ... | inj₂ (inj₁ pl) | ∼0 | ≡-refl = left (headTail-tail⁻ t₁₂ pl) | ||
| ... | inj₂ (inj₁ pl) | ∼- | ≡-refl = left (headTail-tail⁻ t₁₂ pl) | ||
| ... | inj₂ (inj₂ pr) | _ | ≡-refl = right pr |
There was a problem hiding this comment.
Granted that the case analysis on bal appears to be necessary for the sake of the termination checker, this can however be made a bit less indigestible as follows:
| headTail-tail⁻ (node {hˡ = suc _} k₃ t₁₂@(node _ _ _ _) t₄ bal) p | |
| with k₁ , l<k₁ , t₂ ← headTail t₁₂ in eq | |
| -- This match on `bal` is so the termination checker sees `h` | |
| -- decrease. | |
| | joinˡ⁻⁻ k₃ t₂ t₄ bal p | bal | eq | |
| ... | inj₁ pk | _ | ≡-refl = here pk | |
| ... | inj₂ (inj₁ pl) | ∼+ | ≡-refl = left (headTail-tail⁻ t₁₂ pl) | |
| ... | inj₂ (inj₁ pl) | ∼0 | ≡-refl = left (headTail-tail⁻ t₁₂ pl) | |
| ... | inj₂ (inj₁ pl) | ∼- | ≡-refl = left (headTail-tail⁻ t₁₂ pl) | |
| ... | inj₂ (inj₂ pr) | _ | ≡-refl = right pr | |
| headTail-tail⁻ (node k₃ t₁₂@(node _ _ _ _) t₄ bal) p | |
| using k₁ , l<k₁ , t₂ ← headTail t₁₂ | |
| with joinˡ⁻⁻ k₃ t₂ t₄ bal p | |
| ... | inj₁ pk = here pk | |
| ... | inj₂ (inj₂ pr) = right pr | |
| ... | inj₂ (inj₁ pl) using p⁻ ← headTail-tail⁻ t₁₂ pl with bal | |
| ... | ∼+ = left p⁻ | |
| ... | ∼0 = left p⁻ | |
| ... | ∼- = left p⁻ |
| headTail-tail⁻ : ∀ {l u h} (t : Tree V l u (1 + h)) → | ||
| Any P (proj₂ (proj₂ (proj₂ (headTail t)))) → | ||
| Any P t |
There was a problem hiding this comment.
For the 'improvement' below, it seems cleaner to follow the let-binding rephrasing, and write instead:
| headTail-tail⁻ : ∀ {l u h} (t : Tree V l u (1 + h)) → | |
| Any P (proj₂ (proj₂ (proj₂ (headTail t)))) → | |
| Any P t | |
| headTail-tail⁻ : ∀ {l u h} (t : Tree V l u (suc h)) → | |
| let _ , _ , _ , t⁻ = headTail t in | |
| Any P t⁻ → Any P t |
| headTail⁺ (node _ (leaf _) _ ∼+) (here p) = inj₁ p | ||
| headTail⁺ (node _ (leaf _) _ ∼+) (right p) = inj₂ p | ||
| headTail⁺ (node _ (leaf _) _ ∼0) (here p) = inj₁ p | ||
| headTail⁺ (node {hˡ = suc _} k₃ t₁₂@(node _ _ _ _) t₄ bal) (here p) |
There was a problem hiding this comment.
The repetition between the first and third clauses here can be handled by analysis of the bal constraint , which in fact forces the heights to be equal, and thus these two can be collapsed into one, here and similarly elsewhere throughout the development. In that sense the pattern match on here p is conceptually prior to that of the tree and its balance factor.
UPDATED: see #2968 for this analysis in the definition of headTail...
| headTail-head⁻ : ∀ {l u h} → (t : Tree V l u (suc h)) → | ||
| P (proj₁ (headTail t)) → Any P t | ||
| headTail-head⁻ (node _ (leaf _) _ ∼+) p = here p | ||
| headTail-head⁻ (node _ (leaf _) _ ∼0) p = here p |
There was a problem hiding this comment.
Ditto. here as to how to avoid the duplication.
| ... | tri< k<k′ _ _ = let seg′ = l<k , [ k<k′ ]ᴿ; lk′ = insertWith k f lk seg′ | ||
| ih = Any-insertWith-nothing lk seg′ pr (λ p → ¬p (left p)) | ||
| in joinˡ⁺-left⁺ kv lk′ ku bal ih | ||
| ... | tri> _ _ k>k′ = let seg′ = [ k>k′ ]ᴿ , k<u; ku′ = insertWith k f ku seg′ | ||
| ih = Any-insertWith-nothing ku seg′ pr (λ p → ¬p (right p)) | ||
| in joinʳ⁺-right⁺ kv lk ku′ bal ih |
There was a problem hiding this comment.
Is it clearer to handle these subcomputations via a where block, rather than using a let?
| ... | left lp | tri< k<k′ _ _ = let seg′ = l<k , [ k<k′ ]ᴿ; lk′ = insertWith k f lk seg′ in | ||
| joinˡ⁺-left⁺ kv lk′ ku bal (Any-insertWith-just lk seg′ pr lp) | ||
| ... | right rp | tri> _ _ k>k′ = let seg′ = [ k>k′ ]ᴿ , k<u; ku′ = insertWith k f ku seg′ in | ||
| joinʳ⁺-right⁺ kv lk ku′ bal (Any-insertWith-just ku seg′ pr rp) |
| (t₁ : Tree V l m hˡ) (t₂ : Tree V m u hʳ) → | ||
| (bal : hˡ ∼ hʳ ⊔ h) → | ||
| Any P t₂ → Any P (proj₂ (join t₁ t₂ bal)) | ||
| join-right⁺ _ (leaf _) ∼- () |
There was a problem hiding this comment.
I think that Agda is smart enough to detect this clause as impossible anyway, so could be deleted.
| (bal : hˡ ∼ hʳ ⊔ h) → | ||
| Any P t₂ → Any P (proj₂ (join t₁ t₂ bal)) | ||
| join-right⁺ _ (leaf _) ∼- () | ||
| join-right⁺ {hʳ = suc _} t₁ t₂₃@(node _ _ _ _) bal p |
There was a problem hiding this comment.
No need to match {hʳ = suc _} here?
| joinˡ⁻-left⁺ {hˡ = zero} k₂ (0# , t₁) t₃ bal p = left p | ||
| joinˡ⁻-left⁺ {hˡ = zero} k₂ (1# , t₁) t₃ bal p = left p |
There was a problem hiding this comment.
Similarly, t₁ must be a leaf _ instance, and the balance factor 0#/1# can be ignored.
| joinˡ⁻-right⁺ {hˡ = zero} k₂ (0# , t₁) t₃ bal p = right p | ||
| joinˡ⁻-right⁺ {hˡ = zero} k₂ (1# , t₁) t₃ bal p = right p |
| joinˡ⁻⁻ {hˡ = zero} k₂ (0# , t₁) t₃ bal (here p) = inj₁ p | ||
| joinˡ⁻⁻ {hˡ = zero} k₂ (0# , t₁) t₃ bal (right p) = inj₂ (inj₂ p) | ||
| joinˡ⁻⁻ {hˡ = zero} k₂ (1# , t₁) t₃ bal (here p) = inj₁ p | ||
| joinˡ⁻⁻ {hˡ = zero} k₂ (1# , t₁) t₃ bal (right p) = inj₂ (inj₂ p) |
There was a problem hiding this comment.
Similarly, I'd reorder to consider here p clauses before right p clauses, and then collapse the two clauses by ignoring the balance factor and noting that t₁ is in fact a leaf _...
| joinʳ⁻-here⁺ {hʳ = zero} k₂ t₁ (0# , t₃) bal p = here p | ||
| joinʳ⁻-here⁺ {hʳ = zero} k₂ t₁ (1# , t₃) bal p = here p |
| joinʳ⁻-right⁺ {hʳ = zero} k₂ t₁ (0# , t₃) bal p = right p | ||
| joinʳ⁻-right⁺ {hʳ = zero} k₂ t₁ (1# , t₃) bal p = right p |
There was a problem hiding this comment.
Ditto., but now for t₃...
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (0# , t₃) bal (here p) = inj₁ p | ||
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (0# , t₃) bal (left p) = inj₂ (inj₁ p) | ||
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (0# , t₃) bal (right p) = inj₂ (inj₂ p) | ||
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (1# , t₃) bal (here p) = inj₁ p | ||
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (1# , t₃) bal (left p) = inj₂ (inj₁ p) | ||
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (1# , t₃) bal (right p) = inj₂ (inj₂ p) |
There was a problem hiding this comment.
Similarly: reorder clauses and collapse.
| P kv → | ||
| Any P (proj₂ (joinˡ⁻ hˡ kv l r bal)) | ||
| joinˡ⁻-here⁺ {hˡ = zero} k₂ (0# , t₁) t₃ bal p = here p | ||
| joinˡ⁻-here⁺ {hˡ = zero} k₂ (1# , t₁) t₃ bal p = here p |
There was a problem hiding this comment.
These cases can be simplified by ignoring the balance factor, and even noting that t₁ must in fact be an instance of leaf _, so a pattern synonym might be useful additional machinery to handle this case.
UPDATED: see #2968 for examples of this.
| joinˡ⁻⁻ {hˡ = suc _} k₂ (0# , t₁) t₃ ∼0 (here p) = inj₁ p | ||
| joinˡ⁻⁻ {hˡ = suc _} k₂ (0# , t₁) t₃ ∼0 (left p) = inj₂ (inj₁ p) | ||
| joinˡ⁻⁻ {hˡ = suc _} k₂ (0# , t₁) t₃ ∼0 (right p) = inj₂ (inj₂ p) | ||
| joinˡ⁻⁻ {hˡ = suc _} k₂ (0# , t₁) t₃ ∼- (here p) = inj₁ p |
There was a problem hiding this comment.
Blocks such as this might be better be handled in one go via Any.toSum
| joinˡ⁻⁻ {hˡ = suc _} k₂ (0# , t₁) t₃ ∼- (here p) = inj₁ p | ||
| joinˡ⁻⁻ {hˡ = suc _} k₂ (0# , t₁) t₃ ∼- (left p) = inj₂ (inj₁ p) | ||
| joinˡ⁻⁻ {hˡ = suc _} k₂ (0# , t₁) t₃ ∼- (right p) = inj₂ (inj₂ p) |
There was a problem hiding this comment.
Ditto. Any.toSum (and then you can eta-contract as well ;-))
| joinˡ⁻⁻ {hˡ = suc _} k₂ (1# , t₁) t₃ bal (here p) = inj₁ p | ||
| joinˡ⁻⁻ {hˡ = suc _} k₂ (1# , t₁) t₃ bal (left p) = inj₂ (inj₁ p) | ||
| joinˡ⁻⁻ {hˡ = suc _} k₂ (1# , t₁) t₃ bal (right p) = inj₂ (inj₂ p) |
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (0# , t₃) bal (here p) = inj₁ p | ||
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (0# , t₃) bal (left p) = inj₂ (inj₁ p) | ||
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (0# , t₃) bal (right p) = inj₂ (inj₂ p) | ||
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (1# , t₃) bal (here p) = inj₁ p | ||
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (1# , t₃) bal (left p) = inj₂ (inj₁ p) | ||
| joinʳ⁻⁻ {hʳ = zero} k₂ t₁ (1# , t₃) bal (right p) = inj₂ (inj₂ p) |
| joinʳ⁻⁻ {hʳ = suc _} k₂ t₁ (0# , t₃) ∼0 (here p) = inj₁ p | ||
| joinʳ⁻⁻ {hʳ = suc _} k₂ t₁ (0# , t₃) ∼0 (left p) = inj₂ (inj₁ p) | ||
| joinʳ⁻⁻ {hʳ = suc _} k₂ t₁ (0# , t₃) ∼0 (right p) = inj₂ (inj₂ p) | ||
| joinʳ⁻⁻ {hʳ = suc _} k₂ t₁ (0# , t₃) ∼+ (here p) = inj₁ p | ||
| joinʳ⁻⁻ {hʳ = suc _} k₂ t₁ (0# , t₃) ∼+ (left p) = inj₂ (inj₁ p) | ||
| joinʳ⁻⁻ {hʳ = suc _} k₂ t₁ (0# , t₃) ∼+ (right p) = inj₂ (inj₂ p) |
| joinʳ⁻⁻ {hʳ = suc _} k₂ t₁ (1# , t₃) bal (here p) = inj₁ p | ||
| joinʳ⁻⁻ {hʳ = suc _} k₂ t₁ (1# , t₃) bal (left p) = inj₂ (inj₁ p) | ||
| joinʳ⁻⁻ {hʳ = suc _} k₂ t₁ (1# , t₃) bal (right p) = inj₂ (inj₂ p) |
| join-node⁻ _ lk′ k′u bal p with join⁻ lk′ k′u bal p | ||
| ... | inj₁ p₁ = left p₁ | ||
| ... | inj₂ p₂ = right p₂ |
There was a problem hiding this comment.
| join-node⁻ _ lk′ k′u bal p with join⁻ lk′ k′u bal p | |
| ... | inj₁ p₁ = left p₁ | |
| ... | inj₂ p₂ = right p₂ | |
| join-node⁻ _ lk′ k′u bal p | |
| = Sum.[ (λ p → left p) , (λ p → right p) ] (join⁻ lk′ k′u bal p) |
There was a problem hiding this comment.
... And this could even be inlined in the single place where join-node⁻ is invoked?
| join-right⁺ {hʳ = suc _} t₁ t₂₃@(node _ _ _ _) bal p | ||
| with headTail t₂₃ | headTail⁺ t₂₃ p | ||
| ... | k₂ , m<k₂ , t₃ | inj₁ ph = | ||
| joinʳ⁻-here⁺ k₂ (castʳ t₁ m<k₂) t₃ bal ph | ||
| ... | k₂ , m<k₂ , t₃ | inj₂ pt = | ||
| joinʳ⁻-right⁺ k₂ (castʳ t₁ m<k₂) t₃ bal pt |
There was a problem hiding this comment.
Indeed, no need to do the case analysis:
| join-right⁺ {hʳ = suc _} t₁ t₂₃@(node _ _ _ _) bal p | |
| with headTail t₂₃ | headTail⁺ t₂₃ p | |
| ... | k₂ , m<k₂ , t₃ | inj₁ ph = | |
| joinʳ⁻-here⁺ k₂ (castʳ t₁ m<k₂) t₃ bal ph | |
| ... | k₂ , m<k₂ , t₃ | inj₂ pt = | |
| joinʳ⁻-right⁺ k₂ (castʳ t₁ m<k₂) t₃ bal pt | |
| join-right⁺ t₁ t₂₃@(node _ _ _ _) bal p | |
| using k₂ , m<k₂ , t₃ ← headTail t₂₃ | |
| = Sum.[ joinʳ⁻-here⁺ k₂ (castʳ t₁ m<k₂) t₃ bal | |
| , joinʳ⁻-right⁺ k₂ (castʳ t₁ m<k₂) t₃ bal ]′ | |
| (headTail⁺ t₂₃ p) |
| join⁻ {hʳ = suc _} t₁ t₂₃@(node _ _ _ _) bal p | ||
| with (k₂ , m<k₂ , t₃) ← headTail t₂₃ in eq | ||
| | joinʳ⁻⁻ k₂ (castʳ t₁ m<k₂) t₃ bal p | eq | ||
| ... | inj₁ pk | ≡-refl = inj₂ (headTail-head⁻ t₂₃ pk) | ||
| ... | inj₂ (inj₁ pl) | ≡-refl = inj₁ (castʳ⁻ pl) | ||
| ... | inj₂ (inj₂ pr) | ≡-refl = inj₂ (headTail-tail⁻ t₂₃ pr) |
There was a problem hiding this comment.
| join⁻ {hʳ = suc _} t₁ t₂₃@(node _ _ _ _) bal p | |
| with (k₂ , m<k₂ , t₃) ← headTail t₂₃ in eq | |
| | joinʳ⁻⁻ k₂ (castʳ t₁ m<k₂) t₃ bal p | eq | |
| ... | inj₁ pk | ≡-refl = inj₂ (headTail-head⁻ t₂₃ pk) | |
| ... | inj₂ (inj₁ pl) | ≡-refl = inj₁ (castʳ⁻ pl) | |
| ... | inj₂ (inj₂ pr) | ≡-refl = inj₂ (headTail-tail⁻ t₂₃ pr) | |
| join⁻ t₁ t₂₃@(node _ _ _ _) bal p | |
| using k₂ , m<k₂ , t₃ ← headTail t₂₃ | |
| with joinʳ⁻⁻ k₂ (castʳ t₁ m<k₂) t₃ bal p | |
| ... | inj₁ pk = inj₂ (headTail-head⁻ t₂₃ pk) | |
| ... | inj₂ (inj₁ pl) = inj₁ (castʳ⁻ pl) | |
| ... | inj₂ (inj₂ pr) = inj₂ (headTail-tail⁻ t₂₃ pr) |
| delete⁺ : (t : Tree V l u n) (seg : l < k < u) → | ||
| (p : Any P t) → lookupKey p ≉ k → | ||
| Any P (proj₂ (delete k t seg)) | ||
| delete⁺ (leaf _) _ p _ = p |
There was a problem hiding this comment.
This is an impossible clause (p = ()), so could be deleted.
| delete-tree⁻ (node (k′ , v) lk′ k′u bal) (l<k , k<u) p | ||
| with compare k′ k | ||
| delete-tree⁻ (node kv@(k′ , v) lk′ k′u bal) (l<k , k<u) p | ||
| | tri< k′<k _ _ | ||
| with joinʳ⁻⁻ kv lk′ (delete k k′u ([ k′<k ]ᴿ , k<u)) bal p | ||
| ... | inj₁ pk = here pk | ||
| ... | inj₂ (inj₁ pl) = left pl | ||
| ... | inj₂ (inj₂ pr) = right (delete-tree⁻ k′u ([ k′<k ]ᴿ , k<u) pr) | ||
| delete-tree⁻ (node kv@(k′ , v) lk′ k′u bal) (l<k , k<u) p | ||
| | tri> _ _ k′>k | ||
| with joinˡ⁻⁻ kv (delete k lk′ (l<k , [ k′>k ]ᴿ)) k′u bal p | ||
| ... | inj₁ pk = here pk | ||
| ... | inj₂ (inj₁ pl) = left (delete-tree⁻ lk′ (l<k , [ k′>k ]ᴿ) pl) | ||
| ... | inj₂ (inj₂ pr) = right pr | ||
| delete-tree⁻ (node (k′ , v) lk′ k′u bal) (l<k , k<u) p | ||
| | tri≈ _ _ _ = | ||
| join-node⁻ v lk′ k′u bal p |
There was a problem hiding this comment.
A large number of sub-terms are irrelevant to this analysis, as they merely recapitulate the recursion analysis already present in the definition of delete (perhaps one solution is to reconsider the implicit/explicit bindings in the various join lemmas), so this may be considerably streamlined as follows:
| delete-tree⁻ (node (k′ , v) lk′ k′u bal) (l<k , k<u) p | |
| with compare k′ k | |
| delete-tree⁻ (node kv@(k′ , v) lk′ k′u bal) (l<k , k<u) p | |
| | tri< k′<k _ _ | |
| with joinʳ⁻⁻ kv lk′ (delete k k′u ([ k′<k ]ᴿ , k<u)) bal p | |
| ... | inj₁ pk = here pk | |
| ... | inj₂ (inj₁ pl) = left pl | |
| ... | inj₂ (inj₂ pr) = right (delete-tree⁻ k′u ([ k′<k ]ᴿ , k<u) pr) | |
| delete-tree⁻ (node kv@(k′ , v) lk′ k′u bal) (l<k , k<u) p | |
| | tri> _ _ k′>k | |
| with joinˡ⁻⁻ kv (delete k lk′ (l<k , [ k′>k ]ᴿ)) k′u bal p | |
| ... | inj₁ pk = here pk | |
| ... | inj₂ (inj₁ pl) = left (delete-tree⁻ lk′ (l<k , [ k′>k ]ᴿ) pl) | |
| ... | inj₂ (inj₂ pr) = right pr | |
| delete-tree⁻ (node (k′ , v) lk′ k′u bal) (l<k , k<u) p | |
| | tri≈ _ _ _ = | |
| join-node⁻ v lk′ k′u bal p | |
| delete-tree⁻ (node kv@(k′ , _) l u bal) (l<k , k<u) p with compare k′ k | |
| delete-tree⁻ (node kv l u bal) (_ , k<u) p | |
| | tri< k′<k _ _ | |
| using k′<k<u ← [ k′<k ]ᴿ , k<u | |
| with joinʳ⁻⁻ kv l (delete k u k′<k<u) bal p | |
| ... | inj₁ pk = here pk | |
| ... | inj₂ (inj₁ pl) = left pl | |
| ... | inj₂ (inj₂ pr) = right (delete-tree⁻ u k′<k<u pr) | |
| delete-tree⁻ (node kv l u bal) (l<k , _) p | |
| | tri> _ _ k′>k | |
| using l<k<k′ ← l<k , [ k′>k ]ᴿ | |
| with joinˡ⁻⁻ kv (delete k l l<k<k′) u bal p | |
| ... | inj₁ pk = here pk | |
| ... | inj₂ (inj₁ pl) = left (delete-tree⁻ l l<k<k′ pl) | |
| ... | inj₂ (inj₂ pr) = right pr | |
| delete-tree⁻ (node _ l u bal) _ p | |
| | tri≈ _ _ _ = join-node⁻ _ l u bal p |
which shows off the dependencies of the recursive calls on the corresponding appeals to join lemmas...
|
Thanks for the detailed review! I'll just start chugging along on your comments... |
|
Consider also all the various equivalent ways in which the hypothesis ' |
NB. Some of my suggested refactorings may depend on the modifications to the definitions of eg UPDATED: see #2968 |
|
Looping back to your original preamble, I haven't seen exactly the same memory issues as you when checking even the new |
Yes, it was annoying to split up Properties.agda because this should be a different PR than the delete addition. Unfortunately I didn't keep my old code with all my additions in Possibly the difference is due to a different It's counterintuitive to me that checking a large file takes much memory when there are no cyclic dependencies in the file and no holes. Comparing line number counts in the stdlib, However, checking the unsplit |
|
In the updated |
These are properties for the core delete on AVL trees and are similar to the preexisting properties of
insert.Lemmas added for
deletearedelete⁺,delete-tree⁻,delete-key-∈⁻anddelete-key⁻. Together these can be used to prove(k′ ∈ delete k t) ⇔ (k′ ≉ k × k′ ∈ t), which fully characterizesdelete.delete⁺,delete-tree⁻, anddelete-key⁻correspond to Coq.FSets.FSetInterface'sremove_2,remove_3, andremove_1, respectively.Just like other lemmas in Data.Tree.AVL.Indexed.Relation.Unary.Any.Properties,
delete⁺,delete-tree⁻,delete-key⁻generalizek′ ∈ ttoAny P t.delete-key-∈⁻is essentally a helper fordelete-key⁻, which would be difficult to prove directly.Many more lemmas were added for AVL functions that
deletedepends on. These additional lemmas characterize the functionscastʳ,joinˡ⁺,joinʳ⁺,joinˡ⁻,joinʳ⁻,headTail, andjoin. Adding all this code to Properties.agda causedmake testto overflow past the 4096 MB default heap size. This was solved by breaking Properties.agda into multiple files, with roughly one file for each AVL function.Added proofs are a similar style the preexisting proofs in Properties.agda.