module Cat.Solver where
private variable
: Level o h
Solver for categoriesπ
This module is split pretty cleanly into two halves: the first half implements an algorithm for reducing, in a systematic way, problems involving associativity and identity of composition in a precategory. The latter half, significantly more cursed, uses this infrastructure to automatically solve equality goals of this form.
With a precategory in hand, we start by defining a language of composition.
module NbE (Cat : Precategory o h) where
open Precategory Cat
private variable
: Ob A B C
data Expr : Ob β Ob β Type (o β h) where
: Expr A A
`id _`β_ : Expr B C β Expr A B β Expr A C
_β : Hom A B β Expr A B
infixr 40 _`β_
infix 50 _β
A term of type Expr
represents,
in a symbolic way, a composite of morphisms in our category
What this means is that, while
is some unknowable inhabitant of Hom
,
represents an inhabitant of Hom
which is known to be a composition of (the trees that
represent)
and
We can now define βtwoβ ways of computing the morphism that an Expr
represents. The first is a
straightforward embed
ding:
: Expr A B β Hom A B
embed = id
embed `id (f β) = f
embed (f `β g) = embed f β embed g embed
instance
: β¦β§-notation (Expr A B)
β¦β§-Expr = brackets _ embed β¦β§-Expr
The second computation is a bit less obvious. If youβre a programmer, it should be familiar under the name βcontinuation passing styleβ. Categorically, it can be seen as embedding into the presheaf category of In either case, the difference is that instead of computing a single morphism, we compute a transformation of hom-spaces:
: Expr B C β Hom A B β Hom A C
eval = f
eval `id f (f β) g = f β g
eval (f `β g) h = eval f (eval g h)
eval
: Expr A B β Hom A B
nf = eval e id nf e
Working this out in a back-of-the-envelope calculation, one sees that
eval f id
should compute the same morphism as
embed f
. Indeed, thatβs the case! Since embed
is the βintended semanticsβ, and
eval
is an βoptimised evaluatorβ,
we call this result soundness. We can prove it by induction on
the expression, by first generalising over id
:
: (e : Expr B C) (f : Hom A B) β eval e f β‘ β¦ e β§ β f
eval-sound-k = sym (idl _) -- f β‘ id β f
eval-sound-k `id f (f `β g) h =
eval-sound-k (eval g h) β‘β¨ eval-sound-k f _ β©
eval f (embed f β_) (eval-sound-k g _) β©
embed f β eval g h β‘β¨ ap _ _ _ β©
embed f β embed g β h β‘β¨ assoc (embed f β embed g) β h β
(x β) f = refl -- x β f β‘ x β f
eval-sound-k
: (e : Expr A B) β nf e β‘ β¦ e β§
eval-sound = eval-sound-k e id β idr _ eval-sound e
We now have a general theorem for solving associativity and identity problems! If two expressions compute the same transformation of hom-sets, then they represent the same morphism.
abstract
: (f g : Expr A B) β nf f β‘ nf g β β¦ f β§ β‘ β¦ g β§
solve = sym (eval-sound f) ββ p ββ (eval-sound g)
solve f g p
: (f g : Expr A B) β (p : nf f β‘ nf g) β Square (eval-sound f) p (solve f g p) (eval-sound g)
solve-filler = ββ-filler (sym (eval-sound f)) p (eval-sound g) j i solve-filler f g p j i
The cursed partπ
module Reflection where
pattern category-args xs =
_ hmβ· _ hmβ· _ vβ· xs
pattern βidβ =
(quote Precategory.id) (category-args (_ hβ· []))
def
pattern βββ f g =
(quote Precategory._β_) (category-args (_ hβ· _ hβ· _ hβ· f vβ· g vβ· []))
def
: Term β List (Arg Term) β List (Arg Term)
mk-category-args = unknown hβ· unknown hβ· cat vβ· xs
mk-category-args cat xs
: Term β Term β Term β Term
βsolveβ = def (quote NbE.solve) (mk-category-args cat $ infer-hidden 2 $ lhs vβ· rhs vβ· def (quote refl) [] vβ· [])
βsolveβ cat lhs rhs
: Term β Term β Term
βnfβ = def (quote NbE.nf) (mk-category-args cat $ infer-hidden 2 $ e vβ· [])
βnfβ cat e
: Term β Term
build-expr = con (quote NbE.`id) []
build-expr βidβ (βββ f g) = con (quote NbE._`β_) (build-expr f vβ· build-expr g vβ· [] )
build-expr = con (quote NbE._β) (f vβ· [])
build-expr f
: List Name
dont-reduce = quote Precategory.id β· quote Precategory._β_ β· []
dont-reduce
: Term β SimpleSolver
cat-solver .SimpleSolver.dont-reduce = dont-reduce
cat-solver cat .SimpleSolver.build-expr tm = pure $ build-expr tm
cat-solver cat .SimpleSolver.invoke-solver = βsolveβ cat
cat-solver cat .SimpleSolver.invoke-normaliser = βnfβ cat
cat-solver cat
: Term β Term β Term β TC β€
repr-macro _ =
repr-macro cat f (cat-solver cat) f
mk-simple-repr
: Term β Term β Term β TC β€
simplify-macro =
simplify-macro cat f hole (cat-solver cat) f hole
mk-simple-normalise
macro
: Term β Term β Term β TC β€
repr-cat! = Reflection.repr-macro cat f
repr-cat! cat f
: Term β Term β Term β TC β€
simpl-cat! = Reflection.simplify-macro cat f simpl-cat! cat f
module _ {o h} (C : Precategory o h) {x y : β C β} {h1 h2 : C .Precategory.Hom x y} where
open Reflection
private
: Term β TC β€
cat-worker = withReconstructed true $ withNormalisation true $ withReduceDefs (false , dont-reduce) do
cat-worker goal
`h1 β wait-for-type =<< quoteTC h1
`h2 β quoteTC h2
`C β quoteTC C
(βsolveβ `C (build-expr `h1) (build-expr `h2))
unify goal
: {@(tactic cat-worker) p : h1 β‘ h2} β h1 β‘ h2
cat-wrapper {p = p} = p
cat-wrapper
macro
: Term β Term β TC β€
cat! = flip unify (def (quote cat-wrapper) (c vβ· [])) cat! c
Demoπ
As a quick demonstration (and sanity check/future proofing/integration testing/what have you):
module _ (C : Precategory o h) where private
module C = Precategory C
variable
: C.Ob
A B : C.Hom A B
a b c d
: a C.β (b C.β (c C.β C.id) C.β C.id C.β (d C.β C.id))
test .β b C.β c C.β d
β‘ a C= cat! C test