Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
4905cc6
Add definition of Queue and basic operations
silas-hw Jul 20, 2026
4125b12
Add top-level Queue module
silas-hw Jul 20, 2026
bf7790b
Add some basic functions
silas-hw Jul 20, 2026
d26d47c
Add singleton function
silas-hw Jul 20, 2026
6a8bf86
Fix imports
silas-hw Jul 21, 2026
9056b95
Rename l : Level -> a : Level
silas-hw Jul 21, 2026
85e54c5
Use two levels instead of one
silas-hw Jul 21, 2026
95be562
Refactor Queue to use record definition
silas-hw Jul 21, 2026
bebe5a2
Refactor Queue to use record definition
silas-hw Jul 21, 2026
9ef66fb
Cleanup
silas-hw Jul 21, 2026
a0b0353
Add properties file
silas-hw Jul 21, 2026
253edac
Use record syntax in dequeue and enqueue
silas-hw Jul 21, 2026
100d5db
Fix comment
silas-hw Jul 21, 2026
e3f8872
Fix typo in 'amortized'
silas-hw Jul 22, 2026
48b6afd
Use as-pattern in enqueue
silas-hw Jul 22, 2026
86bed0a
Merge branch 'queue' of https://github.com/silas-hw/agda-stdlib into …
silas-hw Jul 22, 2026
0bf49e7
Use ⊥-elim on impossible dequeue case
silas-hw Jul 22, 2026
0561f10
Update argument order for enqueue
silas-hw Jul 22, 2026
1c1d2c6
Add proof that enqueue increases size by 1
silas-hw Jul 22, 2026
55792de
Fix whitespaace
silas-hw Jul 22, 2026
7e7edab
Fix whitespace
silas-hw Jul 22, 2026
9810428
Reformat equaltiy proofs to abide by style guide
silas-hw Jul 22, 2026
bedd0df
Move TwoList Queue into its own module to allow for different impleme…
silas-hw Jul 23, 2026
79b5d6c
Remove unneeded parens
silas-hw Jul 23, 2026
5b36b00
Cleanup isEmpty
silas-hw Jul 23, 2026
7de85c2
Rename 'list' to 'xs' in fromList
silas-hw Jul 23, 2026
1c56ada
Make xs explicit in toList-fromList
silas-hw Jul 23, 2026
5cacded
Use As-pattern in toList-fromList
silas-hw Jul 23, 2026
a3c6eda
Rename enqueueSuc to size-enqueue
silas-hw Jul 23, 2026
c3b93fb
Add start of QueueSpec
silas-hw Jul 23, 2026
3c61400
Add size-empty lemma
silas-hw Jul 24, 2026
a2d5854
Add smart constructor
silas-hw Jul 24, 2026
4ae85fc
fix: `whitespace` for `Data.Queue.TwoList.Properties`
jamesmckinna Jul 26, 2026
257a08b
Update src/Data/Queue/QueueSpec.agda
silas-hw Jul 27, 2026
612c743
Update src/Data/Queue/QueueSpec.agda
silas-hw Jul 27, 2026
e0b83dd
Improve null-::
silas-hw Jul 27, 2026
a071100
Add RawQueue interface and bundle into IsQueue
silas-hw Jul 27, 2026
379375a
Cleanup code
silas-hw Jul 27, 2026
d724dfb
Define equality relation on TwoList queues
silas-hw Jul 27, 2026
41e76dd
Add approx relation
silas-hw Jul 27, 2026
c2246b4
Remove stale file
silas-hw Jul 27, 2026
e8ff3b8
Fix whitespace
silas-hw Jul 27, 2026
e2cc2c8
Cleanup dequeue on TwoList
silas-hw Jul 28, 2026
e790193
Add properties to IsQueue
silas-hw Jul 28, 2026
6a717da
Fix Properties to use new definitions in QueueSpec
silas-hw Jul 28, 2026
d1a9ea9
Prove IsEquivalence of ≈
silas-hw Jul 28, 2026
2ec3eaa
fix: `whitespace`
jamesmckinna Jul 29, 2026
99fc8f1
Cleanup code
silas-hw Jul 29, 2026
6fe776f
Add All< and Null<
silas-hw Jul 29, 2026
0512a11
Update TwoList Queue to use SnocList
silas-hw Jul 29, 2026
009697c
Fix whitespace
silas-hw Jul 29, 2026
b4e1ec7
Cleanup code
silas-hw Jul 29, 2026
07212a4
Fix whitespace
silas-hw Jul 29, 2026
84d517b
Prove ≈-resp-Empty on TwoList
silas-hw Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/Data/Queue.agda
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
------------------------------------------------------------------------
-- The Agda standard library
--
-- Queues
------------------------------------------------------------------------

{-# OPTIONS --without-K --safe #-}

module Data.Queue where

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe this should itself be Data.Queue.TwoList as the top-level (sic) module for this implementation?
Again, up for discussion!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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?


open import Data.Queue.TwoList.Base public
84 changes: 84 additions & 0 deletions src/Data/Queue/QueueSpec.agda
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
------------------------------------------------------------------------
-- The Agda standard library
--
-- Queue specification
------------------------------------------------------------------------

{-# OPTIONS --without-K --safe #-}

module Data.Queue.QueueSpec where

open import Data.Bool.Base using (Bool)
open import Data.List.Base as List using (List; []; length; _∷_)
open import Data.List.Relation.Unary.All using (Null)
open import Data.Maybe.Base using (Maybe; nothing; just)
open import Data.Nat.Base using (ℕ)
open import Data.Product.Base using (_×_; proj₁; proj₂)
open import Function.Base using (_∘_)
open import Level
open import Relation.Binary.Core using (Rel; _=[_]⇒_)
open import Relation.Binary.Definitions using (_Respects_)
open import Relation.Binary.Structures using (IsEquivalence)
open import Relation.Binary.PropositionalEquality.Core using (_≡_)
open import Relation.Nullary.Decidable.Core using (yes; no; isYes; False; does)
open import Relation.Unary using (Pred; Decidable)

private
variable
a b : Level
A : Set a
B : Set b

-- RawQueue defines the 'computations' available on Queues
-- without any of the associated proofs that determine its
-- correctness.
record RawQueue (Q : Set a → Set a) : Set (suc a) where

field
_≈_ : ∀ {A : Set a} → Rel (Q A) a
Empty : ∀ {A : Set a} → Pred (Q A) a
empty? : Decidable (Empty {A = A})
fromList : List A → Q A
toList : Q A → List A
enqueue : A → Q A → Q A
dequeue : (q : Q A) → .{{False (empty? q)}} → A × Q A

empty : Q A
empty = fromList []

pure : A → Q A
pure = fromList ∘ List.[_]

size : Q A → ℕ
size = length ∘ toList

to𝔹 : Q A → Bool
to𝔹 = isYes ∘ empty?
Comment on lines +55 to +56

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similarly, should this be called null? or isEmpty? or what, in general...


dequeue′ : Q A → Maybe (A × Q A)
dequeue′ q with empty? q in eq
... | yes _ = nothing
... | no _ = just (dequeue q)
where instance
_ : False (empty? q)
_ rewrite eq = _

-- IsQueue bundles RawQueue with proofs of a Queues correctness,
-- such as enqueue adding 1 to the Queue's size
-- NOTE: not finished adding everything!
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 : ∀ {xs : List A} → Null {A = A} xs → Empty (fromList xs)
toList-fromList : ∀ {q : Q A} {xs : List A} → q ≈ fromList xs → toList q ≡ xs
fromList-toList : ∀ {q : Q A} {xs : List A} → xs ≡ toList q → fromList xs ≈ q
toList-enqueue : ∀ {q : Q A} {x : A} → toList (enqueue x q) ≡ toList q List.∷ʳ x
-- for some reason, let x , r = ... doesn't bind x and r??
toList-dequeue : ∀ {q : Q A} → .{{i : False (empty? q)}} →
let xr = dequeue q {{i}} in toList q ≡ proj₁ xr ∷ toList (proj₂ xr)
155 changes: 155 additions & 0 deletions src/Data/Queue/TwoList/Base.agda
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
------------------------------------------------------------------------
-- The Agda standard library
--
-- Queues, basic types and operations
------------------------------------------------------------------------
{-# OPTIONS --without-K --safe #-}

-- Queues implemented with the two-list method described in
-- "Purely Functional Data Structures", Chris Okasaki, 1996
--
-- Note that the weaker invariant is used here that only guarantees
-- amortized O(1) when the structure is used non-persistently.

module Data.Queue.TwoList.Base where

open import Level using (Level)
open import Data.Bool.Base using (Bool; true; false)
open import Data.Empty using (⊥-elim)
open import Data.List.Base as List using (List; []; _∷_; reverse; _++_; length; null)
open import Data.List.Relation.Unary.All using (Null; []; _∷_)
open import Data.List.Relation.Unary.All.Properties using (null⇒Null; Null⇒null)
open import Data.Maybe.Base using (Maybe; nothing; just)
open import Data.Nat.Base using (ℕ; zero; suc; _+_)
open import Data.Product using (_×_; _,_; proj₂)
open import Data.Queue.QueueSpec using (RawQueue; IsQueue)
open import Data.SnocList.Base as SnocList using (List<; toList>; fromList>; []; _<:_)
open import Data.SnocList.Relation.Unary.All
open import Data.Unit.Base using (⊤)
open import Function.Base using (id; const; _∘_)
open import Relation.Binary.PropositionalEquality.Core using (_≡_)
open import Relation.Binary.Core using (Rel)
open import Relation.Nullary using (¬_)
open import Relation.Nullary.Decidable.Core using (yes; no; isYes; False)
open import Relation.Nullary.Reflects using (ofʸ; ofⁿ)
open import Relation.Unary using (Pred; Decidable)

private
variable
a b : Level
A : Set a
B : Set b

¬null : {a : A} {as : List A} → ¬ (Null (a ∷ as))
¬null (() Data.List.Relation.Unary.All.∷ n)

¬null< : {a : A} {as : List< A} → ¬ (Null< (as <: a))
¬null< (() Data.SnocList.Relation.Unary.All.<: n)

null-[] : ∀ {xs : List A} → Null xs → Null {A = A} []
null-[] = const []

null<-[] : ∀ {xs : List< A} → Null< xs → Null {A = A} []
null<-[] = const []

null-∷ : ∀ {x} {xs ys : List A} → Null (x ∷ xs) → Null ys
null-∷ (()∷ _)

null-<: : ∀ {x} {xs : List< A} {ys : List A} → Null< (xs <: x) → Null ys
null-<: (()<: _)

-- A Queue consists of a front (dequeue) and back (enqueue) list
-- When enqueing (unless it is the first element), elements are cons'd
-- to the enqueue list.
--
-- When dequeuing, elements are taken from the head of the dequeue
-- list. If this is empty, the enqueue list is reversed and swapped
-- with the dequeue list.
--
-- The dequeue-list should be empty iff the whole queue is empty.

record Queue (A : Set a) : Set a where
constructor mkQ
field
front : List< A
back : List A
Comment thread
silas-hw marked this conversation as resolved.
inv : Null< front → Null back

------------------------------------------------------------------------
--- Basic Functions/Relations/Operators

Empty : ∀ {A : Set a} → Pred (Queue A) a
Empty {a} {A} q = Null< (Queue.front q)

empty? : Decidable (Empty {A = A})
empty? (mkQ front back inv) .Relation.Nullary.does = SnocList.null front
empty? (mkQ [] back inv) .Relation.Nullary.proof = ofʸ []
empty? (mkQ (xs <: x) back inv) .Relation.Nullary.proof = ofⁿ (λ e → ⊥-elim (¬null< e))

isEmpty : Queue A → Bool
isEmpty q = SnocList.null (Queue.front q)

------------------------------------------------------------------------
--- Smart Constructor

queue : List< A → List A → Queue A
queue [] ys = mkQ (fromList> (reverse ys)) [] null<-[]
queue xs@(_ <: _) ys = mkQ xs ys null-<:

------------------------------------------------------------------------
--- Conversion to/from List

-- Create a List from a Queue, such that the last that would be dequeued
-- becomes the head of the list
toList : Queue A → List A
toList q = (Queue.back q) ++ (toList> (Queue.front q))

-- Create a Queue from a List, such that the elements

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Probably consider fromSnocList too.

-- of the list would be dequeued starting from its last element
fromList : List A → Queue A
fromList xs = queue (fromList> xs) []

------------------------------------------------------------------------
-- Construction & Destruction

empty : Queue A
empty = fromList []

enqueue : A → Queue A → Queue A
enqueue x q with bs ← Queue.back q | Queue.front q
... | [] = queue ([] <: x) []
... | front@(_ <: _) = queue front (x ∷ bs)

dequeue : ∀ (q : Queue A) .{{_ : False (empty? q)}} → A × Queue A

Copy link
Copy Markdown
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 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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 :-)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

@jamesmckinna jamesmckinna Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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...

dequeue (mkQ (xs <: x) back _) = x , queue xs back

-- Create a queue with a single element
singleton : A → Queue A
singleton = fromList ∘ List.[_]

-- map : (A → B) → Queue A → Queue B
-- map f empty = empty
-- map f (queue x xs ys) = queue (f x) (List.map f xs) (List.map f ys)

------------------------------------------------------------------------
-- Relations

-- Under the property that toList returns a list in
-- the order of dequeue
_≈_ : ∀ {A : Set a} → Rel (Queue A) a
q ≈ q' = (toList q) ≡ (toList q')
Comment thread
jamesmckinna marked this conversation as resolved.

------------------------------------------------------------------------
--- TwoList Queue is a Queue

instance
TwoList-RawQueue : RawQueue {a} Queue
TwoList-RawQueue = record
{ _≈_ = _≈_
; Empty = Empty
; empty? = empty?
; fromList = fromList
; toList = toList
; enqueue = enqueue
; dequeue = dequeue
}
Loading
Loading