Skip to content

[Add] padRight properties to Data.Vec.Properties #2769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

e-mniang
Copy link

@e-mniang e-mniang commented Jul 14, 2025

This PR adds several equational properties for padRight to Data.Vec.Properties, extending the standard vector operations toolkit with useful lemmas for reasoning about padding on the right.

Added properties:


-     padRight-lookup: accessing a padded vector retrieves the original values at valid indices

-     padRight-map : padRight commutes with map

-     padRight-zipWith : padRight commutes with zipWith

-     padRight-zipWith₁: generalizes padRight-zipWith for different vector lengths

-     padRight-take: recovers the original prefix with take

-     padRight-drop: the suffix added by padding equals replicate

-     padRight-updateAt: updates on padded vectors correspond to updates on the original vectors

Each property is proven by structural induction and uses standard lemmas such as zipWith-replicate, map-replicate, and inject≤.


Authored by : @jmougeot

@e-mniang e-mniang changed the title Add padRight properties to Data.Vec.Properties [Add] padRight properties to Data.Vec.Properties Jul 14, 2025
@jamesmckinna
Copy link
Contributor

jamesmckinna commented Jul 15, 2025

See also: #2770 . Even if the types of the Base operations are not (yet) changed, their properties should, where possible, be written to reflect that they only make irrelevant use of their m ≤ n arguments... UPDATED hmm, but it's not quite as simple as I'd hoped (and suggested as changes) unless we also make the changes proposed in #2770 which allow padRight to reduce more eagerly under weaker assumptions about the proofs of m ≤ n...

Tat being the case, I'll shout out to @JacquesCarette as to whether we go for this PR as is, and refactor downstream, or mark this blocking on #2770 ?

@jamesmckinna
Copy link
Contributor

I should have minded my manners better, and thanked you for the PR before piling in with my nitpicks! These are all, in whatever form they eventually take, a useful contribution... and since padRight goes hand-in-hand with truncate, we should perhaps welcome a follow-up PR with the corresponding lemmas for truncate too?

Copy link
Author

@e-mniang e-mniang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi!
Thanks for your comments! I've made the necessary changes based on your feedback. I just had a small issue with the last point — the irrelevant part of the type signature.
Looking forward to your review of this version!

Great idea for the lemmas for truncate, will definitely look into that!

Copy link
Contributor

@jamesmckinna jamesmckinna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More nitpicks, I'm afraid, but otherwise looks good to go...
... but you'll need to write CHANGELOG entries as well. Suggest you do this once the reset after the v2.3 release is complete, though...

... UPDATED which has now taken place. So, if you look at CHANGELOG.md, you'll see under Additions to existing modules... please add the lemma names with their types, and you can see existing versions under eg CHANGELOG/v2.2.md to see formatting conventions etc.

Comment on lines +1186 to +1187
padRight-trans : ∀ {p} (m≤n : m ≤ n) (n≤p : n ≤ p) (a : A) (xs : Vec A m) →
padRight (≤-trans m≤n n≤p) a xs ≡ padRight n≤p a (padRight m≤n a xs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As previously, please don't use p here, when o would conventionally be better.

Suggested change
padRight-trans : {p} (m≤n : m ≤ n) (n≤p : n ≤ p) (a : A) (xs : Vec A m) →
padRight (≤-trans m≤n n≤p) a xs ≡ padRight n≤p a (padRight m≤n a xs)
padRight-trans : (m≤n : m ≤ n) (n≤o : n ≤ o) (a : A) (xs : Vec A m) →
padRight (≤-trans m≤n n≤o) a xs ≡ padRight n≤o a (padRight m≤n a xs)

Comment on lines +1188 to +1189
padRight-trans z≤n n≤p a [] = padRight-replicate n≤p a
padRight-trans (s≤s m≤n) (s≤s n≤p) a (x ∷ xs) = cong (x ∷_) (padRight-trans m≤n n≤p a xs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LIkewise

Suggested change
padRight-trans z≤n n≤p a [] = padRight-replicate n≤p a
padRight-trans (s≤s m≤n) (s≤s n≤p) a (x ∷ xs) = cong (x ∷_) (padRight-trans m≤n n≤p a xs)
padRight-trans z≤n n≤o a [] = padRight-replicate n≤o a
padRight-trans (s≤s m≤n) (s≤s n≤o) a (x ∷ xs) = cong (x ∷_) (padRight-trans m≤n n≤o a xs)

(a : A) (b : B) (xs : Vec A m) (ys : Vec B p) →
zipWith f (padRight m≤n a xs) (padRight (≤-trans p≤m m≤n) b ys) ≡
padRight m≤n (f a b) (zipWith f xs (padRight p≤m b ys))
padRight-zipWith₁ {p} f m≤n p≤m a b xs ys = trans (cong (zipWith f (padRight m≤n a xs)) (padRight-trans p≤m m≤n b ys))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever else above regarding m n o/p ordering of the variables...

Suggested change
padRight-zipWith₁ {p} f m≤n p≤m a b xs ys = trans (cong (zipWith f (padRight m≤n a xs)) (padRight-trans p≤m m≤n b ys))
padRight-zipWith₁ f m≤n p≤m a b xs ys = trans (cong (zipWith f (padRight m≤n a xs)) (padRight-trans p≤m m≤n b ys))

Implicit p isn't even used here, so it's much better to omit it to make your code less brittle.

Comment on lines +1207 to +1210
padRight-zipWith₁ : ∀ {p} (f : A → B → C) (m≤n : m ≤ n) (p≤m : p ≤ m)
(a : A) (b : B) (xs : Vec A m) (ys : Vec B p) →
zipWith f (padRight m≤n a xs) (padRight (≤-trans p≤m m≤n) b ys) ≡
padRight m≤n (f a b) (zipWith f xs (padRight p≤m b ys))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh!

  • p shouldn't be used here as an implicit; it's previously bound at module-level scope as a variable of type Level, while o is bound at type Nat, so use that instead
  • what's the correct order of assumptions here? that m ≤ n precede p ≤ m, or that you instead order them so that m ≤ n ≤ o and then fix up the relevant assumption uses to reflect that?

Comment on lines +1216 to +1217
padRight-take m≤n a [] p = refl
padRight-take (s≤s m≤n) a (x ∷ xs) p = cong (x ∷_) (padRight-take m≤n a xs (suc-injective p))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use p here as a pattenr variable for an equation, especially given that it is already implicitly bound above at type Level... etc.
Instead use eq or the assumption name you have already chosen in the lemma statement (reduce cognitive load on the reader as the implied design heuristic here)

Comment on lines +1227 to +1228
padRight-updateAt {n = suc n} (s≤s m≤n) (y ∷ xs) f zero x = refl
padRight-updateAt {n = suc n} (s≤s m≤n) (y ∷ xs) f (suc i) x = cong (y ∷_) (padRight-updateAt m≤n xs f i x)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
padRight-updateAt {n = suc n} (s≤s m≤n) (y ∷ xs) f zero x = refl
padRight-updateAt {n = suc n} (s≤s m≤n) (y ∷ xs) f (suc i) x = cong (y ∷_) (padRight-updateAt m≤n xs f i x)
padRight-updateAt {n = suc _} (s≤s m≤n) (y ∷ xs) f zero x = refl
padRight-updateAt {n = suc _} (s≤s m≤n) (y ∷ xs) f (suc i) x = cong (y ∷_) (padRight-updateAt m≤n xs f i x)

The actual n isn't used subsequently on the LHS, nor on the RHS, so omitting it in favour of a wildcard _ makes the code less brittle.

Comment on lines +1224 to +1226
padRight-updateAt : ∀ (m≤n : m ≤ n) (xs : Vec A m) (f : A → A) (i : Fin m) (x : A) →
updateAt (padRight m≤n x xs) (inject≤ i m≤n) f ≡
padRight m≤n x (updateAt xs i f)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the right argument order here? A heuristic I learnt from @JacquesCarette would be: args that vary most should be rightmost, those that are invariant/vary less should be leftmost, together with the heuristic that says that argument order for lemmas should try to follow the argument order for the 'main' function being characterised, so here at least something like:

Suggested change
padRight-updateAt : (m≤n : m ≤ n) (xs : Vec A m) (f : A A) (i : Fin m) (x : A) →
updateAt (padRight m≤n x xs) (inject≤ i m≤n) f ≡
padRight m≤n x (updateAt xs i f)
padRight-updateAt : (m≤n : m ≤ n) (x : A) (xs : Vec A m) (f : A A) (i : Fin m) →
updateAt (padRight m≤n x xs) (inject≤ i m≤n) f ≡
padRight m≤n x (updateAt xs i f)

with the position of f being moot, but perhaps should be rightmost to reflect its arg position in the function calls?

Comment on lines +1172 to +1173
cast-replicate {m = zero} {n = zero} _ _ = refl
cast-replicate {m = suc _} {n = suc _} eq x = cong (x ∷_) (cast-replicate (suc-injective eq) x)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alignment?

Suggested change
cast-replicate {m = zero} {n = zero} _ _ = refl
cast-replicate {m = suc _} {n = suc _} eq x = cong (x ∷_) (cast-replicate (suc-injective eq) x)
cast-replicate {m = zero} {n = zero} _ _ = refl
cast-replicate {m = suc _} {n = suc _} eq x = cong (x ∷_) (cast-replicate (suc-injective eq) x)

@jamesmckinna jamesmckinna added this to the v2.4 milestone Jul 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants