Skip to content
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

Add Data.Nat.Bounded #2257

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

Conversation

jamesmckinna
Copy link
Contributor

@jamesmckinna jamesmckinna commented Jan 15, 2024

Prompted by a recent discussion on the Agda Zulip, and realising that we don't actually have the details of this spelt out in Data.Fin as such...

... and I decided to post it as a PR directly, rather than go via a separate issue first.

Outstanding issues:

If approved, then I would refactor to reflect the customary Data.X.Base|Properties|Literals|Show|... hierarchical decomposition... DONE

Comments welcome!

constructor ⟦_⟧<
field
value : ℕ
.{{bounded}} : value < n
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we very often have instances floating around of type a < b. Does this get inferred often?

Copy link
Contributor Author

@jamesmckinna jamesmckinna Jan 16, 2024

Choose a reason for hiding this comment

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

Well, that had been my thinking underlying #1998 and (the reverted parts of) #2000 ... but indeed reimplementing this via Data.Refinement wouldn't introduce that extra twist. I had the ... even 'gut feeling' seems a bit strong ... sense that in suitable deployment contexts (fixed-width arithmetic; positional notation for numerals; ...) it might be the case the ambient instances would be available from the context ... but perhaps you're correct... UPDATED: unless it's replaced by #2000 's T (value <ᵇ n)?

Copy link
Contributor

Choose a reason for hiding this comment

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

I like the idea of adding BoundedNat as an equivalent for Fin, but I think the 'experiment' of using an instance for bounded is premature.

