diff --git a/src/Cat/Functor/Adjoint/Solver.lagda.md b/src/Cat/Functor/Adjoint/Solver.lagda.md new file mode 100644 index 000000000..3c99938dc --- /dev/null +++ b/src/Cat/Functor/Adjoint/Solver.lagda.md @@ -0,0 +1,902 @@ + + +```agda +module Cat.Functor.Adjoint.Solver where +``` + +# A solver for the theory of Adjoint Functors + +Like most of our solvers, this module is split into two distinct halves. +The first implements an algorithm for normalizing expressions involving +an [adjunction] between two categories. The latter half consists of the +usual reflection machinery required to convert Agda expressions into +our internal expression type. + +[adjunction]: Cat.Functor.Adjoint.html + +```agda +module NbE + {oc ℓc od ℓd} + {C : Precategory oc ℓc} {D : Precategory od ℓd} + {L : Functor C D} {R : Functor D C} + (adj : L ⊣ R) + where +``` + + + +## The Normalization Algorithm + +Before diving into specifics, we should sketch out the normalization +algorithm. As we are normalizing expressions in categories, the natural +notion of value will be a stack of morphisms. This means that the only +real eliminator is composition, which will let us eliminate adjacent +morphisms. With this in mind, let's examine the `zig`{.Agda} and `zag`{.Agda} +equations, which we reproduce below + +$$ +\varepsilon \circ L \eta = id \\ +R \varepsilon \circ \eta = id +$$ + + +Note that $\varepsilon$ only ever occurs on the left hand side of these +equations; this means that our evaluation strategy should try to keep +counits at the bottom of the stacks, and units at the top. This will +be done by repeatedly applying naturality when composing two values +to "float" units upwards, and "sink" counits downwards. + +We also need to handle applications of $L$ and $R$ to morphisms; this +is done by expanding $L (f \circ g)$ to $L f \circ L g$, which allows +for the aforementioned steps interact with naturality better. + +## Expressions + +With that sketch out of the way, we begin by defining syntax for objects +in the two categories, as well as their semantics. This is done for a +rather mundane reason: we will need to index the syntax of morphisms by +objects, and the unifier will get very confused when trying to unify the +actions of $L$ and $R$ on objects if we don't encode them as a datatype. + +```agda + data ‶C‶ : Typeω + data ‶D‶ : Typeω + + data ‶C‶ where + ‶_‶ : C.Ob → ‶C‶ + ‶R‶ : ‶D‶ → ‶C‶ + + data ‶D‶ where + ‶_‶ : D.Ob → ‶D‶ + ‶L‶ : ‶C‶ → ‶D‶ + + C-ob : ‶C‶ → C.Ob + D-ob : ‶D‶ → D.Ob + + C-ob ‶ x ‶ = x + C-ob (‶R‶ x) = R.₀ (D-ob x) + + D-ob ‶ x ‶ = x + D-ob (‶L‶ x) = L.₀ (C-ob x) + + instance + ‶C‶-⟦-⟧ : ⟦-⟧-notation ‶C‶ + ‶C‶-⟦-⟧ = has-⟦-⟧ C.Ob C-ob + + ‶D‶-⟦-⟧ : ⟦-⟧-notation ‶D‶ + ‶D‶-⟦-⟧ = has-⟦-⟧ D.Ob D-ob +``` + +With that technical hiccup out of the way, we can proceed to define +syntax for morphisms in $\cC$ and $\cD$, respectively. + +```agda + data CExpr : ‶C‶ → ‶C‶ → Typeω + data DExpr : ‶D‶ → ‶D‶ → Typeω + + data CExpr where + ‶id‶ : ∀ {x} → CExpr x x + _‶∘‶_ : ∀ {x y z} → CExpr y z → CExpr x y → CExpr x z + ‶R‶ : ∀ {x y} → DExpr x y → CExpr (‶R‶ x) (‶R‶ y) + ‶η‶ : ∀ x → CExpr x (‶R‶ (‶L‶ x)) + ‶_‶ : ∀ {x y} → C.Hom ⟦ x ⟧ ⟦ y ⟧ → CExpr x y + + data DExpr where + ‶id‶ : ∀ {x} → DExpr x x + _‶∘‶_ : ∀ {x y z} → DExpr y z → DExpr x y → DExpr x z + ‶L‶ : ∀ {x y} → CExpr x y → DExpr (‶L‶ x) (‶L‶ y) + ‶ε‶ : ∀ x → DExpr (‶L‶ (‶R‶ x)) x + ‶_‶ : ∀ {x y} → D.Hom ⟦ x ⟧ ⟦ y ⟧ → DExpr x y +``` + +Next, we define the interpretation of syntax back into morphisms. This +will be used in the statement of the crucial soundness theorem later on. + +```agda + C-hom : ∀ {x y} → CExpr x y → C.Hom ⟦ x ⟧ ⟦ y ⟧ + D-hom : ∀ {x y} → DExpr x y → D.Hom ⟦ x ⟧ ⟦ y ⟧ + + C-hom ‶id‶ = C.id + C-hom (f ‶∘‶ g) = C-hom f C.∘ C-hom g + C-hom (‶R‶ f) = R.₁ (D-hom f) + C-hom (‶η‶ x) = unit.η (C-ob x) + C-hom ‶ f ‶ = f + + D-hom ‶id‶ = D.id + D-hom (f ‶∘‶ g) = D-hom f D.∘ D-hom g + D-hom (‶L‶ f) = L.₁ (C-hom f) + D-hom (‶ε‶ x) = counit.ε (D-ob x) + D-hom ‶ f ‶ = f + + instance + CExpr-⟦-⟧ : ∀ {x y} → ⟦-⟧-notation (CExpr x y) + CExpr-⟦-⟧ {x} {y} = has-⟦-⟧ (C.Hom ⟦ x ⟧ ⟦ y ⟧) C-hom + + DExpr-⟦-⟧ : ∀ {x y} → ⟦-⟧-notation (DExpr x y) + DExpr-⟦-⟧ {x} {y} = has-⟦-⟧ (D.Hom ⟦ x ⟧ ⟦ y ⟧) D-hom +``` + +## Values + +We shall define our values to be lists of morphisms, (co)units, or +functor applications. We call the elements of the list *frames*, as +the lists are treated like stacks. + +```agda + data CFrame : ‶C‶ → ‶C‶ → Typeω + data DFrame : ‶D‶ → ‶D‶ → Typeω + + data CFrame where + khom : ∀ {x y} → C.Hom ⟦ x ⟧ ⟦ y ⟧ → CFrame x y + krmap : ∀ {x y} → DFrame x y → CFrame (‶R‶ x) (‶R‶ y) + kunit : ∀ x → CFrame x (‶R‶ (‶L‶ x)) + + data DFrame where + khom : ∀ {x y} → D.Hom ⟦ x ⟧ ⟦ y ⟧ → DFrame x y + klmap : ∀ {x y} → CFrame x y → DFrame (‶L‶ x) (‶L‶ y) + kcounit : ∀ x → DFrame (‶L‶ (‶R‶ x)) x +``` + +As mentioned earlier, values are stacks of frames. Note that we opt to +make the type of $\cD$-values a left-associated list; this is done to +make sinking counit frames to the bottom of the stack easier. + +```agda + data CValue : ‶C‶ → ‶C‶ → Typeω + data DValue : ‶D‶ → ‶D‶ → Typeω + + data CValue where + [] : ∀ {x} → CValue x x + _∷_ : ∀ {x y z} → CFrame y z → CValue x y → CValue x z + + data DValue where + [] : ∀ {x} → DValue x x + _∷r_ : ∀ {x y z} → DValue y z → DFrame x y → DValue x z + + infixr 20 _∷_ + infixl 20 _∷r_ +``` + +Semantics of frames and values are what one would expect. + +```agda + C-frame : ∀ {x y} → CFrame x y → C.Hom ⟦ x ⟧ ⟦ y ⟧ + D-frame : ∀ {x y} → DFrame x y → D.Hom ⟦ x ⟧ ⟦ y ⟧ + + C-frame (khom f) = f + C-frame (krmap k) = R.F₁ (D-frame k) + C-frame (kunit x) = unit.η ⟦ x ⟧ + + D-frame (khom f) = f + D-frame (klmap k) = L.F₁ (C-frame k) + D-frame (kcounit x) = counit.ε ⟦ x ⟧ + + instance + CFrame-⟦-⟧ : ∀ {x y} → ⟦-⟧-notation (CFrame x y) + CFrame-⟦-⟧ {x} {y} = has-⟦-⟧ (C.Hom ⟦ x ⟧ ⟦ y ⟧) C-frame + + DFrame-⟦-⟧ : ∀ {x y} → ⟦-⟧-notation (DFrame x y) + DFrame-⟦-⟧ {x} {y} = has-⟦-⟧ (D.Hom ⟦ x ⟧ ⟦ y ⟧) D-frame + + C-value : ∀ {x y} → CValue x y → C.Hom ⟦ x ⟧ ⟦ y ⟧ + C-value [] = C.id + C-value (k ∷ v) = ⟦ k ⟧ C.∘ C-value v + + D-value : ∀ {x y} → DValue x y → D.Hom ⟦ x ⟧ ⟦ y ⟧ + D-value [] = D.id + D-value (v ∷r k) = D-value v D.∘ ⟦ k ⟧ + + instance + CValue-⟦-⟧ : ∀ {x y} → ⟦-⟧-notation (CValue x y) + CValue-⟦-⟧ {x} {y} = has-⟦-⟧ (C.Hom ⟦ x ⟧ ⟦ y ⟧) C-value + + DValue-⟦-⟧ : ∀ {x y} → ⟦-⟧-notation (DValue x y) + DValue-⟦-⟧ {x} {y} = has-⟦-⟧ (D.Hom ⟦ x ⟧ ⟦ y ⟧) D-value +``` + +## Evaluation + +We begin by defining some small helper functions for manipulating values. +The first pair of helpers concatenates two values together without +performing any sort of simplification. + +```agda + _++c_ : ∀ {x y z} → CValue y z → CValue x y → CValue x z + [] ++c v2 = v2 + (k ∷ v1) ++c v2 = k ∷ (v1 ++c v2) + + _++d_ : ∀ {x y z} → DValue y z → DValue x y → DValue x z + v1 ++d [] = v1 + v1 ++d (v2 ∷r k) = (v1 ++d v2) ∷r k +``` + +Next, a pair of functions for composing functor applications of the form +$R f \circ g$ and $f \circ L g$, resp. These also perform expansion of the +functor application: for instance, $R (f \circ g) \circ h$ gets expanded to +$R f \circ R g \circ h$. + +```agda + do-vrmap : ∀ {x y z} → DValue y z → CValue x (‶R‶ y) → CValue x (‶R‶ z) + do-vrmap [] v2 = v2 + do-vrmap (v1 ∷r k) v2 = do-vrmap v1 (krmap k ∷ v2) + + do-vlmap : ∀ {x y z} → DValue (‶L‶ y) z → CValue x y → DValue (‶L‶ x) z + do-vlmap v1 [] = v1 + do-vlmap v1 (k ∷ v2) = do-vlmap (v1 ∷r klmap k) v2 +``` + +Now, for the meat of the solver. `enact-claws`{.Agda} and `enact-dlaws`{.Agda} +both take in a frame and a non-empty stack, and push that frame to the +top/bottom of the stack, respectively. While doing so, they perform the +following simplifications: +* They call `do-vrmap`{.Agda} and `do-vlmap`{.Agda} to handle functor + expansion. +* They apply naturality to float `kunit`{.Agda} up the stack and sink + `kcounit`{.Agda} down the stack. +* They enact the `zig`{.Agda} and `zag`{.Agda} equations. + +`push-cframe`{.Agda} and `push-dframe`{.Agda} are small helpers that +take in a frame and a potentially empty stack, and hand off the +stack to `enact-claws`{.Agda} and `enact-dlaws`{.Agda} if it is non-empty. + +```agda + enact-claws : ∀ {w x y z} → CFrame y z → CFrame x y → CValue w x → CValue w z + enact-dlaws : ∀ {w x y z} → DValue y z → DFrame x y → DFrame w x → DValue w z + + push-cframe : ∀ {x y z} → CFrame y z → CValue x y → CValue x z + push-dframe : ∀ {x y z} → DValue y z → DFrame x y → DValue x z + + push-cframe k [] = k ∷ [] + push-cframe k (k' ∷ v) = enact-claws k k' v + + push-dframe [] k = [] ∷r k + push-dframe (v ∷r k') k = enact-dlaws v k' k +``` + +Let's step through `enact-claws`{.Agda} on a case-by-case basis, as it is +quite delicate. We start out slow: if we are composing an unknown morphism +$f$ to a stack, we have no hope of simplifying, so we simply stick it on top. + +``` + enact-claws (khom f) k' v = khom f ∷ k' ∷ v +``` + +The same goes for composing a functor application when the head of the +stack is an unknown morphism. + +```agda + enact-claws (krmap k) (khom f) v = krmap k ∷ khom f ∷ v +``` + +If we have 2 adjacent functor applications, we perform simplification +on their composite, and then expand the result via `do-vrmap`. Note that +we use `++c`{.Agda} to concatenate the stacks, so no further +simplification is performed. This is done to break infinite loops; for +instance, consider what happens when we push a `krmap (khom f)` to a +stack whose head is `krmap (khom g)`. + +```agda + enact-claws (krmap k) (krmap k') v = do-vrmap (enact-dlaws [] k k') [] ++c v +``` + +If we are pushing $R f$ for an unknown morphism $f$, and the head of the +stack is $\eta$, then no simplification is possible. + +```agda + enact-claws (krmap (khom f)) (kunit _) v = krmap (khom f) ∷ kunit _ ∷ v +``` + +This case enacts naturality, floating the $\eta$ upwards so that it can +be eliminated by either `zig`{.Agda} or `zag`{.Agda}. We also need to +keep pushing the `k` frame downwards, so we invoke `push-cframe`{.Agda} +to keep going. + +``` + enact-claws (krmap (klmap k)) (kunit _) v = kunit _ ∷ push-cframe k v +``` + +Speaking of which, when we push a $R \varepsilon$ to a $\eta$, we can +enact the `zag`{.Agda} equation! + +```agda + enact-claws (krmap (kcounit _)) (kunit _) v = v + +``` + +Finally, we want to keep $\eta$ on the top of the stack, so pushing +an $\eta$ leaves it on top. + +```agda + enact-claws (kunit _) k' v = kunit _ ∷ k' ∷ v +``` + +`enact-dlaws`{.Agda} is entirely dual to `enact-claws`, so we do not +dwell on it too deeply. + +```agda + enact-dlaws v k (khom f) = v ∷r k ∷r khom f + enact-dlaws v (khom f) (klmap k) = v ∷r khom f ∷r klmap k + enact-dlaws v (klmap k') (klmap k) = v ++d do-vlmap [] (enact-claws k' k []) + enact-dlaws v (kcounit _) (klmap (khom f)) = v ∷r kcounit _ ∷r klmap (khom f) + enact-dlaws v (kcounit _) (klmap (krmap k)) = push-dframe v k ∷r kcounit _ + enact-dlaws v (kcounit _) (klmap (kunit _)) = v + enact-dlaws v k' (kcounit _) = v ∷r k' ∷r kcounit _ +``` + +We can then leverage `enact-claws`{.Agda} and `enact-dlaws{.Agda}` to +define composition of values, which repeatedly pushes frames from +one value onto the others, performing simplification each time. + +```agda + do-cvcomp : ∀ {x y z} → CValue y z → CValue x y → CValue x z + do-cvcomp [] v2 = v2 + do-cvcomp (k ∷ v1) v2 = push-cframe k (do-cvcomp v1 v2) + + do-dvcomp : ∀ {x y z} → DValue y z → DValue x y → DValue x z + do-dvcomp v1 [] = v1 + do-dvcomp v1 (v2 ∷r k) = push-dframe (do-dvcomp v1 v2) k +``` + +Evaluation then interprets syntax into the corresponding operations +on values. + +```agda + C-eval : ∀ {x y} → CExpr x y → CValue x y + D-eval : ∀ {x y} → DExpr x y → DValue x y + + C-eval ‶id‶ = [] + C-eval (f ‶∘‶ g) = do-cvcomp (C-eval f) (C-eval g) + C-eval (‶R‶ f) = do-vrmap (D-eval f) [] + C-eval (‶η‶ x) = kunit x ∷ [] + C-eval ‶ f ‶ = khom f ∷ [] + + D-eval ‶id‶ = [] + D-eval (f ‶∘‶ g) = do-dvcomp (D-eval f) (D-eval g) + D-eval (‶L‶ f) = do-vlmap [] (C-eval f) + D-eval (‶ε‶ x) = [] ∷r kcounit x + D-eval ‶ f ‶ = [] ∷r khom f +``` + +## Soundness + +We begin by proving soundness lemmas for our helper functions. + +```agda + vrmap-sound + : ∀ {x y z} (v1 : DValue y z) (v2 : CValue x (‶R‶ y)) + → ⟦ do-vrmap v1 v2 ⟧ ≡ R.₁ ⟦ v1 ⟧ C.∘ ⟦ v2 ⟧ + vrmap-sound [] v2 = C.introl R.F-id + vrmap-sound (v1 ∷r k) v2 = + ⟦ do-vrmap v1 (krmap k ∷ v2) ⟧ ≡⟨ vrmap-sound v1 (krmap k ∷ v2) ⟩ + R.₁ ⟦ v1 ⟧ C.∘ R.₁ ⟦ k ⟧ C.∘ ⟦ v2 ⟧ ≡⟨ C.pulll (sym (R.F-∘ _ _)) ⟩ + R.₁ (⟦ v1 ⟧ D.∘ ⟦ k ⟧) C.∘ ⟦ v2 ⟧ ∎ + + vlmap-sound + : ∀ {x y z} (v1 : DValue (‶L‶ y) z) (v2 : CValue x y) + → ⟦ do-vlmap v1 v2 ⟧ ≡ ⟦ v1 ⟧ D.∘ L.₁ ⟦ v2 ⟧ + vlmap-sound v1 [] = D.intror L.F-id + vlmap-sound v1 (k ∷ v2) = + ⟦ do-vlmap (v1 ∷r klmap k) v2 ⟧ ≡⟨ vlmap-sound (v1 ∷r klmap k) v2 ⟩ + (⟦ v1 ⟧ D.∘ L.₁ ⟦ k ⟧) D.∘ L.₁ ⟦ v2 ⟧ ≡⟨ D.pullr (sym (L.F-∘ _ _)) ⟩ + ⟦ v1 ⟧ D.∘ L.₁ (⟦ k ⟧ C.∘ ⟦ v2 ⟧) ∎ + + cvconcat-sound + : ∀ {x y z} + → (v1 : CValue y z) (v2 : CValue x y) + → ⟦ v1 ++c v2 ⟧ ≡ ⟦ v1 ⟧ C.∘ ⟦ v2 ⟧ + cvconcat-sound [] v2 = sym (C.idl _) + cvconcat-sound (k ∷ v1) v2 = C.pushr (cvconcat-sound v1 v2) + + dvconcat-sound + : ∀ {x y z} + → (v1 : DValue y z) (v2 : DValue x y) + → ⟦ v1 ++d v2 ⟧ ≡ ⟦ v1 ⟧ D.∘ ⟦ v2 ⟧ + dvconcat-sound v1 [] = sym (D.idr _) + dvconcat-sound v1 (v2 ∷r k) = D.pushl (dvconcat-sound v1 v2) +``` + +Now, time for the crux: proving soundness for `enact-claws`{.Agda} and +`enact-dlaws`{.Agda}. + +```agda + push-cframe-sound + : ∀ {x y z} → (k : CFrame y z) (v : CValue x y) + → ⟦ push-cframe k v ⟧ ≡ ⟦ k ⟧ C.∘ ⟦ v ⟧ + push-dframe-sound + : ∀ {x y z} → (v : DValue y z) (k : DFrame x y) + → ⟦ push-dframe v k ⟧ ≡ ⟦ v ⟧ D.∘ ⟦ k ⟧ + + enact-claws-sound + : ∀ {w x y z} (k1 : CFrame y z) (k2 : CFrame x y) (v : CValue w x) + → ⟦ enact-claws k1 k2 v ⟧ ≡ ⟦ k1 ⟧ C.∘ ⟦ k2 ⟧ C.∘ ⟦ v ⟧ + enact-dlaws-sound + : ∀ {w x y z} (v : DValue y z) (k1 : DFrame x y) (k2 : DFrame w x) + → ⟦ enact-dlaws v k1 k2 ⟧ ≡ (⟦ v ⟧ D.∘ ⟦ k1 ⟧) D.∘ ⟦ k2 ⟧ +``` + +We start off easy: `push-cframe`{.Agda} and `push-dframe`{.Agda} are +obviously sound if `enact-claws`{.Agda} and `enact-dlaws`{.Agda} are! + +```agda + push-cframe-sound k [] = refl + push-cframe-sound k (k' ∷ v) = enact-claws-sound k k' v + + push-dframe-sound [] k = refl + push-dframe-sound (v ∷r k') k = enact-dlaws-sound v k' k +``` + +Now, time for `enact-claws`{.Agda}. Let's step through each interesting +case, and omit the ones where no simplification occurs + + + +The `krmap`/`krmap` case is a bit of a pain, but this is mostly due +to the amount of lemmas we need to invoke. + +```agda + enact-claws-sound (krmap k1) (krmap k2) v = + ⟦ do-vrmap (enact-dlaws [] k1 k2) [] ++c v ⟧ ≡⟨ cvconcat-sound (do-vrmap (enact-dlaws [] k1 k2) []) v ⟩ + ⟦ do-vrmap (enact-dlaws [] k1 k2) [] ⟧ C.∘ ⟦ v ⟧ ≡⟨ vrmap-sound (enact-dlaws [] k1 k2) [] C.⟩∘⟨refl ⟩ + (R.₁ ⟦ enact-dlaws [] k1 k2 ⟧ C.∘ C.id) C.∘ ⟦ v ⟧ ≡⟨ (C.idr _) C.⟩∘⟨refl ⟩ + R.₁ ⟦ enact-dlaws [] k1 k2 ⟧ C.∘ ⟦ v ⟧ ≡⟨ R.pushl (enact-dlaws-sound [] k1 k2) ⟩ + R.₁ ⌜ D.id D.∘ ⟦ k1 ⟧ ⌝ C.∘ R.₁ ⟦ k2 ⟧ C.∘ ⟦ v ⟧ ≡⟨ ap! (D.idl _) ⟩ + R.₁ ⟦ k1 ⟧ C.∘ R.₁ ⟦ k2 ⟧ C.∘ ⟦ v ⟧ ∎ +``` + + + +When we enact naturality, we appeal to unsurprisingly appeal to naturality. + +```agda + enact-claws-sound (krmap (klmap k1)) (kunit _) v = + unit.η _ C.∘ ⟦ push-cframe k1 v ⟧ ≡⟨ C.refl⟩∘⟨ push-cframe-sound k1 v ⟩ + unit.η _ C.∘ ⟦ k1 ⟧ C.∘ ⟦ v ⟧ ≡⟨ C.extendl (unit.is-natural _ _ _) ⟩ + R.₁ (L.₁ ⟦ k1 ⟧) C.∘ unit.η _ C.∘ ⟦ v ⟧ ∎ +``` + +Enacting `zag`{.Agda} is thankfully very easy. + +```agda + enact-claws-sound (krmap (kcounit _)) (kunit _) v = + ⟦ v ⟧ ≡⟨ C.insertl zag ⟩ + R.₁ (counit.ε _) C.∘ unit.η _ C.∘ ⟦ v ⟧ ∎ +``` + + + +
+The soundess proof for `enact-dlaws-sound`{.Agda} is dual, so +we omit it. + + +```agda + enact-dlaws-sound v k1 (khom f) = refl + enact-dlaws-sound v (khom f) (klmap k2) = refl + enact-dlaws-sound v (klmap k1) (klmap k2) = + ⟦ v ++d do-vlmap [] (enact-claws k1 k2 []) ⟧ ≡⟨ dvconcat-sound v (do-vlmap [] (enact-claws k1 k2 [])) ⟩ + ⟦ v ⟧ D.∘ ⟦ do-vlmap [] (enact-claws k1 k2 []) ⟧ ≡⟨ D.refl⟩∘⟨ vlmap-sound [] (enact-claws k1 k2 []) ⟩ + ⟦ v ⟧ D.∘ D.id D.∘ L.₁ ⟦ enact-claws k1 k2 [] ⟧ ≡⟨ D.refl⟩∘⟨ D.idl _ ⟩ + ⟦ v ⟧ D.∘ L.₁ ⟦ enact-claws k1 k2 [] ⟧ ≡⟨ L.pushr (enact-claws-sound k1 k2 []) ⟩ + (⟦ v ⟧ D.∘ L.₁ ⟦ k1 ⟧) D.∘ L.₁ ⌜ ⟦ k2 ⟧ C.∘ C.id ⌝ ≡⟨ ap! (C.idr _) ⟩ + (⟦ v ⟧ D.∘ L.₁ ⟦ k1 ⟧) D.∘ L.₁ ⟦ k2 ⟧ ∎ + enact-dlaws-sound v (kcounit _) (klmap (khom f)) = refl + enact-dlaws-sound v (kcounit _) (klmap (krmap k2)) = + ⟦ push-dframe v k2 ⟧ D.∘ counit.ε _ ≡⟨ push-dframe-sound v k2 D.⟩∘⟨refl ⟩ + (⟦ v ⟧ D.∘ ⟦ k2 ⟧) D.∘ counit.ε _ ≡⟨ D.extendr (sym (counit.is-natural _ _ _)) ⟩ + (⟦ v ⟧ D.∘ counit.ε _) D.∘ L.₁ (R.₁ ⟦ k2 ⟧) ∎ + enact-dlaws-sound v (kcounit _) (klmap (kunit _)) = + ⟦ v ⟧ ≡⟨ D.insertr zig ⟩ + ((⟦ v ⟧ D.∘ counit.ε _) D.∘ L.₁ (unit.η _)) ∎ + enact-dlaws-sound v k1 (kcounit _) = refl +``` +
+ +Winding down, soundness of composition follows from soundness of enacting +the laws. + +```agda + cvcomp-sound + : ∀ {x y z} (v1 : CValue y z) (v2 : CValue x y) + → ⟦ do-cvcomp v1 v2 ⟧ ≡ ⟦ v1 ⟧ C.∘ ⟦ v2 ⟧ + cvcomp-sound [] v2 = sym (C.idl _) + cvcomp-sound (k ∷ v1) v2 = + ⟦ push-cframe k (do-cvcomp v1 v2) ⟧ ≡⟨ push-cframe-sound k (do-cvcomp v1 v2) ⟩ + ⟦ k ⟧ C.∘ ⟦ do-cvcomp v1 v2 ⟧ ≡⟨ C.pushr (cvcomp-sound v1 v2) ⟩ + (⟦ k ⟧ C.∘ ⟦ v1 ⟧) C.∘ ⟦ v2 ⟧ ∎ + + dvcomp-sound + : ∀ {x y z} (v1 : DValue y z) (v2 : DValue x y) + → ⟦ do-dvcomp v1 v2 ⟧ ≡ ⟦ v1 ⟧ D.∘ ⟦ v2 ⟧ + dvcomp-sound v1 [] = sym (D.idr _) + dvcomp-sound v1 (v2 ∷r k) = + ⟦ push-dframe (do-dvcomp v1 v2) k ⟧ ≡⟨ push-dframe-sound (do-dvcomp v1 v2) k ⟩ + (⟦ do-dvcomp v1 v2 ⟧ D.∘ ⟦ k ⟧) ≡⟨ D.pushl (dvcomp-sound v1 v2) ⟩ + ⟦ v1 ⟧ D.∘ ⟦ v2 ⟧ D.∘ ⟦ k ⟧ ∎ +``` + +Finally, soundness of evaluation is one big case bash. + +```agda + C-eval-sound : ∀ {x y} (e : CExpr x y) → ⟦ C-eval e ⟧ ≡ ⟦ e ⟧ + D-eval-sound : ∀ {x y} (e : DExpr x y) → ⟦ D-eval e ⟧ ≡ ⟦ e ⟧ + + C-eval-sound ‶id‶ = refl + C-eval-sound (e1 ‶∘‶ e2) = + ⟦ do-cvcomp (C-eval e1) (C-eval e2) ⟧ ≡⟨ cvcomp-sound (C-eval e1) (C-eval e2) ⟩ + (⟦ C-eval e1 ⟧ C.∘ ⟦ C-eval e2 ⟧) ≡⟨ ap₂ C._∘_ (C-eval-sound e1) (C-eval-sound e2) ⟩ + ⟦ e1 ⟧ C.∘ ⟦ e2 ⟧ ∎ + C-eval-sound (‶R‶ e) = + ⟦ do-vrmap (D-eval e) [] ⟧ ≡⟨ vrmap-sound (D-eval e) [] ⟩ + R.₁ ⟦ D-eval e ⟧ C.∘ C.id ≡⟨ C.idr _ ⟩ + R.₁ ⟦ D-eval e ⟧ ≡⟨ ap R.₁ (D-eval-sound e) ⟩ + R.₁ ⟦ e ⟧ ∎ + C-eval-sound (‶η‶ _) = C.idr _ + C-eval-sound ‶ x ‶ = C.idr _ + + D-eval-sound ‶id‶ = refl + D-eval-sound (e1 ‶∘‶ e2) = + ⟦ do-dvcomp (D-eval e1) (D-eval e2) ⟧ ≡⟨ dvcomp-sound (D-eval e1) (D-eval e2) ⟩ + (⟦ D-eval e1 ⟧ D.∘ ⟦ D-eval e2 ⟧) ≡⟨ ap₂ D._∘_ (D-eval-sound e1) (D-eval-sound e2) ⟩ + ⟦ e1 ⟧ D.∘ ⟦ e2 ⟧ ∎ + D-eval-sound (‶L‶ e) = + ⟦ do-vlmap [] (C-eval e) ⟧ ≡⟨ vlmap-sound [] (C-eval e) ⟩ + D.id D.∘ L.₁ ⟦ C-eval e ⟧ ≡⟨ D.idl _ ⟩ + L.₁ ⟦ C-eval e ⟧ ≡⟨ ap L.₁ (C-eval-sound e) ⟩ + L.₁ ⟦ e ⟧ ∎ + D-eval-sound (‶ε‶ _) = D.idl _ + D-eval-sound ‶ x ‶ = D.idl _ +``` + +## Solver Interface + +In order to make the reflection easier later, we bundle up the soundness +proof. Marking this as abstract is *very important*. This prevents +agda from unfolding into an absolutely enormous proof when used as +a macro, which is critical for performance. + +```agda + abstract + C-solve : ∀ {x y} (e1 e2 : CExpr x y) → ⟦ C-eval e1 ⟧ ≡ ⟦ C-eval e2 ⟧ → ⟦ e1 ⟧ ≡ ⟦ e2 ⟧ + C-solve e1 e2 p = sym (C-eval-sound e1) ·· p ·· C-eval-sound e2 + + D-solve : ∀ {x y} (e1 e2 : DExpr x y) → ⟦ D-eval e1 ⟧ ≡ ⟦ D-eval e2 ⟧ → ⟦ e1 ⟧ ≡ ⟦ e2 ⟧ + D-solve e1 e2 p = sym (D-eval-sound e1) ·· p ·· D-eval-sound e2 +``` + +## Reflection + +This place is not a place of honor... +no highly esteemed deed is commemorated here... +nothing valued is here. +What is here was dangerous and repulsive to us. This message is a warning about danger. +The danger is in a particular location... +it increases towards the center... +the center of danger is here... +of a particular size and shape, and below us. + +The danger is still present, in your time, as it was in ours. +The danger is to the mind and it can kill. +The form of the danger is Agda reflection code. + +The danger is unleashed only if you substantially disturb this place physically. +This place is best shunned and left uninhabited. + +
+The danger is unleashed only if you substantially disturb this +place physically. This place is best shunned and left uninhabited. + +```agda +module Reflection where + + pattern category-args cat args = + _ hm∷ _ hm∷ cat v∷ args + + pattern functor-args functor args = + _ hm∷ _ hm∷ _ hm∷ _ hm∷ _ hm∷ _ hm∷ functor v∷ args + + pattern nat-trans-args nt args = + _ hm∷ _ hm∷ _ hm∷ _ hm∷ + _ hm∷ _ hm∷ + _ hm∷ _ hm∷ + nt v∷ args + + pattern adjoint-args adj args = + _ hm∷ _ hm∷ _ hm∷ _ hm∷ + _ hm∷ _ hm∷ + _ hm∷ _ hm∷ + adj v∷ args + + pattern “id” C = + def (quote Precategory.id) (category-args C (_ h∷ [])) + + + pattern “∘” C f g = + def (quote Precategory._∘_) (category-args C (_ h∷ _ h∷ _ h∷ f v∷ g v∷ [])) + + pattern “F₀” F x = + def (quote Functor.F₀) (functor-args F (x v∷ [])) + + pattern “F₁” F f = + def (quote Functor.F₁) (functor-args F (_ h∷ _ h∷ f v∷ [])) + + pattern “η” adj x = + def (quote _=>_.η) (nat-trans-args (def (quote _⊣_.unit) (adjoint-args adj [])) (x v∷ [])) + + pattern “ε” adj x = + def (quote _=>_.η) (nat-trans-args (def (quote _⊣_.counit) (adjoint-args adj [])) (x v∷ [])) + + pattern “Hom” C x y = + def (quote Precategory.Hom) (category-args C (x v∷ y v∷ [])) + + mk-nbe-args : Term → List (Arg Term) → List (Arg Term) + mk-nbe-args adj args = + unknown h∷ unknown h∷ unknown h∷ unknown h∷ + unknown h∷ unknown h∷ + unknown h∷ unknown h∷ + adj v∷ args + + unapply-hom : Term → TC (Maybe (Term × Term × Term)) + unapply-hom red@(“Hom” C x y) = do + pure (just (C , x , y)) + unapply-hom tm = reduce tm >>= λ where + tm@(meta _ _) → do + C ← new-meta (def (quote Precategory) (unknown v∷ unknown v∷ [])) + x ← new-meta (def (quote Precategory.Ob) (infer-hidden 2 (C v∷ []))) + y ← new-meta (def (quote Precategory.Ob) (infer-hidden 2 (C v∷ []))) + unify tm (def (quote Precategory.Hom) (infer-hidden 2 (C v∷ x v∷ y v∷ []))) + traverse wait-for-type (x ∷ y ∷ []) + pure (just (C , x , y)) + tm@(“Hom” C x y) → + pure (just (C , x , y)) + _ → returnTC nothing + + get-hom-boundary : Term → TC (Term × Term) + get-hom-boundary tm = unapply-hom tm >>= λ where + (just (_ , src , tgt)) → pure (src , tgt) + nothing → typeError "Couldn't get hom boundary." + + “C-solve” : Term → Term → Term → Term + “C-solve” adj lhs rhs = + def (quote NbE.C-solve) (mk-nbe-args adj $ infer-hidden 2 (lhs v∷ rhs v∷ “refl” v∷ [])) + + “D-solve” : Term → Term → Term → Term + “D-solve” adj lhs rhs = + def (quote NbE.D-solve) (mk-nbe-args adj $ infer-hidden 2 (lhs v∷ rhs v∷ “refl” v∷ [])) + + record Adj-terms : Type where + field + C : Term + D : Term + L : Term + R : Term + adjoint : Term + + open Adj-terms + + build-C-obj-expr : Adj-terms → Term → TC Term + build-D-obj-expr : Adj-terms → Term → TC Term + + build-C-obj-expr tms t@(“F₀” F x) = + catchTC + (do unify (tms .R) F + x ← build-D-obj-expr tms x + returnTC $ con (quote NbE.‶C‶.‶R‶) (x v∷ [])) + (returnTC $ con (quote NbE.‶C‶.‶_‶) (t v∷ [])) + build-C-obj-expr tms x = + returnTC $ con (quote NbE.‶C‶.‶_‶) (x v∷ []) + + build-D-obj-expr tms t@(“F₀” F x) = + catchTC + (do unify (tms .L) F + x ← build-C-obj-expr tms x + returnTC $ con (quote NbE.‶D‶.‶L‶) (x v∷ [])) + (returnTC $ con (quote NbE.‶D‶.‶_‶) (t v∷ [])) + build-D-obj-expr tms x = do + returnTC $ con (quote NbE.‶D‶.‶_‶) (x v∷ []) + + {-# TERMINATING #-} + build-C-expr : Adj-terms → Term → TC Term + build-D-expr : Adj-terms → Term → TC Term + + build-C-expr tms (“id” cat) = + returnTC $ con (quote NbE.CExpr.‶id‶) [] + build-C-expr tms (“∘” cat f g) = do + f ← build-C-expr tms f + g ← build-C-expr tms g + returnTC $ con (quote NbE.CExpr._‶∘‶_) (f v∷ g v∷ []) + build-C-expr tms t@(“F₁” F f) = + catchTC + (do unify (tms .R) F + f ← build-D-expr tms f + returnTC $ con (quote NbE.CExpr.‶R‶) (f v∷ [])) + (returnTC $ con (quote NbE.CExpr.‶_‶) (t v∷ [])) + build-C-expr tms t@(“η” adj x) = + catchTC + (do unify (tms .adjoint) adj + x ← build-C-obj-expr tms x + returnTC $ con (quote NbE.CExpr.‶η‶) (x v∷ [])) + (returnTC $ con (quote NbE.CExpr.‶_‶) (t v∷ [])) + build-C-expr tms f = do + x , y ← get-hom-boundary =<< inferType f + x ← build-C-obj-expr tms =<< normalise x + y ← build-C-obj-expr tms =<< normalise y + returnTC $ con (quote NbE.CExpr.‶_‶) (infer-hidden 9 $ (x h∷ y h∷ f v∷ [])) + + build-D-expr tms (“id” cat) = + returnTC $ con (quote NbE.DExpr.‶id‶) [] + build-D-expr tms (“∘” cat f g) = do + f ← build-D-expr tms f + g ← build-D-expr tms g + returnTC $ con (quote NbE.DExpr._‶∘‶_) (f v∷ g v∷ []) + build-D-expr tms t@(“F₁” F f) = + catchTC + (do unify (tms .L) F + f ← build-C-expr tms f + returnTC $ con (quote NbE.DExpr.‶L‶) (f v∷ [])) + (returnTC $ con (quote NbE.DExpr.‶_‶) (t v∷ [])) + build-D-expr tms t@(“ε” adj x) = + catchTC + (do unify (tms .adjoint) adj + x ← build-D-obj-expr tms x + returnTC $ con (quote NbE.DExpr.‶ε‶) (x v∷ [])) + (returnTC $ con (quote NbE.DExpr.‶_‶) (t v∷ [])) + build-D-expr tms f = do + x , y ← get-hom-boundary =<< inferType f + x ← build-D-obj-expr tms =<< normalise x + y ← build-D-obj-expr tms =<< normalise y + returnTC $ con (quote NbE.DExpr.‶_‶) (infer-hidden 9 $ (x h∷ y h∷ f v∷ [])) + + -- We are conservative when blocking reduction, and unfold on-demand + -- in the expression builder. + dont-reduce : List Name + dont-reduce = + quote Precategory.id ∷ quote Precategory._∘_ ∷ + quote Functor.F₀ ∷ quote Functor.F₁ ∷ + quote _=>_.η ∷ + quote _⊣_.unit ∷ quote _⊣_.counit ∷ [] + + solve-left-macro + : ∀ {oc ℓc od ℓd} {C : Precategory oc ℓc} {D : Precategory od ℓd} + → {L : Functor C D} {R : Functor D C} + → L ⊣ R + → Term → TC ⊤ + solve-left-macro {C = C} {D = D} {L = L} {R = R} adj hole = + withNormalisation false $ + withReduceDefs (false , dont-reduce) $ do + C-tm ← quoteTC C + D-tm ← quoteTC D + L-tm ← quoteTC L + R-tm ← quoteTC R + adj-tm ← quoteTC adj + goal ← inferType hole >>= reduce + just (lhs , rhs) ← get-boundary goal + where nothing → typeError $ strErr "Can't determine boundary: " ∷ + termErr goal ∷ [] + let tms = record { C = C-tm ; D = D-tm ; L = L-tm ; R = R-tm ; adjoint = adj-tm } + elhs ← build-D-expr tms =<< normalise lhs + erhs ← build-D-expr tms =<< normalise rhs + noConstraints $ unify hole (“D-solve” adj-tm elhs erhs) + + solve-right-macro + : ∀ {oc ℓc od ℓd} {C : Precategory oc ℓc} {D : Precategory od ℓd} + → {L : Functor C D} {R : Functor D C} + → L ⊣ R + → Term → TC ⊤ + solve-right-macro {C = C} {D = D} {L = L} {R = R} adj hole = + withNormalisation false $ + withReduceDefs (false , dont-reduce) $ do + C-tm ← quoteTC C + D-tm ← quoteTC D + L-tm ← quoteTC L + R-tm ← quoteTC R + adj-tm ← quoteTC adj + goal ← inferType hole >>= reduce + just (lhs , rhs) ← get-boundary goal + where nothing → typeError $ strErr "Can't determine boundary: " ∷ + termErr goal ∷ [] + let tms = record { C = C-tm ; D = D-tm ; L = L-tm ; R = R-tm ; adjoint = adj-tm } + elhs ← build-C-expr tms =<< normalise lhs + erhs ← build-C-expr tms =<< normalise rhs + noConstraints $ unify hole (“C-solve” adj-tm elhs erhs) +``` +
+ +With that out of the way, we expose the solver as a pair of macros, +that solve equations in $\cD$ and $\cC$, respectively. + +```agda +macro + left-adjoint! + : ∀ {oc ℓc od ℓd} {C : Precategory oc ℓc} {D : Precategory od ℓd} + → {L : Functor C D} {R : Functor D C} + → L ⊣ R + → Term → TC ⊤ + left-adjoint! = Reflection.solve-left-macro + + right-adjoint! + : ∀ {oc ℓc od ℓd} {C : Precategory oc ℓc} {D : Precategory od ℓd} + → {L : Functor C D} {R : Functor D C} + → L ⊣ R + → Term → TC ⊤ + right-adjoint! = Reflection.solve-right-macro +``` + + +## Demo + +Wow, that was a lot of hard work! Let's marvel at the fruits of our labor. + +```agda +private + module _ + {oc ℓc od ℓd} {C : Precategory oc ℓc} {D : Precategory od ℓd} + {L : Functor C D} {R : Functor D C} + (adj : L ⊣ R) where + module C = Precategory C + module D = Precategory D + module L = Functor L + module R = Functor R + open _⊣_ adj + + reflection-test' : ∀ {x y} → (f : D.Hom (L.₀ x) y) → R-adjunct adj (L-adjunct adj f) ≡ f + reflection-test' f = left-adjoint! adj + + R-adjunct-natural₂' + : ∀ {a b c d} (f : D.Hom a b) (g : C.Hom c d) (x : C.Hom d (R.F₀ a)) + → R-adjunct adj (R.₁ f C.∘ x C.∘ g) ≡ f D.∘ R-adjunct adj x D.∘ L.₁ g + R-adjunct-natural₂' _ _ _ = left-adjoint! adj +``` diff --git a/src/Meta/Brackets.lagda.md b/src/Meta/Brackets.lagda.md new file mode 100644 index 000000000..2d650c23d --- /dev/null +++ b/src/Meta/Brackets.lagda.md @@ -0,0 +1,26 @@ + + +```agda +module Meta.Brackets where +``` + +# Brackets notation + +```agda +record ⟦-⟧-notation (Syn : Typeω) : Typeω where + constructor has-⟦-⟧ + no-eta-equality + field + {lvl} : Level + Sem : Type lvl + interpret : Syn → Sem + +open ⟦-⟧-notation + +⟦_⟧ : ∀ {Syn : Typeω} ⦃ not : ⟦-⟧-notation Syn ⦄ → Syn → not .Sem +⟦_⟧ ⦃ not ⦄ = not .interpret +``` diff --git a/src/index.lagda.md b/src/index.lagda.md index 4c2d18e61..a572bdbe3 100644 --- a/src/index.lagda.md +++ b/src/index.lagda.md @@ -423,6 +423,7 @@ open import Cat.Functor.Adjoint.Monadic -- Monadic adjunctions open import Cat.Functor.Adjoint.Compose -- Adjunctions compose open import Cat.Functor.Adjoint.Continuous -- Right adjoints preserve limits open import Cat.Functor.Adjoint.Reflective -- Reflective subcategories +open import Cat.Functor.Adjoint.Solver -- Automatic solver for adjoints ``` Monadicity theorems: