[Agda] Sized Types: Run-length Encoding Example
Jannis Limperg
jannis at limperg.de
Sun Feb 24 01:19:15 CET 2019
Hej Craig,
z is a mixed recursive-corecursive function, meaning that some of its
recursive calls are justified by a termination argument (x gets closer
to 9) and some by a productivity argument (all other cases). There are
two ways to deal with such functions:
A. Elementary Solution
Split the implementation into two functions:
1. a recursive function which generates the first element of the output
stream;
2. a corecursive function which calls the function from (1) infinitely
often and conses the results.
B. Library Solution
For my B.Sc., I developed a library to deal with mixed
recursive-corecursive functions [1]. It defines a fixpoint combinator
that captures the essense of mixed recursion: You can either recurse by
decreasing a well-founded relation, or corecurse 'under a constructor'.
This allows an implementation that looks more or less like the pseudocode.
Unfortunately, the library requires that you encode your data types as
greatest fixed points of container functors, so it comes with
considerable syntactic baggage. However, you could specialise the
fixpoint combinator to streams (or whatever type interests you) to get
rid of the incidental complexity. The library is documented only in my
thesis (available in the repo), but I'll be happy to answer questions if
you want to use it.
Attached is an Agda file with implementations of both solutions.
[1] https://github.com/JLimperg/well-founded-corecursion
Cheers,
Jannis
-------------- next part --------------
module runlength where
open import Data.Bool using (Bool ; true ; false)
open import Data.List as List using (List ; [] ; _∷_)
open import Data.Nat using
(ℕ ; zero ; suc ; _≟_ ; _∸_ ; _<_ ; _<?_ ; _≤_ ; z≤n ; s≤s)
open import Data.Nat.Properties using (≤-refl)
open import Data.Product using (_×_ ; _,_ ; proj₁ ; proj₂)
open import Data.Unit using (⊤)
open import Level using (Level) renaming (zero to lzero)
open import Relation.Binary using (Rel)
open import Relation.Binary.PropositionalEquality using (_≡_ ; refl)
open import Relation.Nullary using (Dec ; yes ; no)
open import Size using (Size ; Size<_ ; ∞ ; ↑_)
module Elementary where
-- I didn't get this approach to work with the standard library's stream type.
-- Apparently the termination checker is a bit fickle when it comes to
-- productivity arguments that don't use sizes.
record Stream (A : Set) : Set where
coinductive
field
head : A
tail : Stream A
open Stream
data Bit : Set where
zero one : Bit
-- This implementation is a bit convoluted because I wanted to recurse
-- structurally over `fuel`, counting down from 9 to 0. Alternatively,
-- we could use well-founded recursion like in the other solution.
firstDigit′ : ℕ → ℕ → Stream Bit → ℕ × Stream Bit
firstDigit′ maxFuel fuel xs with head xs
firstDigit′ maxFuel fuel xs | zero with fuel ≟ maxFuel
... | yes _ = zero , tail xs
... | no _ = maxFuel ∸ fuel , xs
firstDigit′ maxFuel zero xs | one = maxFuel , xs
firstDigit′ maxFuel (suc fuel) xs | one = firstDigit′ maxFuel fuel (tail xs)
firstDigit : Stream Bit → ℕ × Stream Bit
firstDigit = firstDigit′ 9 9
rlen : Stream Bit → Stream ℕ
rlen xs
= let d , xs′ = firstDigit xs in
λ { .head → d ; .tail → rlen xs′ }
ones : Stream Bit
ones = λ { .head → one ; .tail → ones }
take : ∀ {A} → ℕ → Stream A → List A
take zero xs = []
take (suc n) xs = head xs ∷ take n (tail xs)
test : take 3 (rlen ones) ≡ 9 ∷ 9 ∷ 9 ∷ []
test = refl
module CoWf where
open import Coinduction.WellFounded using
(Container ; _▷_ ; ⟦_⟧ ; M ; inf ; cofixWf)
open import Induction.Nat using (<-wellFounded)
open import Induction.WellFounded using
(WellFounded ; module Inverse-image)
-- The stream container
StreamC : Set → Container lzero lzero
StreamC A = A ▷ λ _ → ⊤
-- Streams are greatest fixed points of the stream container functor.
Stream : Set → Size → Set
Stream A = M (StreamC A)
-- The unfolding of a stream (i.e. a pair `(head , tail)`).
StreamU : Set → Size → Set
StreamU A s = ⟦ StreamC A ⟧ (Stream A s)
head : ∀ {s} {t : Size< s} {A} → Stream A s → A
head xs = proj₁ (inf xs)
tail : ∀ {s A} → Stream A s → {t : Size< s} → Stream A t
tail xs = proj₂ (inf xs) _
cons′ : ∀ {s A} → A → Stream A s → StreamU A s
cons′ x xs = x , λ _ → xs
cons : ∀ {s A} → A → Stream A s → Stream A (↑ s)
cons x xs .inf = cons′ x xs
_<<_ : Rel (Stream ℕ ∞) lzero
xs << ys = 9 ∸ head xs < 9 ∸ head ys
<<-wf : WellFounded _<<_
<<-wf = Inverse-image.wellFounded (λ xs → 9 ∸ head xs) <-wellFounded
∸-<-suc : ∀ {n m} → n < m → m ∸ suc n < m ∸ n
∸-<-suc {n} {zero} ()
∸-<-suc {zero} {suc m} (s≤s n<m) = s≤s ≤-refl
∸-<-suc {suc n} {suc m} (s≤s n<m) = ∸-<-suc n<m
rlenF : ∀ {s} (xs : Stream ℕ ∞)
→ (Stream ℕ ∞ → Stream ℕ s)
→ ((ys : Stream ℕ ∞) → ys << xs → StreamU ℕ s)
→ StreamU ℕ s
rlenF xs corec wfrec with head xs
rlenF xs corec wfrec | zero
= cons′ zero (corec (tail xs))
rlenF xs corec wfrec | x@(suc _) with head (tail xs)
rlenF xs corec wfrec | x@(suc _) | zero
= cons′ x (cons zero (corec (tail (tail xs))))
rlenF xs corec wfrec | x@(suc _) | y@(suc _) with x <? 9
rlenF xs corec wfrec | x@(suc _) | y@(suc _) | no _
= cons′ x (corec (tail xs))
rlenF xs corec wfrec | x@(suc _) | y@(suc _) | yes x<9
= wfrec (cons (suc x) (tail (tail xs))) (∸-<-suc x<9)
rlen : ∀ {s} → Stream ℕ ∞ → Stream ℕ s
rlen = cofixWf <<-wf rlenF
ones : ∀ {s} → Stream ℕ s
ones .inf = 1 , λ _ → ones
take : ∀ {A} → ℕ → Stream A ∞ → List A
take zero xs = []
take (suc n) xs = head xs ∷ take n (tail xs)
test : take 3 (rlen ones) ≡ 9 ∷ 9 ∷ 9 ∷ []
test = refl
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: OpenPGP digital signature
URL: <http://lists.chalmers.se/pipermail/agda/attachments/20190224/2e96f405/attachment.sig>
More information about the Agda
mailing list