Supporting evidence: in code that Conor and I were playing around with, we had exactly BoundedNat in our extensional universe as our definition of Fin, and we did have to supply the proof a non-trivial number of times. (That the proof was irrelevant without needed Agda's irrelevance machinery was what we were really after.)

Copy link
Contributor Author

@jamesmckinna jamesmckinna Jan 20, 2024

Choose a reason for hiding this comment

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

OK, so that's two of you... so either:

I guess it's the first choice, then, with the second as a possible downstream refactoring? DONE.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

NB making bounded an 'ordinary' (even if irrelevant) field vitiates somewhat my choice of ⟦_⟧< as a constructor, which could be used 'bare', without bracketing, as a pattern constructor, in favour of ⟦_⟧<_ with the explicit bound argument, and the value argument position now mostly redundant... oh well. The redundancy is harmless, unless someone else has a compelling improvement on my notation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The addition of a Number instance with Constraint m = T (m <ᵇ n) biases towards the second alternative...
... but stdlib style suggests using True (m <? n) instead... cf. Data.Fin.Literals

Any comment on the trade-offs between the two, given that the latter is much more indirect than the former...?

constructor ⟦_⟧<
field
value : ℕ
.{{bounded}} : value < n
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the idea of adding BoundedNat as an equivalent for Fin, but I think the 'experiment' of using an instance for bounded is premature.

Supporting evidence: in code that Conor and I were playing around with, we had exactly BoundedNat in our extensional universe as our definition of Fin, and we did have to supply the proof a non-trivial number of times. (That the proof was irrelevant without needed Agda's irrelevance machinery was what we were really after.)

src/Data/Nat/Bounded.agda Outdated Show resolved Hide resolved

{-# OPTIONS --cubical-compatible --safe #-}

module Data.Nat.Bounded where
Copy link
Contributor

Choose a reason for hiding this comment

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

So to touch on your question of whether this belongs in the library or a readme file, I guess my question is what circumstances is this easier to work with than Fin? In general we try to avoid duplicate but equivalent definitions unless there is clear reason that sometimes one representation is easier to work with than the other...

Copy link
Contributor Author

@jamesmckinna jamesmckinna Jan 20, 2024

Choose a reason for hiding this comment

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

Thanks, Matthew! I can think of two or three, some already in the initial preamble (which was really about helping someone solve a related problem of representation, where Fin didn't seem to fit well):

  • fixed-width arithmetic (or even: its extension to variable-widths, but in a way which retains predictable/computable bounds);
  • Digit bases for arithmetic, as a specialised instance/mode of use of the above;
  • revising the 'specification' of Data.Nat.DivMod, to avoid the marshalling/unmarshalling required by the use of Fin to ensure that mod is suitably bounded.

Now, none of these could be said to be a deal-breaker, but I increasingly wonder whether Fin is better suited to its use as an indexing type (for Vec, List, de Bruijn representations, Reflection/deeply embedded syntaxes more generally, etc.), with ℕ< as an arithmetic type/Number instance... cf. #2073 which I might now think makes the 'wrong' choice in using Fin...

Perhaps other pros (and cons!) might be lurking off-stage...
... UPDATED: (pro)

@jamesmckinna
Copy link
Contributor Author

Previously...

... UPDATED:

* re: 3rd point above. This might (eventually) lead to the deprecation of `Data.Nat.DivMod.WithK` (no longer any need to consider erasing proofs of `_≤″_`...?) and hence perhaps to the deprecation of `_≤″_` _altogether_, cf. [Remove all external use of `_≤″_` beyond `Data.Nat.*` #2262](https://github.com/agda/agda-stdlib/pull/2262)

This cashes out as the following breaking (because type-signature-changing... hence not committed to this PR) simplifications to Data.Nat.DivMod and Data.Nat.DivMod.WithK:

record DivMod (dividend divisor : ℕ) : Set where
  constructor result
  field
    quotient  :remainder : ℕ< divisor
    property  : dividend ≡ ⟦ remainder ⟧ + quotient * divisor

  nonZeroDivisor : NonZero divisor
  nonZeroDivisor = nonZeroIndex remainder


infixl 7 _div_ _mod_ _divMod_

_div_ : (dividend divisor : ℕ) .{{_ : NonZero divisor}}  ℕ
_div_ = _/_

_mod_ : (dividend divisor : ℕ) .{{_ : NonZero divisor}}  ℕ< divisor
m mod n = ⟦ m % n ⟧< (m%n<n m n)

_divMod_ : (dividend divisor : ℕ) .{{_ : NonZero divisor}} 
           DivMod dividend divisor
m divMod n = result (m / n) (m mod n) $ m≡m%n+[m/n]*n m n

and (WithK)

_divMod_ : (dividend divisor : ℕ)  .{{ NonZero divisor }} 
           DivMod dividend divisor
m divMod n = result (m / n) (m mod n) $ ≡-erase $ m≡m%n+[m/n]*n m n

Suggestions on how to proceed, if, like me, you find these compelling.

src/Data/Nat/Bounded.agda Outdated Show resolved Hide resolved
src/Data/Nat/Bounded.agda Outdated Show resolved Hide resolved
@jamesmckinna
Copy link
Contributor Author

jamesmckinna commented Jan 27, 2024

I think I proved Bijective believing that to proxy for Isomorphism (of Propositional setoids), and that to be stronger than Equivalence. But... @JacquesCarette , suitably chided by your "wow", perhaps you should school me in what I should have proved instead... (and see previous discussions about my obliviousness/lack of skill with the Function.* hierarchy...)

@jamesmckinna jamesmckinna marked this pull request as draft February 5, 2024 07:17
@jamesmckinna jamesmckinna marked this pull request as ready for review February 5, 2024 12:42
@jamesmckinna
Copy link
Contributor Author

jamesmckinna commented Feb 10, 2024

Am now in a (better) position to prove the two lemmas relating and ℕ / ~, at which point this can merge, from my point of view. DONE

Copy link
Contributor

@JacquesCarette JacquesCarette left a comment

Choose a reason for hiding this comment

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

I remain happy with this.

Comment on lines 190 to 225
module _ .{{_ : NonZero n}} (m o : ℕ) where

open ≡-Reasoning

+-distribˡ-% : ((m % n) + o) ≡ (m + o) modℕ n
+-distribˡ-% = %≡%⇒≡-mod $ begin
(m % n + o) % n ≡⟨ ℕ.%-distribˡ-+ (m % n) o n ⟩
(m % n % n + o % n) % n ≡⟨ cong ((_% n) ∘ (_+ o % n)) (ℕ.m%n%n≡m%n m n) ⟩
(m % n + o % n) % n ≡⟨ ℕ.%-distribˡ-+ m o n ⟨
(m + o) % n ∎

+-distribʳ-% : (m + (o % n)) ≡ (m + o) modℕ n
+-distribʳ-% = %≡%⇒≡-mod $ begin
(m + o % n) % n ≡⟨ ℕ.%-distribˡ-+ m (o % n) n ⟩
(m % n + o % n % n) % n ≡⟨ cong ((_% n) ∘ (m % n +_)) (ℕ.m%n%n≡m%n o n) ⟩
(m % n + o % n) % n ≡⟨ ℕ.%-distribˡ-+ m o n ⟨
(m + o) % n ∎

+-distrib-% : ((m % n) + (o % n)) ≡ (m + o) modℕ n
+-distrib-% = %≡%⇒≡-mod $ begin
(m % n + o % n) % n ≡⟨ ℕ.%-distribˡ-+ m o n ⟨
(m + o) % n ∎

*-distribˡ-% : ((m % n) * o) ≡ (m * o) modℕ n
*-distribˡ-% = %≡%⇒≡-mod $ begin
(m % n * o) % n ≡⟨ ℕ.%-distribˡ-* (m % n) o n ⟩
(m % n % n * (o % n)) % n ≡⟨ cong ((_% n) ∘ (_* (o % n))) (ℕ.m%n%n≡m%n m n) ⟩
(m % n * (o % n)) % n ≡⟨ ℕ.%-distribˡ-* m o n ⟨
(m * o) % n ∎

*-distribʳ-% : (m * (o % n)) ≡ (m * o) modℕ n
*-distribʳ-% = %≡%⇒≡-mod $ begin
(m * (o % n)) % n ≡⟨ ℕ.%-distribˡ-* m (o % n) n ⟩
(m % n * (o % n % n)) % n ≡⟨ cong ((_% n) ∘ (m % n *_)) (ℕ.m%n%n≡m%n o n) ⟩
(m % n * (o % n)) % n ≡⟨ ℕ.%-distribˡ-* m o n ⟨
(m * o) % n ∎
Copy link
Contributor Author

@jamesmckinna jamesmckinna Feb 13, 2024

Choose a reason for hiding this comment

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

This section might benefit from some (more intelligent) refactoring, but I made these last two commits in order to make this PR disjoint from #2292, against that one not being merged.
Latest commit refactors.

jamesmckinna added a commit to jamesmckinna/agda-stdlib that referenced this pull request Feb 13, 2024
@jamesmckinna
Copy link
Contributor Author

In the event that #2292 is not merged, then the Semiring structure of ℕ< should be developed as a followup PR to this one, by analogy with the Ring development there, so the additional arithmetic lemmas are stubs towards such eventual downstream development. But I'll definitely stop here now.

Copy link
Contributor

@JacquesCarette JacquesCarette left a comment

Choose a reason for hiding this comment

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

If you're going to keep tweaking a PR, you should mark it as Draft. That doesn't prevent us from reviewing, but it will make our expectations match reality.

@jamesmckinna
Copy link
Contributor Author

Of course you're right re: tweaking, so apologies for this.
I was spooked by wanting to disconnect this PR from #2292 after @Taneb 's comments on that.

@jamesmckinna
Copy link
Contributor Author

Aaargh... CHANGELOG hell. (Partly self-inflicted; hopefully this will converge soon)

@jamesmckinna jamesmckinna added this to the v2.2 milestone Jun 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants