Skip to content

Instantly share code, notes, and snippets.

@rntz
Last active September 4, 2024 14:49
Show Gist options
  • Save rntz/c2996e06301d9ae95ee0c7a9b43f5e0d to your computer and use it in GitHub Desktop.
Save rntz/c2996e06301d9ae95ee0c7a9b43f5e0d to your computer and use it in GitHub Desktop.
implementation of muKanren's search strategy in Agda with --guarded
{-# OPTIONS --guarded #-}
module GuardedKanren where
open import Agda.Primitive using (Level)
private variable
l : Level
A B : Set l
data Bool : Set where true false : Bool
if_then_else_ : ∀{A : Set} → Bool → A → A → A
if true then x else y = x
if false then x else y = y
data _×_ (A B : Set) : Set where
_,_ : A → B → A × B
data _≡_ {A : Set} : A → A → Set where
refl : ∀{a} → a ≡ a
data _⊎_ (A B : Set) : Set where
in₁ : A → A ⊎ B
in₂ : B → A ⊎ B
-- Copied & modified from this example file:
-- https://github.com/agda/agda/blob/172366db528b28fb2eda03c5fc9804f2cdb1be18/test/Succeed/LaterPrims.agda
module Prims where primitive primLockUniv : Set₁
open Prims renaming (primLockUniv to LockU) public
postulate Tick : LockU
▹_ : ∀ {l} → Set l → Set l
▹_ A = (@tick x : Tick) -> A
▸_ : ∀ {l} → ▹ Set l → Set l
▸ A = (@tick x : Tick) → A x
-- does this actually *compute*? Can we make it compute instead of being a postulate?
postulate
dfix : ∀ {l} {A : Set l} → (▹ A → A) → ▹ A
fix : ∀ {l} {A : Set l} → (▹ A → A) → A
fix f = f (dfix f)
-- If a type includes its own delayed version, we can do "fix" at that type directly.
delayfix : (▹ A → A) → (A → A) → A
delayfix delay f = fix (λ self → f (delay self))
-- Kanren search monad
data Stream (A : Set) : Set where
nil : Stream A
cons : A → Stream A → Stream A
later : ▹ Stream A → Stream A
-- mukanren's mplus; simpler to prove correct?
union : Stream A → Stream A → Stream A
union nil ys = ys
union (cons x xs) ys = cons x (union ys xs)
union (later x) ys = later (λ t → union ys (x t))
-- -- The eager version I prefer; don't delay until you have no work left to do.
-- union : Stream A → Stream A → Stream A
-- union nil ys = ys
-- union xs nil = xs
-- union (cons x xs) ys = cons x (union xs ys)
-- union xs (cons y ys) = cons y (union xs ys)
-- union (later x) (later y) = later (λ t → union (x t) (y t))
bind : Stream A → (A → Stream B) → Stream B
bind nil f = nil
bind (cons x s) f = union (f x) (bind s f)
bind (later x) f = later λ t → bind (x t) f
-- Ok, let's do some proof search! NB. This is not a kanren yet since we don't have
-- existential variables or unification.
data Node : Set where node-x node-y node-z : Node
node-eq : Node → Node → Bool
node-eq node-x = λ { node-x → true ; _ → false }
node-eq node-y = λ { node-y → true ; _ → false }
node-eq node-z = λ { node-z → true ; _ → false }
edge : Stream (Node × Node)
edge = cons (node-x , node-y)
(cons (node-y , node-z)
(cons (node-x , node-z) nil))
trans : (A → A → Bool) → Stream (A × A) → Stream (A × A)
trans {A} eq edge = delayfix later f
where f : Stream (A × A) → Stream (A × A)
f self = union edge (bind self λ { (y₂ , z) →
bind edge λ { (x , y₁) →
if eq y₁ y₂ then cons (x , z) nil else nil } } )
reach : Stream (Node × Node)
reach = trans node-eq edge
-- Ok, can we prove something about this?
module Attempt1 where
-- I want a type of "streams which completely enumerate some type".
-- To do this I need the type of elements of a stream.
data _∈_ {A : Set} (a : A) : Stream A → Set where
∈car : ∀{as : Stream A} → a ∈ cons a as
∈cdr : ∀{b bs} → a ∈ bs → a ∈ cons b bs
∈later : ∀{as : ▹ Stream A} → ▸ (λ t → a ∈ as t) → a ∈ later as
-- OH NO, this definition of _∈_ is WRONG.
-- It allows the stream `never` to contain EVERYTHING!
-- I'm confident this can be proven using pfix (see the example file).
-- So what I really need is a way to move back down to a clockless world.
-- I should email Bob Atkey.
never : ∀{A} → Stream A
never = fix later
∈-never : ∀{a : A} → a ∈ never
∈-never = ∈later (λ t → {!!})
-- Completeness of union: if it's in as or bs, it's in (union as bs).
∈union₁ : ∀{a : A} {as bs} → (a ∈ as) → a ∈ union as bs
∈union₂ : ∀{b : A} as → ∀{bs} → (b ∈ bs) → b ∈ union as bs
∈union₁ ∈car = ∈car
∈union₁ (∈cdr a∈as) = ∈cdr (∈union₂ _ a∈as)
∈union₁ (∈later ▸a∈as) = ∈later λ t → ∈union₂ _ (▸a∈as t)
∈union₂ nil b∈bs = b∈bs
∈union₂ (cons x as) b∈bs = ∈cdr (∈union₁ b∈bs)
∈union₂ (later x) b∈bs = ∈later λ t → ∈union₁ b∈bs
-- Soundness of union: if it's in (union as bs), it's in as or bs.
-- Except, shit! We can't decide this up front!
-- We will need a number of steps depending on the size of the proof of containment!
union∋ : ∀{a : A} as bs → a ∈ union as bs → (a ∈ as) ⊎ (a ∈ bs)
union∋ nil bs a∈abs = in₂ a∈abs
union∋ (cons a as) bs ∈car = in₁ ∈car
union∋ (cons a as) bs (∈cdr a∈abs) with union∋ bs as a∈abs
... | in₁ x = in₂ x
... | in₂ x = in₁ (∈cdr x)
union∋ (later as) bs (∈later a∈abs) = {!!} -- THE PROBLEM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment