Add Queue datatype - #3084
Conversation
|
When adding new functionality, we tend to make somewhat larger PRs than when fixing things. In particular, a bunch of properties should be proved too. The main reason is that quite often the proofs reveal when some of functionality was written in a correct-but-hard-to-use (or reason about) manner. So basically all of #3083 will need to be done in this PR. On the other hand, asking for reviews of the intermediate steps is a good idea. |
| toList empty = [] | ||
| toList (queue dq-hd dq-tail eq) = dq-hd ∷ (dq-tail ++ (reverse eq)) | ||
|
|
||
| -- Create a Queue from a List, such that the elements |
There was a problem hiding this comment.
Probably consider fromSnocList too.
There was a problem hiding this comment.
Some alternative suggestions as to implementation.
Against @JacquesCarette , and because I'm a bit more 'OO-minded', I tend to put things like Empty/isEmpty as manifest fields of the record, because then I don't have to think about the scope management. Similarly toList, because all the pieces are at-hand without further ado... but YMMV.
As for references for this kind of implementation, probably Okasaki's "Purely functional data structures" would be the go-to citation?
Oh, and: fix-whitespace!
There is also https://doi.org/10.1017/S0956796800001489 |
So, eg.: toList-fromList : toList (fromList xs) ≡ xsThe converse direction requires some simulation relation on |
Co-authored-by: jamesmckinna <31931406+jamesmckinna@users.noreply.github.com>
Co-authored-by: jamesmckinna <31931406+jamesmckinna@users.noreply.github.com>
|
Isn't |
Yeah, it is...
Edit: I see where I'm going wrong! It's not observational at all... |
Sorry! I'd in the meantime refactored my own experiment to have fromList : List A → Queue A
fromList xs = mkQ xs [] (const [])which is obviously uncoupled from |
| empty-toList : toList (empty {A = A}) ≡ [] | ||
| fromList-empty : empty {A = A} ≈ fromList [] |
There was a problem hiding this comment.
Thanks for this... I'd needed some specimen fields, but it occurs to me that we should only add IsQueue once we actually agree what properties we want!
There was a problem hiding this comment.
Eg the above two properties, I think, follow from:
record IsQueue {Q : Set a → Set a} (rawQ : RawQueue Q) : Set (suc a) where
open RawQueue rawQ
field
isEquivalence : IsEquivalence (_≈_ {A = A})
≈-resp-Empty : Empty Respects (_≈_ {A = A})
≈-=[toList]⇒-≡ : _≈_ {A = A} =[ toList ]⇒ _≡_
empty-toList : ∀ {q : Q A} → Empty q → Null (toList q)
empty-fromList : Null {A = A} xs → Empty (fromList xs)
toList-fromList : ∀ {q : Q A} → q ≈ fromList xs → toList q ≡ xs
fromList-toList : ∀ {q : Q A} → xs ≡ toList q → fromList xs ≈ q
toList-enqueue : ∀ {q : Q A} → toList (enqueue x q) ≡ toList q List.∷ʳ x
toList-dequeue : ∀ {q : Q A} → .{{i : False (empty? q)}} →
let x , r = dequeue q {{i}} in toList q ≡ x ∷ toList rThere was a problem hiding this comment.
I think enqueue-suc is good to add as well. And maybe something like
empty-enqueue-dequeue : ∀ {q : Q A} {x : A} → Empty q → proj₁ (dequeue (enqueue x q)) ≡ xAnd just generally some more properties on the behaviour of enqueue and dequeue
There was a problem hiding this comment.
Given toList-enqueue and toList-dequeue, I think _≈_ can be defined generically as
_≈_ : ∀ {A : Set a} → Rel (Q A) a
q ≈ q' = (toList q) ≡ (toList q')Which is how it is currently defined in TwoList.Base!
But would this mean moving _≈_ to IsQueue? which feels like it somewhat betrays the distinction between RawQueue and IsQueue...
There was a problem hiding this comment.
To maintain 'abstraction', it seemed that simply requiring an implication in the spec was preferably to insisting this be definitional... the fact that the proof of that property is then trivial for TwoList.Base is a bonus...?
Ah.. you think it's definable as _≈_ = _≡_ on toList (see Function.Base._on_) given the toList characterisations? Could be!?
There was a problem hiding this comment.
I think
enqueue-sucis good to add as well. And maybe something likeempty-enqueue-dequeue : ∀ {q : Q A} {x : A} → Empty q → proj₁ (dequeue (enqueue x q)) ≡ x
And just generally some more properties on the behaviour of enqueue and dequeue
Hmmm... there is a whole thing here, and I'd like @JacquesCarette and @MatthewDaggitt to weigh in on this, as it's yet another twist on the 'narrow' vs. 'wide' signature for a given 'algebraic specification'. The enqueue-suc property might seems good to have, but it is in any case derivable once you have toList-enqueue, isn't it?
There was a problem hiding this comment.
See also @MatthewDaggitt 's comment on #1552 ... it seems my instinct towards minimality/definitional extension may be at odds with usability. So a design more akin to that introduced as Effect.Foldable.RawFoldable{WithDefaults} might be more suitable?
There was a problem hiding this comment.
I like the general idea of Effect.Foldable.RawFoldable{WithDefaults} a lot, but wonder if there's an approach that doesn't require two records?
Obviously, native 'defaults' would be nice, but in is place, maybe a function/constructor that could take an implementation of the minimal interface and fill in the 'defaults'?
For lemmas/proofs, maybe there's also room to consider what should just be generic properties of Queues and not part of the interface, such that they would be placed in say Queue.Properties.
All open to discussion!
| ... | [] = queue (x ∷ []) [] | ||
| ... | front@(_ ∷ _) = queue front (x ∷ bs) | ||
|
|
||
| dequeue : ∀ (q : Queue A) .{{_ : False (empty? q)}} → A × Queue A |
There was a problem hiding this comment.
I don't think this will be ergonomic to use as opposed to the
Maybe-returning one.
Note that the List equivalents are all Maybe-based:
https://agda.github.io/agda-stdlib/master/Data.List.Base.html#8038
I'm not against having a dequeue-style variant that always succeeds
by constraining its input but in that case it should be suffixed IMO. We
may also want to go for full-blown size-indexed queues in that case.
There was a problem hiding this comment.
I'm torn between the two directions myself; I don't have enough 'proof intuition' to know which is best! It's an easy change between the two for the most part, so I'll let other maintainers discuss :-)
There was a problem hiding this comment.
Well, in a way, the definition of dequeue′ as a derived form shows the clunkiness, so in that sense I agree.
I'd understood from @Taneb 's comments above that this was an opportunity for something other than the pure-haskell-ish style, and of course that style is a major influence on the 'core' of stdlib. And yes: on whatever types things should have, in the spec or in any candidate implementation, we should reach a reasonable consensus! But nothing stops us from having the Maybe style...
What name do you think the 'guarded' version should have?
There was a problem hiding this comment.
Oh, and regarding the ergonomics (I've always been a bit shaky on True/False... ;-)), we can simplify this a bit to
dequeue : Q A → Maybe (A × Q A)
dequeue q with isEmpty q in eq
... | true = nothing
... | false = just (dequeue-guarded q)
where instance
_ : T (not (isEmpty q))
_ rewrite eq = _for the 'obvious' renamings of everything that has gone before, and unfolding False...
Co-authored-by: jamesmckinna <31931406+jamesmckinna@users.noreply.github.com>
|
|
||
| {-# OPTIONS --without-K --safe #-} | ||
|
|
||
| module Data.Queue where |
There was a problem hiding this comment.
Maybe this should itself be Data.Queue.TwoList as the top-level (sic) module for this implementation?
Again, up for discussion!
There was a problem hiding this comment.
As in have a file Data/Queue/TwoList.agda with this content?
Indeed, this file itself will be changed to not just have TwoList.Base imported, and will then likely import Data,Queue.TwoList instead?
| to𝔹 : Q A → Bool | ||
| to𝔹 = isYes ∘ empty? |
There was a problem hiding this comment.
Similarly, should this be called null? or isEmpty? or what, in general...
|
Note: sorry for pushing a file with holes in! |
| {!!} ≡⟨⟩ | ||
| {!!} | ||
| ++-[] : ∀ {xs ys : List A} → (xs ++ ys) ≡ [] → ys ≡ [] | ||
| ++-[] {xs = []} {ys = []} xs++ys≡[] = xs++ys≡[] |
There was a problem hiding this comment.
Note that the equivalent properties in Data.Nat.Properties have more complex names:
m+n≡0⇒m≡0 : ∀ m {n} → m + n ≡ 0 → m ≡ 0
m+n≡0⇒n≡0 : ∀ m {n} → m + n ≡ 0 → n ≡ 0I personally would expect ++-[] to be a proof that xs ++ [] ≡ xs
There was a problem hiding this comment.
There are two hard things in computer science...
Descriptive names is definitely something I need to get a hold of!
Will eventually resolve #3083. Uses the two-list method mentioned in #3072 (but in a PR not generated by an LLM this time).
There may be some poor stylistic choices due to my unfamiliarity with the standard library, for which I am sorry! There are some 'low hanging' basic operations that could still be added, but I think those would be good for separate PRs.