3.2. Implementation
The previous chapter explained
monoidal_coherence
and
bicategory_coherence,
which solve goals built only from associators and unitors.
This chapter explains what is added by
monoidal
and
bicategory
when a goal contains actual maps, not merely those constructed from associators and unitors.
The extra work is to rearrange each side until those maps can be compared directly, leaving only
coherence equalities for the previous chapter.
3.2.1. Shared Core
The previous chapter explained the design of the shared core. What is added here is the normalization of 2-morphisms.
The core recursive function is
eval,
which rearranges the parsed expression into a form where the actual maps can be compared one by one.
The classes
MkEvalComp,
MkEvalWhiskerLeft,
MkEvalWhiskerRight,
MkEvalHorizontalComp,
and
MkEval
support this function by supplying the equality proofs needed at each recursive step.
Normalization means separating two kinds of data in the composite. The actual maps stay in the same order. Associators and unitors are pushed into the coherence pieces around them. The target shape is an alternating composite
\alpha_0 \,\mathbin{\gg}\, \eta_1 \,\mathbin{\gg}\, \alpha_1 \,\mathbin{\gg}\, \cdots
\,\mathbin{\gg}\, \eta_n \,\mathbin{\gg}\, \alpha_n,
where each \alpha_i is one coherence piece built only from associators and unitors.
More precisely, the source code defines each \eta_i to have the form
f_1 \triangleleft \cdots \triangleleft f_n \triangleleft \theta.
Here \theta has the form
\iota_1 \otimes \cdots \otimes \iota_\ell,
and each \iota_j has the form
\kappa \triangleright g_1 \triangleright \cdots \triangleright g_k,
where \kappa is one atomic 2-morphism from the original expression. In the implementation,
these three layers are represented by
WhiskerLeft,
HorizontalComp,
and WhiskerRight.
The datatype NormalExpr
then stores the full alternating expression.
/-- Expressions of the form `η ▷ f₁ ▷ ... ▷ fₙ`. -/
inductive WhiskerRight : Type
/-- Construct the expression for an atomic 2-morphism. -/
| of (η : Atom) : WhiskerRight
/-- Construct the expression for `η ▷ f`. -/
| whisker (e : Mor₂) (η : WhiskerRight) (f : Atom₁) : WhiskerRight
deriving Inhabited/-- Expressions of the form `η₁ ⊗ ... ⊗ ηₙ`. -/
inductive HorizontalComp : Type
| of (η : WhiskerRight) : HorizontalComp
| cons (e : Mor₂) (η : WhiskerRight) (ηs : HorizontalComp) :
HorizontalComp
deriving Inhabited/-- Expressions of the form `f₁ ◁ ... ◁ fₙ ◁ η`. -/
inductive WhiskerLeft : Type
/-- Construct the expression for a right-whiskered 2-morphism. -/
| of (η : HorizontalComp) : WhiskerLeft
/-- Construct the expression for `f ◁ η`. -/
| whisker (e : Mor₂) (f : Atom₁) (η : WhiskerLeft) : WhiskerLeft
deriving Inhabited/-- Normalized expressions for 2-morphisms. -/
inductive NormalExpr : Type
/-- Construct the expression for a structural 2-morphism. -/
| nil (e : Mor₂) (α : Structural) : NormalExpr
/-- Construct the normalized expression of a 2-morphism `α ≫ η ≫ ηs` recursively. -/
| cons (e : Mor₂) (α : Structural) (η : WhiskerLeft) (ηs : NormalExpr) : NormalExpr
deriving Inhabited
The result type
Eval.Result
packages such a normalized expression together with a proof that it is equal to the original term.
/-- The result of evaluating an expression into normal form. -/
structure Eval.Result where
/-- The normalized expression of the 2-morphism. -/
expr : NormalExpr
/-- The proof that the normalized expression is equal to the original expression. -/
proof : Expr
deriving Inhabited
The function that actually carries out this normalization is
eval.
-- TODO: It takes a while to compile. Find out why.
/-- Evaluate the expression of a 2-morphism into a normalized form. -/
def eval (nm : Name) (e : Mor₂) : CoherenceM ρ Eval.Result := do
withTraceNode nm (fun _ => return m!"eval: {e.e}") do
match e with
| .isoHom _ _ α => withTraceNode nm (fun _ => return m!"Iso.hom") do match α with
| .structuralAtom α => return ⟨← nilM <| .structuralAtom α, ← mkEqRefl e.e⟩
| .of η =>
let η ← MonadMor₂.atomHomM η
let result ← mkEvalOf η
traceProof nm result
return ⟨← NormalExpr.ofAtomM η, result⟩
| _ => throwError "not implemented. try dsimp first."
| .isoInv _ _ α => withTraceNode nm (fun _ => return m!"Iso.inv") do match α with
| .structuralAtom α => return ⟨← nilM <| (← symmM (.structuralAtom α)), ← mkEqRefl e.e⟩
| .of η =>
let η ← MonadMor₂.atomInvM η
let result ← mkEvalOf η
traceProof nm result
return ⟨← NormalExpr.ofAtomM η, result⟩
| _ => throwError "not implemented. try dsimp first."
| .id _ _ f =>
let α ← MonadMor₂Iso.id₂M f
return ⟨← nilM <| .structuralAtom α, ← mkEqRefl e.e⟩
| .comp _ _ _ _ _ η θ => withTraceNode nm (fun _ => return m!"comp") do
let ⟨η', e_η⟩ ← eval nm η
let ⟨θ', e_θ⟩ ← eval nm θ
let ⟨ηθ, pf⟩ ← evalComp η' θ'
let result ← mkEvalComp η θ η' θ' ηθ e_η e_θ pf
traceProof nm result
return ⟨ηθ, result⟩
| .whiskerLeft _ _ f _ _ η => withTraceNode nm (fun _ => return m!"whiskerLeft") do
let ⟨η', e_η⟩ ← eval nm η
let ⟨θ, e_θ⟩ ← evalWhiskerLeft f η'
let result ← mkEvalWhiskerLeft f η η' θ e_η e_θ
traceProof nm result
return ⟨θ, result⟩
| .whiskerRight _ _ _ _ η h =>
withTraceNode nm (fun _ => return m!"whiskerRight") do
let ⟨η', e_η⟩ ← eval nm η
let ⟨θ, e_θ⟩ ← evalWhiskerRight η' h
let result ← mkEvalWhiskerRight η h η' θ e_η e_θ
traceProof nm result
return ⟨θ, result⟩
| .coherenceComp _ _ _ _ _ _ α₀ η θ =>
withTraceNode nm (fun _ => return m!"monoidalComp") do
let ⟨η', e_η⟩ ← eval nm η
let α₀ := .structuralAtom <| .coherenceHom α₀
let α ← nilM α₀
let ⟨θ', e_θ⟩ ← eval nm θ
let ⟨αθ, e_αθ⟩ ← evalComp α θ'
let ⟨ηαθ, e_ηαθ⟩ ← evalComp η' αθ
let result ← mkEvalMonoidalComp η θ α₀ η' θ' αθ ηαθ e_η e_θ e_αθ e_ηαθ
traceProof nm result
return ⟨ηαθ, result⟩
| .horizontalComp _ _ _ _ _ _ η θ =>
withTraceNode nm (fun _ => return m!"horizontalComp") do
let ⟨η', e_η⟩ ← eval nm η
let ⟨θ', e_θ⟩ ← eval nm θ
let ⟨ηθ, e_ηθ⟩ ← evalHorizontalComp η' θ'
let result ← mkEvalHorizontalComp η θ η' θ' ηθ e_η e_θ e_ηθ
traceProof nm result
return ⟨ηθ, result⟩
| .of η =>
let result ← mkEvalOf η
traceProof nm result
return ⟨← NormalExpr.ofAtomM η, result⟩
The helper evalComp
combines two already-normalized expressions.
/-- Evaluate the expression `η ≫ θ` into a normalized form. -/
def evalComp : NormalExpr → NormalExpr → CoherenceM ρ Eval.Result
| .nil _ α, η => do evalCompNil α η
| .cons _ α η ηs, θ => do
let ⟨ι, e_ι⟩ ← evalComp ηs θ
return ⟨← consM α η ι, ← mkEvalCompCons α η ηs θ ι e_ι⟩
The helper evalWhiskerLeft
handles left whiskering.
/-- Evaluate the expression `f ◁ η` into a normalized form. -/
def evalWhiskerLeft : Mor₁ → NormalExpr → CoherenceM ρ Eval.Result
| f, .nil _ α => do
return ⟨← nilM (← whiskerLeftM f α), ← mkEvalWhiskerLeftNil f α⟩
| .of f, .cons _ α η ηs => do
let η' ← MonadWhiskerLeft.whiskerLeftM f η
let ⟨θ, e_θ⟩ ← evalWhiskerLeft (.of f) ηs
let η'' ← consM (← whiskerLeftM (.of f) α) η' θ
return ⟨η'', ← mkEvalWhiskerLeftOfCons f α η ηs θ e_θ⟩
| .comp _ f g, η => do
let ⟨θ, e_θ⟩ ← evalWhiskerLeft g η
let ⟨ι, e_ι⟩ ← evalWhiskerLeft f θ
let h ← η.srcM
let h' ← η.tgtM
let ⟨ι', e_ι'⟩ ← evalComp ι (← NormalExpr.associatorInvM f g h')
let ⟨ι'', e_ι''⟩ ← evalComp (← NormalExpr.associatorM f g h) ι'
return ⟨ι'', ← mkEvalWhiskerLeftComp f g η θ ι ι' ι'' e_θ e_ι e_ι' e_ι''⟩
| .id _ _, η => do
let f ← η.srcM
let g ← η.tgtM
let ⟨η', e_η'⟩ ← evalComp η (← NormalExpr.leftUnitorInvM g)
let ⟨η'', e_η''⟩ ← evalComp (← NormalExpr.leftUnitorM f) η'
return ⟨η'', ← mkEvalWhiskerLeftId η η' η'' e_η' e_η''⟩
The helper evalWhiskerRight
for right whiskering and
evalHorizontalComp
for horizontal composition are more involved.
One reason is that the normal form stores actual maps as
WhiskerLeft
terms, and the expression in the form (f ◁ η) ▷ h cannot be in the normal form.
These two helper functions are implemented by mutual recursion with auxiliary functions.
mutual
/-- Evaluate the expression `η ▷ f` into a normalized form. -/
partial def evalWhiskerRightAux : HorizontalComp → Atom₁ → CoherenceM ρ Eval.Result
| .of η, f => do
let η' ← NormalExpr.ofM <| .of <| .of <| ← MonadWhiskerRight.whiskerRightM η f
return ⟨η', ← mkEvalWhiskerRightAuxOf η f⟩
| .cons _ η ηs, f => do
let ⟨ηs', e_ηs'⟩ ← evalWhiskerRightAux ηs f
let ⟨η₁, e_η₁⟩ ← evalHorizontalComp (← NormalExpr.ofM <| .of <| .of η) ηs'
let ⟨η₂, e_η₂⟩ ← evalComp η₁ (← NormalExpr.associatorInvM (← η.tgtM) (← ηs.tgtM) (.of f))
let ⟨η₃, e_η₃⟩ ← evalComp (← NormalExpr.associatorM (← η.srcM) (← ηs.srcM) (.of f)) η₂
return ⟨η₃, ← mkEvalWhiskerRightAuxCons f η ηs ηs' η₁ η₂ η₃ e_ηs' e_η₁ e_η₂ e_η₃⟩
/-- Evaluate the expression `η ▷ f` into a normalized form. -/
partial def evalWhiskerRight : NormalExpr → Mor₁ → CoherenceM ρ Eval.Result
| .nil _ α, h => do
return ⟨← nilM (← whiskerRightM α h), ← mkEvalWhiskerRightNil α h⟩
| .cons _ α (.of η) ηs, .of f => do
let ⟨ηs₁, e_ηs₁⟩ ← evalWhiskerRight ηs (.of f)
let ⟨η₁, e_η₁⟩ ← evalWhiskerRightAux η f
let ⟨η₂, e_η₂⟩ ← evalComp η₁ ηs₁
let ⟨η₃, e_η₃⟩ ← evalCompNil (← whiskerRightM α (.of f)) η₂
return ⟨η₃, ← mkEvalWhiskerRightConsOfOf f α η ηs ηs₁ η₁ η₂ η₃ e_ηs₁ e_η₁ e_η₂ e_η₃⟩
| .cons _ α (.whisker _ f η) ηs, h => do
let g ← η.srcM
let g' ← η.tgtM
let ⟨η₁, e_η₁⟩ ← evalWhiskerRight (← consM (← id₂M' g) η (← NormalExpr.idM g')) h
let ⟨η₂, e_η₂⟩ ← evalWhiskerLeft (.of f) η₁
let ⟨ηs₁, e_ηs₁⟩ ← evalWhiskerRight ηs h
let α' ← whiskerRightM α h
let ⟨ηs₂, e_ηs₂⟩ ← evalComp (← NormalExpr.associatorInvM (.of f) g' h) ηs₁
let ⟨η₃, e_η₃⟩ ← evalComp η₂ ηs₂
let ⟨η₄, e_η₄⟩ ← evalComp (← NormalExpr.associatorM (.of f) g h) η₃
let ⟨η₅, e_η₅⟩ ← evalComp (← nilM α') η₄
return ⟨η₅, ← mkEvalWhiskerRightConsWhisker f h α η ηs η₁ η₂ ηs₁ ηs₂ η₃ η₄ η₅
e_η₁ e_η₂ e_ηs₁ e_ηs₂ e_η₃ e_η₄ e_η₅⟩
| η, .comp _ g h => do
let ⟨η₁, e_η₁⟩ ← evalWhiskerRight η g
let ⟨η₂, e_η₂⟩ ← evalWhiskerRight η₁ h
let f ← η.srcM
let f' ← η.tgtM
let ⟨η₃, e_η₃⟩ ← evalComp η₂ (← NormalExpr.associatorM f' g h)
let ⟨η₄, e_η₄⟩ ← evalComp (← NormalExpr.associatorInvM f g h) η₃
return ⟨η₄, ← mkEvalWhiskerRightComp g h η η₁ η₂ η₃ η₄ e_η₁ e_η₂ e_η₃ e_η₄⟩
| η, .id _ _ => do
let f ← η.srcM
let g ← η.tgtM
let ⟨η₁, e_η₁⟩ ← evalComp η (← NormalExpr.rightUnitorInvM g)
let ⟨η₂, e_η₂⟩ ← evalComp (← NormalExpr.rightUnitorM f) η₁
return ⟨η₂, ← mkEvalWhiskerRightId η η₁ η₂ e_η₁ e_η₂⟩
/-- Evaluate the expression `η ⊗ θ` into a normalized form. -/
partial def evalHorizontalCompAux : HorizontalComp → HorizontalComp → CoherenceM ρ Eval.Result
| .of η, θ => do
return ⟨← NormalExpr.ofM <| .of <| ← MonadHorizontalComp.hConsM η θ,
← mkEvalHorizontalCompAuxOf η θ⟩
| .cons _ η ηs, θ => do
let α ← NormalExpr.associatorM (← η.srcM) (← ηs.srcM) (← θ.srcM)
let α' ← NormalExpr.associatorInvM (← η.tgtM) (← ηs.tgtM) (← θ.tgtM)
let ⟨ηθ, e_ηθ⟩ ← evalHorizontalCompAux ηs θ
let ⟨η₁, e_η₁⟩ ← evalHorizontalComp (← NormalExpr.ofM <| .of <| .of η) ηθ
let ⟨ηθ₁, e_ηθ₁⟩ ← evalComp η₁ α'
let ⟨ηθ₂, e_ηθ₂⟩ ← evalComp α ηθ₁
return ⟨ηθ₂, ← mkEvalHorizontalCompAuxCons η ηs θ ηθ η₁ ηθ₁ ηθ₂ e_ηθ e_η₁ e_ηθ₁ e_ηθ₂⟩
/-- Evaluate the expression `η ⊗ θ` into a normalized form. -/
partial def evalHorizontalCompAux' : WhiskerLeft → WhiskerLeft → CoherenceM ρ Eval.Result
| .of η, .of θ => evalHorizontalCompAux η θ
| .whisker _ f η, θ => do
let ⟨ηθ, e_ηθ⟩ ← evalHorizontalCompAux' η θ
let ⟨ηθ₁, e_ηθ₁⟩ ← evalWhiskerLeft (.of f) ηθ
let ⟨ηθ₂, e_ηθ₂⟩ ← evalComp ηθ₁ (← NormalExpr.associatorInvM (.of f) (← η.tgtM) (← θ.tgtM))
let ⟨ηθ₃, e_ηθ₃⟩ ← evalComp (← NormalExpr.associatorM (.of f) (← η.srcM) (← θ.srcM)) ηθ₂
return ⟨ηθ₃, ← mkEvalHorizontalCompAux'Whisker f η θ ηθ ηθ₁ ηθ₂ ηθ₃ e_ηθ e_ηθ₁ e_ηθ₂ e_ηθ₃⟩
| .of η, .whisker _ f θ => do
let ⟨η₁, e_η₁⟩ ← evalWhiskerRightAux η f
let ⟨ηθ, e_ηθ⟩ ← evalHorizontalComp η₁ (← NormalExpr.ofM θ)
let ⟨ηθ₁, e_ηθ₁⟩ ← evalComp ηθ (← NormalExpr.associatorM (← η.tgtM) (.of f) (← θ.tgtM))
let ⟨ηθ₂, e_ηθ₂⟩ ← evalComp (← NormalExpr.associatorInvM (← η.srcM) (.of f) (← θ.srcM)) ηθ₁
return ⟨ηθ₂, ← mkEvalHorizontalCompAux'OfWhisker f η θ ηθ η₁ ηθ₁ ηθ₂ e_η₁ e_ηθ e_ηθ₁ e_ηθ₂⟩
/-- Evaluate the expression `η ⊗ θ` into a normalized form. -/
partial def evalHorizontalComp : NormalExpr → NormalExpr → CoherenceM ρ Eval.Result
| .nil _ α, .nil _ β => do
return ⟨← nilM <| ← horizontalCompM α β, ← mkEvalHorizontalCompNilNil α β⟩
| .nil _ α, .cons _ β η ηs => do
let ⟨η₁, e_η₁⟩ ← evalWhiskerLeft (← α.tgtM) (← NormalExpr.ofM η)
let ⟨ηs₁, e_ηs₁⟩ ← evalWhiskerLeft (← α.tgtM) ηs
let ⟨η₂, e_η₂⟩ ← evalComp η₁ ηs₁
let ⟨η₃, e_η₃⟩ ← evalCompNil (← horizontalCompM α β) η₂
return ⟨η₃, ← mkEvalHorizontalCompNilCons α β η ηs η₁ ηs₁ η₂ η₃ e_η₁ e_ηs₁ e_η₂ e_η₃⟩
| .cons _ α η ηs, .nil _ β => do
let ⟨η₁, e_η₁⟩ ← evalWhiskerRight (← NormalExpr.ofM η) (← β.tgtM)
let ⟨ηs₁, e_ηs₁⟩ ← evalWhiskerRight ηs (← β.tgtM)
let ⟨η₂, e_η₂⟩ ← evalComp η₁ ηs₁
let ⟨η₃, e_η₃⟩ ← evalCompNil (← horizontalCompM α β) η₂
return ⟨η₃, ← mkEvalHorizontalCompConsNil α β η ηs η₁ ηs₁ η₂ η₃ e_η₁ e_ηs₁ e_η₂ e_η₃⟩
| .cons _ α η ηs, .cons _ β θ θs => do
let ⟨ηθ, e_ηθ⟩ ← evalHorizontalCompAux' η θ
let ⟨ηθs, e_ηθs⟩ ← evalHorizontalComp ηs θs
let ⟨ηθ₁, e_ηθ₁⟩ ← evalComp ηθ ηθs
let ⟨ηθ₂, e_ηθ₂⟩ ← evalCompNil (← horizontalCompM α β) ηθ₁
return ⟨ηθ₂,
← mkEvalHorizontalCompConsCons α β η θ ηs θs ηθ ηθs ηθ₁ ηθ₂ e_ηθ e_ηθs e_ηθ₁ e_ηθ₂⟩
end
Using eval, the function
normalForm
replace the original goal by the equality of the
two normalized expressions.
/-- Transform an equality between 2-morphisms into the equality between their normalizations. -/
def normalForm (ρ : Type) [Context ρ]
[MonadMor₁ (CoherenceM ρ)]
[MonadMor₂Iso (CoherenceM ρ)]
[MonadNormalExpr (CoherenceM ρ)] [MkEval (CoherenceM ρ)]
[MkMor₂ (CoherenceM ρ)]
[MonadMor₂ (CoherenceM ρ)]
(nm : Name) (mvarId : MVarId) : MetaM (List MVarId) := do
mvarId.withContext do
let e ← instantiateMVars <| ← mvarId.getType
withTraceNode nm (fun _ => return m!"normalize: {e}") do
let some (_, e₁, e₂) := (← whnfR <| ← instantiateMVars <| e).eq?
| throwError "{nm}_nf requires an equality goal"
let ctx : ρ ← mkContext e₁
CoherenceM.run (ctx := ctx) do
let e₁' ← MkMor₂.ofExpr e₁
let e₂' ← MkMor₂.ofExpr e₂
let e₁'' ← eval nm e₁'
let e₂'' ← eval nm e₂'
let H ← mkAppM ``mk_eq #[e₁, e₂, e₁''.expr.e.e, e₂''.expr.e.e, e₁''.proof, e₂''.proof]
mvarId.apply H
Then
ofNormalizedEq
examines an equality of normalized composites and decomposes the leading cons layer.
Its theorem mk_eq_of_cons turns
α ≫ η ≫ ηs = α' ≫ η' ≫ ηs'
into three new goals:
the first coherence piece, the next actual map, and the remaining tail.
/-- Split the goal `α ≫ η ≫ ηs = α' ≫ η' ≫ ηs'` into `α = α'`, `η = η'`, and `ηs = ηs'`. -/
def ofNormalizedEq (mvarId : MVarId) : MetaM (List MVarId) := do
mvarId.withContext do
let e ← instantiateMVars <| ← mvarId.getType
let some (_, e₁, e₂) := (← whnfR e).eq? | throwError "requires an equality goal"
match (← whnfR e₁).getAppFnArgs, (← whnfR e₂).getAppFnArgs with
| (``CategoryStruct.comp, #[_, _, _, _, _, α, η]),
(``CategoryStruct.comp, #[_, _, _, _, _, α', η']) =>
match (← whnfR η).getAppFnArgs, (← whnfR η').getAppFnArgs with
| (``CategoryStruct.comp, #[_, _, _, _, _, η, ηs]),
(``CategoryStruct.comp, #[_, _, _, _, _, η', ηs']) =>
let e_α ← mkFreshExprMVar (← Meta.mkEq α α')
let e_η ← mkFreshExprMVar (← Meta.mkEq η η')
let e_ηs ← mkFreshExprMVar (← Meta.mkEq ηs ηs')
let x ← mvarId.apply (← mkAppM ``mk_eq_of_cons #[α, α', η, η', ηs, ηs', e_α, e_η, e_ηs])
return x
| _, _ => throwError "failed to make a normalized equality for {e}"
| _, _ => throwError "failed to make a normalized equality for {e}"
Finally,
main
first calls
normalForm
to replace the original goal by an equality of normalized expressions. It then repeats
ofNormalizedEq
until no further cons layer remains.
At that point the remaining goals alternate between coherence pieces and actual maps. The actual-map
goals are closed by reflexivity. The remaining coherence goals are then sent to
the pure coherence tactic from the
previous chapter.
/-- The core function for `monoidal` and `bicategory` tactics. -/
def main (ρ : Type) [Context ρ] [MonadMor₁ (CoherenceM ρ)] [MonadMor₂Iso (CoherenceM ρ)]
[MonadNormalExpr (CoherenceM ρ)] [MkEval (CoherenceM ρ)] [MkMor₂ (CoherenceM ρ)]
[MonadMor₂ (CoherenceM ρ)] [MonadCoherehnceHom (CoherenceM ρ)]
[MonadNormalizeNaturality (CoherenceM ρ)] [MkEqOfNaturality (CoherenceM ρ)]
(nm : Name) (mvarId : MVarId) : MetaM (List MVarId) :=
mvarId.withContext do
let mvarIds ← normalForm ρ nm mvarId
let (mvarIdsCoherence, mvarIdsRefl) := List.splitEvenOdd (← repeat' ofNormalizedEq mvarIds)
for mvarId in mvarIdsRefl do mvarId.refl
let mvarIds'' ← mvarIdsCoherence.mapM fun mvarId => do
withTraceNode nm (fun _ => do return m!"goal: {← mvarId.getType}") do
try
pureCoherence ρ nm mvarId
catch _ => return [mvarId]
return mvarIds''.flatten3.2.2. Monoidal Front End
The monoidal front end is the code around the user-facing tactic
/--
Use the coherence theorem for monoidal categories to solve equations in a monoidal category,
where the two sides only differ by replacing strings of monoidal structural morphisms
(that is, associators, unitors, and identities)
with different strings of structural morphisms with the same source and target.
That is, `monoidal` can handle goals of the form
`a ≫ f ≫ b ≫ g ≫ c = a' ≫ f ≫ b' ≫ g ≫ c'`
where `a = a'`, `b = b'`, and `c = c'` can be proved using `monoidal_coherence`.
-/
def monoidal (mvarId : MVarId) : MetaM (List MVarId) :=
BicategoryLike.main Monoidal.Context `monoidal mvarId3.2.3. Bicategorical Front End
The bicategorical front end is the code around the user-facing tactic
/--
Use the coherence theorem for bicategories to solve equations in a bicategory,
where the two sides only differ by replacing strings of bicategory structural morphisms
(that is, associators, unitors, and identities)
with different strings of structural morphisms with the same source and target.
That is, `bicategory` can handle goals of the form
`a ≫ f ≫ b ≫ g ≫ c = a' ≫ f ≫ b' ≫ g ≫ c'`
where `a = a'`, `b = b'`, and `c = c'` can be proved using `bicategory_coherence`.
-/
def bicategory (mvarId : MVarId) : MetaM (List MVarId) :=
BicategoryLike.main Bicategory.Context `bicategory mvarId