/-
Copyright (c) 2014 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro

! This file was ported from Lean 3 source module algebra.char_zero.defs
! leanprover-community/mathlib commit 448144f7ae193a8990cb7473c9e9a01990f64ac7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Int.Cast.Defs

/-!
# Characteristic zero

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

A ring `R` is called of characteristic zero if every natural number `n` is non-zero when considered
as an element of `R`. Since this definition doesn't mention the multiplicative structure of `R`
except for the existence of `1` in this file characteristic zero is defined for additive monoids
with `1`.

## Main definition

`char_zero` is the typeclass of an additive monoid with one such that the natural homomorphism
from the natural numbers into it is injective.

## TODO

* Unify with `char_p` (possibly using an out-parameter)
-/

#print CharZero /-
/-- Typeclass for monoids with characteristic zero.
  (This is usually stated on fields but it makes sense for any additive monoid with 1.)

*Warning*: for a semiring `R`, `char_zero R` and `char_p R 0` need not coincide.
* `char_zero R` requires an injection `ℕ ↪ R`;
* `char_p R 0` asks that only `0 : ℕ` maps to `0 : R` under the map `ℕ → R`.

For instance, endowing `{0, 1}` with addition given by `max` (i.e. `1` is absorbing), shows that
`char_zero {0, 1}` does not hold and yet `char_p {0, 1} 0` does.
This example is formalized in `counterexamples/char_p_zero_ne_char_zero`.
 -/
class CharZero (R : Type _) [AddMonoidWithOne R] : Prop where
  cast_injective : Function.Injective (coe : ℕ → R)
#align char_zero CharZero
-/

/- warning: char_zero_of_inj_zero -> charZero_of_inj_zero is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : AddGroupWithOne.{u1} R], (forall (n : Nat), (Eq.{succ u1} R ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat R (HasLiftT.mk.{1, succ u1} Nat R (CoeTCₓ.coe.{1, succ u1} Nat R (Nat.castCoe.{u1} R (AddMonoidWithOne.toNatCast.{u1} R (AddGroupWithOne.toAddMonoidWithOne.{u1} R _inst_1))))) n) (OfNat.ofNat.{u1} R 0 (OfNat.mk.{u1} R 0 (Zero.zero.{u1} R (AddZeroClass.toHasZero.{u1} R (AddMonoid.toAddZeroClass.{u1} R (AddMonoidWithOne.toAddMonoid.{u1} R (AddGroupWithOne.toAddMonoidWithOne.{u1} R _inst_1)))))))) -> (Eq.{1} Nat n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero))))) -> (CharZero.{u1} R (AddGroupWithOne.toAddMonoidWithOne.{u1} R _inst_1))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : AddGroupWithOne.{u1} R], (forall (n : Nat), (Eq.{succ u1} R (Nat.cast.{u1} R (AddMonoidWithOne.toNatCast.{u1} R (AddGroupWithOne.toAddMonoidWithOne.{u1} R _inst_1)) n) (OfNat.ofNat.{u1} R 0 (Zero.toOfNat0.{u1} R (AddRightCancelMonoid.toZero.{u1} R (AddCancelMonoid.toAddRightCancelMonoid.{u1} R (AddGroup.toAddCancelMonoid.{u1} R (AddGroupWithOne.toAddGroup.{u1} R _inst_1))))))) -> (Eq.{1} Nat n (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)))) -> (CharZero.{u1} R (AddGroupWithOne.toAddMonoidWithOne.{u1} R _inst_1))
Case conversion may be inaccurate. Consider using '#align char_zero_of_inj_zero charZero_of_inj_zeroₓ'. -/
theorem charZero_of_inj_zero {R : Type _} [AddGroupWithOne R] (H : ∀ n : ℕ, (n : R) = 0 → n = 0) :
    CharZero R :=
  ⟨fun m n h => by
    induction' m with m ih generalizing n;
    · rw [H n]
      rw [← h, Nat.cast_zero]
    cases' n with n;
    · apply H
      rw [h, Nat.cast_zero]
    simp_rw [Nat.cast_succ, add_right_cancel_iff] at h; rwa [ih]⟩
#align char_zero_of_inj_zero charZero_of_inj_zero

namespace Nat

variable {R : Type _} [AddMonoidWithOne R] [CharZero R]

#print Nat.cast_injective /-
theorem cast_injective : Function.Injective (coe : ℕ → R) :=
  CharZero.cast_injective
#align nat.cast_injective Nat.cast_injective
-/

#print Nat.cast_inj /-
@[simp, norm_cast]
theorem cast_inj {m n : ℕ} : (m : R) = n ↔ m = n :=
  cast_injective.eq_iff
#align nat.cast_inj Nat.cast_inj
-/

/- warning: nat.cast_eq_zero -> Nat.cast_eq_zero is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : AddMonoidWithOne.{u1} R] [_inst_2 : CharZero.{u1} R _inst_1] {n : Nat}, Iff (Eq.{succ u1} R ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat R (HasLiftT.mk.{1, succ u1} Nat R (CoeTCₓ.coe.{1, succ u1} Nat R (Nat.castCoe.{u1} R (AddMonoidWithOne.toNatCast.{u1} R _inst_1)))) n) (OfNat.ofNat.{u1} R 0 (OfNat.mk.{u1} R 0 (Zero.zero.{u1} R (AddZeroClass.toHasZero.{u1} R (AddMonoid.toAddZeroClass.{u1} R (AddMonoidWithOne.toAddMonoid.{u1} R _inst_1))))))) (Eq.{1} Nat n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : AddMonoidWithOne.{u1} R] [_inst_2 : CharZero.{u1} R _inst_1] {n : Nat}, Iff (Eq.{succ u1} R (Nat.cast.{u1} R (AddMonoidWithOne.toNatCast.{u1} R _inst_1) n) (OfNat.ofNat.{u1} R 0 (Zero.toOfNat0.{u1} R (AddMonoid.toZero.{u1} R (AddMonoidWithOne.toAddMonoid.{u1} R _inst_1))))) (Eq.{1} Nat n (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)))
Case conversion may be inaccurate. Consider using '#align nat.cast_eq_zero Nat.cast_eq_zeroₓ'. -/
@[simp, norm_cast]
theorem cast_eq_zero {n : ℕ} : (n : R) = 0 ↔ n = 0 := by rw [← cast_zero, cast_inj]
#align nat.cast_eq_zero Nat.cast_eq_zero

/- warning: nat.cast_ne_zero -> Nat.cast_ne_zero is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : AddMonoidWithOne.{u1} R] [_inst_2 : CharZero.{u1} R _inst_1] {n : Nat}, Iff (Ne.{succ u1} R ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat R (HasLiftT.mk.{1, succ u1} Nat R (CoeTCₓ.coe.{1, succ u1} Nat R (Nat.castCoe.{u1} R (AddMonoidWithOne.toNatCast.{u1} R _inst_1)))) n) (OfNat.ofNat.{u1} R 0 (OfNat.mk.{u1} R 0 (Zero.zero.{u1} R (AddZeroClass.toHasZero.{u1} R (AddMonoid.toAddZeroClass.{u1} R (AddMonoidWithOne.toAddMonoid.{u1} R _inst_1))))))) (Ne.{1} Nat n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : AddMonoidWithOne.{u1} R] [_inst_2 : CharZero.{u1} R _inst_1] {n : Nat}, Iff (Ne.{succ u1} R (Nat.cast.{u1} R (AddMonoidWithOne.toNatCast.{u1} R _inst_1) n) (OfNat.ofNat.{u1} R 0 (Zero.toOfNat0.{u1} R (AddMonoid.toZero.{u1} R (AddMonoidWithOne.toAddMonoid.{u1} R _inst_1))))) (Ne.{1} Nat n (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)))
Case conversion may be inaccurate. Consider using '#align nat.cast_ne_zero Nat.cast_ne_zeroₓ'. -/
@[norm_cast]
theorem cast_ne_zero {n : ℕ} : (n : R) ≠ 0 ↔ n ≠ 0 :=
  not_congr cast_eq_zero
#align nat.cast_ne_zero Nat.cast_ne_zero

/- warning: nat.cast_add_one_ne_zero -> Nat.cast_add_one_ne_zero is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_1 : AddMonoidWithOne.{u1} R] [_inst_2 : CharZero.{u1} R _inst_1] (n : Nat), Ne.{succ u1} R (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (AddZeroClass.toHasAdd.{u1} R (AddMonoid.toAddZeroClass.{u1} R (AddMonoidWithOne.toAddMonoid.{u1} R _inst_1)))) ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat R (HasLiftT.mk.{1, succ u1} Nat R (CoeTCₓ.coe.{1, succ u1} Nat R (Nat.castCoe.{u1} R (AddMonoidWithOne.toNatCast.{u1} R _inst_1)))) n) (OfNat.ofNat.{u1} R 1 (OfNat.mk.{u1} R 1 (One.one.{u1} R (AddMonoidWithOne.toOne.{u1} R _inst_1))))) (OfNat.ofNat.{u1} R 0 (OfNat.mk.{u1} R 0 (Zero.zero.{u1} R (AddZeroClass.toHasZero.{u1} R (AddMonoid.toAddZeroClass.{u1} R (AddMonoidWithOne.toAddMonoid.{u1} R _inst_1))))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_1 : AddMonoidWithOne.{u1} R] [_inst_2 : CharZero.{u1} R _inst_1] (n : Nat), Ne.{succ u1} R (HAdd.hAdd.{u1, u1, u1} R R R (instHAdd.{u1} R (AddZeroClass.toAdd.{u1} R (AddMonoid.toAddZeroClass.{u1} R (AddMonoidWithOne.toAddMonoid.{u1} R _inst_1)))) (Nat.cast.{u1} R (AddMonoidWithOne.toNatCast.{u1} R _inst_1) n) (OfNat.ofNat.{u1} R 1 (One.toOfNat1.{u1} R (AddMonoidWithOne.toOne.{u1} R _inst_1)))) (OfNat.ofNat.{u1} R 0 (Zero.toOfNat0.{u1} R (AddMonoid.toZero.{u1} R (AddMonoidWithOne.toAddMonoid.{u1} R _inst_1))))
Case conversion may be inaccurate. Consider using '#align nat.cast_add_one_ne_zero Nat.cast_add_one_ne_zeroₓ'. -/
theorem cast_add_one_ne_zero (n : ℕ) : (n + 1 : R) ≠ 0 := by exact_mod_cast n.succ_ne_zero
#align nat.cast_add_one_ne_zero Nat.cast_add_one_ne_zero

#print Nat.cast_eq_one /-
@[simp, norm_cast]
theorem cast_eq_one {n : ℕ} : (n : R) = 1 ↔ n = 1 := by rw [← cast_one, cast_inj]
#align nat.cast_eq_one Nat.cast_eq_one
-/

#print Nat.cast_ne_one /-
@[norm_cast]
theorem cast_ne_one {n : ℕ} : (n : R) ≠ 1 ↔ n ≠ 1 :=
  cast_eq_one.Not
#align nat.cast_ne_one Nat.cast_ne_one
-/

end Nat

namespace NeZero

/- warning: ne_zero.char_zero -> NeZero.charZero is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {n : Nat} [_inst_1 : NeZero.{0} Nat Nat.hasZero n] [_inst_2 : AddMonoidWithOne.{u1} M] [_inst_3 : CharZero.{u1} M _inst_2], NeZero.{u1} M (AddZeroClass.toHasZero.{u1} M (AddMonoid.toAddZeroClass.{u1} M (AddMonoidWithOne.toAddMonoid.{u1} M _inst_2))) ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat M (HasLiftT.mk.{1, succ u1} Nat M (CoeTCₓ.coe.{1, succ u1} Nat M (Nat.castCoe.{u1} M (AddMonoidWithOne.toNatCast.{u1} M _inst_2)))) n)
but is expected to have type
  forall {M : Type.{u1}} {n : Nat} [_inst_1 : NeZero.{0} Nat (Zero.ofOfNat0.{0} Nat (instOfNatNat 0)) n] [_inst_2 : AddMonoidWithOne.{u1} M] [_inst_3 : CharZero.{u1} M _inst_2], NeZero.{u1} M (AddMonoid.toZero.{u1} M (AddMonoidWithOne.toAddMonoid.{u1} M _inst_2)) (Nat.cast.{u1} M (AddMonoidWithOne.toNatCast.{u1} M _inst_2) n)
Case conversion may be inaccurate. Consider using '#align ne_zero.char_zero NeZero.charZeroₓ'. -/
instance charZero {M} {n : ℕ} [NeZero n] [AddMonoidWithOne M] [CharZero M] : NeZero (n : M) :=
  ⟨Nat.cast_ne_zero.mpr out⟩
#align ne_zero.char_zero NeZero.charZero

end NeZero


/-
Copyright (c) 2020 Eric Weiser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Weiser
-/
import algebra.star.basic
import algebra.free_algebra

/-!
# A *-algebra structure on the free algebra.

Reversing words gives a *-structure on the free monoid or on the free algebra on a type.

## Implementation note
We have this in a separate file, rather than in `algebra.free_monoid` and `algebra.free_algebra`,
to avoid importing `algebra.star.basic` into the entire hierarchy.
-/

namespace free_monoid
variables {α : Type*}

instance : star_semigroup (free_monoid α) :=
{ star := list.reverse,
  star_involutive := list.reverse_reverse,
  star_mul := list.reverse_append, }

@[simp]
lemma star_of (x : α) : star (of x) = of x := rfl

/-- Note that `star_one` is already a global simp lemma, but this one works with dsimp too -/
@[simp]
lemma star_one : star (1 : free_monoid α) = 1 := rfl

end free_monoid

namespace free_algebra
variables {R : Type*} [comm_semiring R] {X : Type*}

/-- The star ring formed by reversing the elements of products -/
instance : star_ring (free_algebra R X) :=
{ star := mul_opposite.unop ∘ lift R (mul_opposite.op ∘ ι R),
  star_involutive := λ x, by
  { unfold has_star.star,
    simp only [function.comp_apply],
    refine free_algebra.induction R X _ _ _ _ x; intros; simp [*] },
  star_mul := λ a b, by simp,
  star_add := λ a b, by simp }

@[simp]
lemma star_ι (x : X) : star (ι R x) = (ι R x) :=
by simp [star, has_star.star]

@[simp]
lemma star_algebra_map (r : R) : star (algebra_map R (free_algebra R X) r) = (algebra_map R _ r) :=
by simp [star, has_star.star]

/-- `star` as an `alg_equiv` -/
def star_hom : free_algebra R X ≃ₐ[R] (free_algebra R X)ᵐᵒᵖ :=
{ commutes' := λ r, by simp [star_algebra_map],
  ..star_ring_equiv }

end free_algebra


/-
Copyright (c) 2023 Antoine Labelle. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Antoine Labelle

! This file was ported from Lean 3 source module combinatorics.quiver.single_obj
! leanprover-community/mathlib commit 327c3c0d9232d80e250dc8f65e7835b82b266ea5
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Combinatorics.Quiver.Cast
import Mathbin.Combinatorics.Quiver.Symmetric

/-!
# Single-object quiver

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

Single object quiver with a given arrows type.

## Main definitions

Given a type `α`, `single_obj α` is the `unit` type, whose single object is called `star α`, with
`quiver` structure such that `star α ⟶ star α` is the type `α`.
An element `x : α` can be reinterpreted as an element of `star α ⟶ star α` using
`to_hom`.
More generally, a list of elements of `a` can be reinterpreted as a path from `star α` to
itself using `path_equiv_list`.
-/

namespace Quiver

#print Quiver.SingleObj /-
/-- Type tag on `unit` used to define single-object quivers. -/
@[nolint unused_arguments]
def SingleObj (α : Type _) : Type :=
  Unit deriving Unique
#align quiver.single_obj Quiver.SingleObj
-/

namespace SingleObj

variable (α β γ : Type _)

instance : Quiver (SingleObj α) :=
  ⟨fun _ _ => α⟩

#print Quiver.SingleObj.star /-
/-- The single object in `single_obj α`. -/
def star : SingleObj α :=
  Unit.unit
#align quiver.single_obj.star Quiver.SingleObj.star
-/

instance : Inhabited (SingleObj α) :=
  ⟨star α⟩

variable {α β γ}

#print Quiver.SingleObj.hasReverse /-
-- See note [reducible non-instances]
/-- Equip `single_obj α` with a reverse operation. -/
@[reducible]
def hasReverse (rev : α → α) : HasReverse (SingleObj α) :=
  ⟨fun _ _ => rev⟩
#align quiver.single_obj.has_reverse Quiver.SingleObj.hasReverse
-/

#print Quiver.SingleObj.hasInvolutiveReverse /-
-- See note [reducible non-instances]
/-- Equip `single_obj α` with an involutive reverse operation. -/
@[reducible]
def hasInvolutiveReverse (rev : α → α) (h : Function.Involutive rev) :
    HasInvolutiveReverse (SingleObj α)
    where
  toHasReverse := hasReverse rev
  inv' _ _ := h
#align quiver.single_obj.has_involutive_reverse Quiver.SingleObj.hasInvolutiveReverse
-/

#print Quiver.SingleObj.toHom /-
/-- The type of arrows from `star α` to itself is equivalent to the original type `α`. -/
@[simps]
def toHom : α ≃ (star α ⟶ star α) :=
  Equiv.refl _
#align quiver.single_obj.to_hom Quiver.SingleObj.toHom
-/

#print Quiver.SingleObj.toPrefunctor /-
/-- Prefunctors between two `single_obj` quivers correspond to functions between the corresponding
arrows types.
-/
@[simps]
def toPrefunctor : (α → β) ≃ SingleObj α ⥤q SingleObj β
    where
  toFun f := ⟨id, fun _ _ => f⟩
  invFun f a := f.map (toHom a)
  left_inv _ := rfl
  right_inv f := by cases f <;> obviously
#align quiver.single_obj.to_prefunctor Quiver.SingleObj.toPrefunctor
-/

#print Quiver.SingleObj.toPrefunctor_id /-
theorem toPrefunctor_id : toPrefunctor id = 𝟭q (SingleObj α) :=
  rfl
#align quiver.single_obj.to_prefunctor_id Quiver.SingleObj.toPrefunctor_id
-/

#print Quiver.SingleObj.toPrefunctor_symm_id /-
@[simp]
theorem toPrefunctor_symm_id : toPrefunctor.symm (𝟭q (SingleObj α)) = id :=
  rfl
#align quiver.single_obj.to_prefunctor_symm_id Quiver.SingleObj.toPrefunctor_symm_id
-/

/- warning: quiver.single_obj.to_prefunctor_comp -> Quiver.SingleObj.toPrefunctor_comp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} (f : α -> β) (g : β -> γ), Eq.{max 1 (succ u1) (succ u3)} (Prefunctor.{succ u1, succ u3, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)) (coeFn.{max 1 (succ u1) (succ u3), max 1 (succ u1) (succ u3)} (Equiv.{max (succ u1) (succ u3), max 1 (succ u1) (succ u3)} (α -> γ) (Prefunctor.{succ u1, succ u3, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ))) (fun (_x : Equiv.{max (succ u1) (succ u3), max 1 (succ u1) (succ u3)} (α -> γ) (Prefunctor.{succ u1, succ u3, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ))) => (α -> γ) -> (Prefunctor.{succ u1, succ u3, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ))) (Equiv.hasCoeToFun.{max (succ u1) (succ u3), max 1 (succ u1) (succ u3)} (α -> γ) (Prefunctor.{succ u1, succ u3, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ))) (Quiver.SingleObj.toPrefunctor.{u1, u3} α γ) (Function.comp.{succ u1, succ u2, succ u3} α β γ g f)) (Prefunctor.comp.{0, succ u1, 0, succ u2, 0, succ u3} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ) (coeFn.{max 1 (succ u1) (succ u2), max 1 (succ u1) (succ u2)} (Equiv.{max (succ u1) (succ u2), max 1 (succ u1) (succ u2)} (α -> β) (Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β))) (fun (_x : Equiv.{max (succ u1) (succ u2), max 1 (succ u1) (succ u2)} (α -> β) (Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β))) => (α -> β) -> (Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β))) (Equiv.hasCoeToFun.{max (succ u1) (succ u2), max 1 (succ u1) (succ u2)} (α -> β) (Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β))) (Quiver.SingleObj.toPrefunctor.{u1, u2} α β) f) (coeFn.{max 1 (succ u2) (succ u3), max 1 (succ u2) (succ u3)} (Equiv.{max (succ u2) (succ u3), max 1 (succ u2) (succ u3)} (β -> γ) (Prefunctor.{succ u2, succ u3, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ))) (fun (_x : Equiv.{max (succ u2) (succ u3), max 1 (succ u2) (succ u3)} (β -> γ) (Prefunctor.{succ u2, succ u3, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ))) => (β -> γ) -> (Prefunctor.{succ u2, succ u3, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ))) (Equiv.hasCoeToFun.{max (succ u2) (succ u3), max 1 (succ u2) (succ u3)} (β -> γ) (Prefunctor.{succ u2, succ u3, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ))) (Quiver.SingleObj.toPrefunctor.{u2, u3} β γ) g))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u1}} {γ : Type.{u2}} (f : α -> β) (g : β -> γ), Eq.{max (succ u3) (succ u2)} ((fun (x._@.Mathlib.Logic.Equiv.Defs._hyg.808 : α -> γ) => Prefunctor.{succ u3, succ u2, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} γ) (Quiver.SingleObj.instQuiverSingleObj.{u2} γ)) (Function.comp.{succ u3, succ u1, succ u2} α β γ g f)) (FunLike.coe.{max (succ u2) (succ u3), max (succ u2) (succ u3), max (succ u2) (succ u3)} (Equiv.{max (succ u3) (succ u2), max (succ u2) (succ u3)} (α -> γ) (Prefunctor.{succ u3, succ u2, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} γ) (Quiver.SingleObj.instQuiverSingleObj.{u2} γ))) (α -> γ) (fun (_x : α -> γ) => (fun (x._@.Mathlib.Logic.Equiv.Defs._hyg.808 : α -> γ) => Prefunctor.{succ u3, succ u2, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} γ) (Quiver.SingleObj.instQuiverSingleObj.{u2} γ)) _x) (Equiv.instFunLikeEquiv.{max (succ u2) (succ u3), max (succ u2) (succ u3)} (α -> γ) (Prefunctor.{succ u3, succ u2, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} γ) (Quiver.SingleObj.instQuiverSingleObj.{u2} γ))) (Quiver.SingleObj.toPrefunctor.{u3, u2} α γ) (Function.comp.{succ u3, succ u1, succ u2} α β γ g f)) (Prefunctor.comp.{0, succ u3, 0, succ u1, 0, succ u2} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u1} β) (Quiver.SingleObj.instQuiverSingleObj.{u1} β) (Quiver.SingleObj.{u2} γ) (Quiver.SingleObj.instQuiverSingleObj.{u2} γ) (FunLike.coe.{max (succ u1) (succ u3), max (succ u1) (succ u3), max (succ u1) (succ u3)} (Equiv.{max (succ u3) (succ u1), max (succ u1) (succ u3)} (α -> β) (Prefunctor.{succ u3, succ u1, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u1} β) (Quiver.SingleObj.instQuiverSingleObj.{u1} β))) (α -> β) (fun (_x : α -> β) => (fun (x._@.Mathlib.Logic.Equiv.Defs._hyg.808 : α -> β) => Prefunctor.{succ u3, succ u1, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u1} β) (Quiver.SingleObj.instQuiverSingleObj.{u1} β)) _x) (Equiv.instFunLikeEquiv.{max (succ u1) (succ u3), max (succ u1) (succ u3)} (α -> β) (Prefunctor.{succ u3, succ u1, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u1} β) (Quiver.SingleObj.instQuiverSingleObj.{u1} β))) (Quiver.SingleObj.toPrefunctor.{u3, u1} α β) f) (FunLike.coe.{max (succ u2) (succ u1), max (succ u2) (succ u1), max (succ u2) (succ u1)} (Equiv.{max (succ u1) (succ u2), max (succ u2) (succ u1)} (β -> γ) (Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} β) (Quiver.SingleObj.instQuiverSingleObj.{u1} β) (Quiver.SingleObj.{u2} γ) (Quiver.SingleObj.instQuiverSingleObj.{u2} γ))) (β -> γ) (fun (_x : β -> γ) => (fun (x._@.Mathlib.Logic.Equiv.Defs._hyg.808 : β -> γ) => Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} β) (Quiver.SingleObj.instQuiverSingleObj.{u1} β) (Quiver.SingleObj.{u2} γ) (Quiver.SingleObj.instQuiverSingleObj.{u2} γ)) _x) (Equiv.instFunLikeEquiv.{max (succ u2) (succ u1), max (succ u2) (succ u1)} (β -> γ) (Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} β) (Quiver.SingleObj.instQuiverSingleObj.{u1} β) (Quiver.SingleObj.{u2} γ) (Quiver.SingleObj.instQuiverSingleObj.{u2} γ))) (Quiver.SingleObj.toPrefunctor.{u1, u2} β γ) g))
Case conversion may be inaccurate. Consider using '#align quiver.single_obj.to_prefunctor_comp Quiver.SingleObj.toPrefunctor_compₓ'. -/
theorem toPrefunctor_comp (f : α → β) (g : β → γ) :
    toPrefunctor (g ∘ f) = toPrefunctor f ⋙q toPrefunctor g :=
  rfl
#align quiver.single_obj.to_prefunctor_comp Quiver.SingleObj.toPrefunctor_comp

/- warning: quiver.single_obj.to_prefunctor_symm_comp -> Quiver.SingleObj.toPrefunctor_symm_comp is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} (f : Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β)) (g : Prefunctor.{succ u2, succ u3, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)), Eq.{max (succ u1) (succ u3)} (α -> γ) (coeFn.{max 1 (succ u1) (succ u3), max 1 (succ u1) (succ u3)} (Equiv.{max 1 (succ u1) (succ u3), max (succ u1) (succ u3)} (Prefunctor.{succ u1, succ u3, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)) (α -> γ)) (fun (_x : Equiv.{max 1 (succ u1) (succ u3), max (succ u1) (succ u3)} (Prefunctor.{succ u1, succ u3, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)) (α -> γ)) => (Prefunctor.{succ u1, succ u3, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)) -> α -> γ) (Equiv.hasCoeToFun.{max 1 (succ u1) (succ u3), max (succ u1) (succ u3)} (Prefunctor.{succ u1, succ u3, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)) (α -> γ)) (Equiv.symm.{max (succ u1) (succ u3), max 1 (succ u1) (succ u3)} (α -> γ) (Prefunctor.{succ u1, succ u3, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)) (Quiver.SingleObj.toPrefunctor.{u1, u3} α γ)) (Prefunctor.comp.{0, succ u1, 0, succ u2, 0, succ u3} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ) f g)) (Function.comp.{succ u1, succ u2, succ u3} α β γ (coeFn.{max 1 (succ u2) (succ u3), max 1 (succ u2) (succ u3)} (Equiv.{max 1 (succ u2) (succ u3), max (succ u2) (succ u3)} (Prefunctor.{succ u2, succ u3, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)) (β -> γ)) (fun (_x : Equiv.{max 1 (succ u2) (succ u3), max (succ u2) (succ u3)} (Prefunctor.{succ u2, succ u3, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)) (β -> γ)) => (Prefunctor.{succ u2, succ u3, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)) -> β -> γ) (Equiv.hasCoeToFun.{max 1 (succ u2) (succ u3), max (succ u2) (succ u3)} (Prefunctor.{succ u2, succ u3, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)) (β -> γ)) (Equiv.symm.{max (succ u2) (succ u3), max 1 (succ u2) (succ u3)} (β -> γ) (Prefunctor.{succ u2, succ u3, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β) (Quiver.SingleObj.{u3} γ) (Quiver.SingleObj.quiver.{u3} γ)) (Quiver.SingleObj.toPrefunctor.{u2, u3} β γ)) g) (coeFn.{max 1 (succ u1) (succ u2), max 1 (succ u1) (succ u2)} (Equiv.{max 1 (succ u1) (succ u2), max (succ u1) (succ u2)} (Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β)) (α -> β)) (fun (_x : Equiv.{max 1 (succ u1) (succ u2), max (succ u1) (succ u2)} (Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β)) (α -> β)) => (Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β)) -> α -> β) (Equiv.hasCoeToFun.{max 1 (succ u1) (succ u2), max (succ u1) (succ u2)} (Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β)) (α -> β)) (Equiv.symm.{max (succ u1) (succ u2), max 1 (succ u1) (succ u2)} (α -> β) (Prefunctor.{succ u1, succ u2, 0, 0} (Quiver.SingleObj.{u1} α) (Quiver.SingleObj.quiver.{u1} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.quiver.{u2} β)) (Quiver.SingleObj.toPrefunctor.{u1, u2} α β)) f))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u2}} {γ : Type.{u1}} (f : Prefunctor.{succ u3, succ u2, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β)) (g : Prefunctor.{succ u2, succ u1, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)), Eq.{max (succ u3) (succ u1)} ((fun (x._@.Mathlib.Logic.Equiv.Defs._hyg.808 : Prefunctor.{succ u3, succ u1, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) => α -> γ) (Prefunctor.comp.{0, succ u3, 0, succ u2, 0, succ u1} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ) f g)) (FunLike.coe.{max (succ u1) (succ u3), max (succ u1) (succ u3), max (succ u1) (succ u3)} (Equiv.{max (succ u1) (succ u3), max (succ u1) (succ u3)} (Prefunctor.{succ u3, succ u1, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) (α -> γ)) (Prefunctor.{succ u3, succ u1, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) (fun (_x : Prefunctor.{succ u3, succ u1, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) => (fun (x._@.Mathlib.Logic.Equiv.Defs._hyg.808 : Prefunctor.{succ u3, succ u1, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) => α -> γ) _x) (Equiv.instFunLikeEquiv.{max (succ u1) (succ u3), max (succ u1) (succ u3)} (Prefunctor.{succ u3, succ u1, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) (α -> γ)) (Equiv.symm.{max (succ u1) (succ u3), max (succ u1) (succ u3)} (α -> γ) (Prefunctor.{succ u3, succ u1, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) (Quiver.SingleObj.toPrefunctor.{u3, u1} α γ)) (Prefunctor.comp.{0, succ u3, 0, succ u2, 0, succ u1} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ) f g)) (Function.comp.{succ u3, succ u2, succ u1} α β γ (FunLike.coe.{max (succ u1) (succ u2), max (succ u1) (succ u2), max (succ u1) (succ u2)} (Equiv.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Prefunctor.{succ u2, succ u1, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) (β -> γ)) (Prefunctor.{succ u2, succ u1, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) (fun (_x : Prefunctor.{succ u2, succ u1, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) => (fun (x._@.Mathlib.Logic.Equiv.Defs._hyg.808 : Prefunctor.{succ u2, succ u1, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) => β -> γ) _x) (Equiv.instFunLikeEquiv.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (Prefunctor.{succ u2, succ u1, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) (β -> γ)) (Equiv.symm.{max (succ u1) (succ u2), max (succ u1) (succ u2)} (β -> γ) (Prefunctor.{succ u2, succ u1, 0, 0} (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β) (Quiver.SingleObj.{u1} γ) (Quiver.SingleObj.instQuiverSingleObj.{u1} γ)) (Quiver.SingleObj.toPrefunctor.{u2, u1} β γ)) g) (FunLike.coe.{max (succ u2) (succ u3), max (succ u2) (succ u3), max (succ u2) (succ u3)} (Equiv.{max (succ u2) (succ u3), max (succ u2) (succ u3)} (Prefunctor.{succ u3, succ u2, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β)) (α -> β)) (Prefunctor.{succ u3, succ u2, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β)) (fun (_x : Prefunctor.{succ u3, succ u2, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β)) => (fun (x._@.Mathlib.Logic.Equiv.Defs._hyg.808 : Prefunctor.{succ u3, succ u2, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β)) => α -> β) _x) (Equiv.instFunLikeEquiv.{max (succ u2) (succ u3), max (succ u2) (succ u3)} (Prefunctor.{succ u3, succ u2, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β)) (α -> β)) (Equiv.symm.{max (succ u2) (succ u3), max (succ u2) (succ u3)} (α -> β) (Prefunctor.{succ u3, succ u2, 0, 0} (Quiver.SingleObj.{u3} α) (Quiver.SingleObj.instQuiverSingleObj.{u3} α) (Quiver.SingleObj.{u2} β) (Quiver.SingleObj.instQuiverSingleObj.{u2} β)) (Quiver.SingleObj.toPrefunctor.{u3, u2} α β)) f))
Case conversion may be inaccurate. Consider using '#align quiver.single_obj.to_prefunctor_symm_comp Quiver.SingleObj.toPrefunctor_symm_compₓ'. -/
@[simp]
theorem toPrefunctor_symm_comp (f : SingleObj α ⥤q SingleObj β) (g : SingleObj β ⥤q SingleObj γ) :
    toPrefunctor.symm (f ⋙q g) = toPrefunctor.symm g ∘ toPrefunctor.symm f := by
  simp only [Equiv.symm_apply_eq, to_prefunctor_comp, Equiv.apply_symm_apply]
#align quiver.single_obj.to_prefunctor_symm_comp Quiver.SingleObj.toPrefunctor_symm_comp

#print Quiver.SingleObj.pathToList /-
/-- Auxiliary definition for `quiver.single_obj.path_equiv_list`.
Converts a path in the quiver `single_obj α` into a list of elements of type `a`.
-/
@[simp]
def pathToList : ∀ {x : SingleObj α}, Path (star α) x → List α
  | _, path.nil => []
  | _, path.cons p a => a :: path_to_list p
#align quiver.single_obj.path_to_list Quiver.SingleObj.pathToList
-/

#print Quiver.SingleObj.listToPath /-
/-- Auxiliary definition for `quiver.single_obj.path_equiv_list`.
Converts a list of elements of type `α` into a path in the quiver `single_obj α`.
-/
@[simp]
def listToPath : List α → Path (star α) (star α)
  | [] => Path.nil
  | a :: l => (list_to_path l).cons a
#align quiver.single_obj.list_to_path Quiver.SingleObj.listToPath
-/

#print Quiver.SingleObj.listToPath_pathToList /-
theorem listToPath_pathToList {x : SingleObj α} (p : Path (star α) x) :
    listToPath (pathToList p) = p.cast rfl Unit.ext :=
  by
  induction' p with y z p a ih
  rfl
  tidy
#align quiver.single_obj.path_to_list_to_path Quiver.SingleObj.listToPath_pathToList
-/

#print Quiver.SingleObj.pathToList_listToPath /-
theorem pathToList_listToPath (l : List α) : pathToList (listToPath l) = l :=
  by
  induction' l with a l ih
  rfl
  simp [ih]
#align quiver.single_obj.list_to_path_to_list Quiver.SingleObj.pathToList_listToPath
-/

#print Quiver.SingleObj.pathEquivList /-
/-- Paths in `single_obj α` quiver correspond to lists of elements of type `α`. -/
def pathEquivList : Path (star α) (star α) ≃ List α :=
  ⟨pathToList, listToPath, fun p => listToPath_pathToList p, pathToList_listToPath⟩
#align quiver.single_obj.path_equiv_list Quiver.SingleObj.pathEquivList
-/

#print Quiver.SingleObj.pathEquivList_nil /-
@[simp]
theorem pathEquivList_nil : pathEquivList Path.nil = ([] : List α) :=
  rfl
#align quiver.single_obj.path_equiv_list_nil Quiver.SingleObj.pathEquivList_nil
-/

#print Quiver.SingleObj.pathEquivList_cons /-
@[simp]
theorem pathEquivList_cons (p : Path (star α) (star α)) (a : star α ⟶ star α) :
    pathEquivList (Path.cons p a) = a :: pathToList p :=
  rfl
#align quiver.single_obj.path_equiv_list_cons Quiver.SingleObj.pathEquivList_cons
-/

#print Quiver.SingleObj.pathEquivList_symm_nil /-
@[simp]
theorem pathEquivList_symm_nil : pathEquivList.symm ([] : List α) = Path.nil :=
  rfl
#align quiver.single_obj.path_equiv_list_symm_nil Quiver.SingleObj.pathEquivList_symm_nil
-/

#print Quiver.SingleObj.pathEquivList_symm_cons /-
@[simp]
theorem pathEquivList_symm_cons (l : List α) (a : α) :
    pathEquivList.symm (a :: l) = Path.cons (pathEquivList.symm l) a :=
  rfl
#align quiver.single_obj.path_equiv_list_symm_cons Quiver.SingleObj.pathEquivList_symm_cons
-/

end SingleObj

end Quiver


-- import tactic

inductive typ : Type
| num : typ
| str : typ

inductive exp : Type
| var : string → exp
| lit_num : ℕ → exp
| lit_str : string → exp
| plus : exp → exp → exp
| times : exp → exp → exp
| cat : exp → exp → exp
| len : exp → exp
| elet : string → exp → exp → exp

open exp
open typ

-- Statics:
def context := (string → option typ)
def update_context (Γ:context) (x:string) (t:typ) : context :=
  λ y,
  if x = y then
  some t
  else Γ y

notation x `↦` τ `;` Γ := update_context Γ x τ

inductive has_type : context → exp → typ → Prop
| t_var (Γ:context) (x:string) (t:typ) : (Γ x = some t) → has_type Γ (var x) t
| t_lit_str (s:string) (Γ:context) : has_type Γ (lit_str s) str
| t_lit_num: ∀ (n:ℕ) Γ, has_type Γ (lit_num n) num
| t_plus: ∀ (e1 e2:exp) Γ,  has_type Γ e1 num → has_type Γ e2 num →
                        has_type Γ (plus e1 e2) num
| t_times: ∀ (e1 e2:exp) Γ, has_type Γ e1 num → has_type Γ e2 num →
                        has_type Γ (times e1 e2) num
| t_cat: ∀ (e1 e2:exp) Γ, has_type Γ e1 str → has_type Γ e2 str → has_type Γ (cat e1 e2) str
| t_len: ∀ (e1:exp) Γ, has_type Γ e1 str → has_type Γ (len e1) num
| t_let: ∀ x (e1 e2:exp) Γ τ1 τ2 (He1 : has_type Γ e1 τ1) (He2:has_type (x ↦ τ1 ; Γ) e2 τ2),
                        has_type Γ (elet x e1 e2) τ2

def substitute (x':string) (e':exp) : exp → exp
| (var y) := if (x' = y) then e' else var y
| (lit_num n) := lit_num n
| (lit_str s) := lit_str s
| (plus e1 e2) := plus (substitute e1) (substitute e2)
| (times e1 e2) := times (substitute e1) (substitute e2)
| (cat e1 e2) := cat (substitute e1) (substitute e2)
| (len e1) := len (substitute e1)
| (elet y e1 e2) := if (x' = y) then elet y (substitute e1) e2 else elet y (substitute e1) (substitute e2)

-- Dynamics
inductive is_val : exp → Prop
| v_num : ∀ n, is_val (lit_num n)
| v_str : ∀ s, is_val (lit_str s)

-- structural dynamics
inductive is_step : exp → exp → Prop
| s_plus : ∀ (n1 n2 n:ℕ), n = n1 + n2 → is_step (plus (lit_num n1) (lit_num n2)) (lit_num n)
| s_times : ∀ (n1 n2 n:ℕ), n = n1 * n2 → is_step (times (lit_num n1) (lit_num n2)) (lit_num n)
| s_cat : ∀ (s1 s2 s:string), s = s1 ++ s2 → is_step (cat (lit_str s1) (lit_str s2)) (lit_str s)
| s_len : ∀ s, is_step (len (lit_str s)) (lit_num (string.length s))
| s_let : ∀ x e1 e2, is_step (elet x e1 e2) (substitute x e1 e2) -- NOTE: this is call by-name
-- the rest of these are the "search" transitions
| s_plus_l : ∀ e1 e2 e1', is_step e1 e1' → is_step (plus e1 e2) (plus e1' e2)
| s_plus_r : ∀ v1 e2 e2', is_val v1 → is_step e2 e2' → is_step (plus v1 e2) (plus v1 e2')
| s_times_l : ∀ e1 e2 e1', is_step e1 e1' → is_step (times e1 e2) (times e1' e2)
| s_times_r : ∀ v1 e2 e2', is_val v1 → is_step e2 e2' → is_step (times v1 e2) (times v1 e2')
| s_cat_l : ∀ e1 e2 e1', is_step e1 e1' → is_step (cat e1 e2) (cat e1' e2)
| s_cat_r : ∀ v1 e2 e2', is_val v1 → is_step e2 e2' → is_step (cat v1 e2) (cat v1 e2')
| s_len_i : ∀ e1 e1', is_step e1 e1' → is_step (len e1) (len e1')

-- contextual dynamics
inductive ectx
| empty : ectx
| plus_l (E:ectx) (e2:exp) : ectx
| plus_r (e1:exp) (E:ectx) : is_val e1 → ectx
| times_l (E:ectx) (e2:exp) : ectx
| times_r (e1:exp) (E:ectx) : is_val e1 → ectx
| cat_l (E:ectx) (e2:exp) : ectx
| cat_r (e1:exp) (E:ectx) : is_val e1 → ectx
| len (E:ectx) : ectx

def instantiate_ectx : ectx → exp → exp
| ectx.empty e := e
| (ectx.plus_l E1 e2) e := exp.plus (instantiate_ectx E1 e) e2
| (ectx.plus_r e1 E2 _) e := exp.plus e1 (instantiate_ectx E2 e)
| (ectx.times_l E1 e2) e := exp.times (instantiate_ectx E1 e) e2
| (ectx.times_r e1 E2 _) e := exp.times e1 (instantiate_ectx E2 e)
| (ectx.cat_l E1 e2) e := exp.cat (instantiate_ectx E1 e) e2
| (ectx.cat_r e1 E2 _) e := exp.cat e1 (instantiate_ectx E2 e)
| (ectx.len E) e := exp.len (instantiate_ectx E e)

notation `∘` := ectx.empty
notation E `{` e `}` := instantiate_ectx E e

-- structural dynamics
inductive is_step_instr : exp → exp → Prop
| s_plus : ∀ (n1 n2 n:ℕ), n = n1 + n2 → is_step_instr (plus (lit_num n1) (lit_num n2)) (lit_num n)
| s_times : ∀ (n1 n2 n:ℕ), n = n1 * n2 → is_step_instr (times (lit_num n1) (lit_num n2)) (lit_num n)
| s_cat : ∀ (s1 s2 s:string), s = s1 ++ s2 → is_step_instr (cat (lit_str s1) (lit_str s2)) (lit_str s)
| s_len : ∀ s, is_step_instr (len (lit_str s)) (lit_num (string.length s))
| s_let : ∀ x e1 e2, is_step_instr (elet x e1 e2) (substitute x e1 e2) -- NOTE: this is call by-name

inductive is_step_contextual (e e':exp) : Prop
| s_ctx : ∀ E e0 e0', e = E{e0} → e' = E{e0'} → is_step_instr e0 e0' → is_step_contextual

notation e ` ↦ctx ` e' := is_step_contextual e e'
notation e ` ↦str ` e' := is_step e e'

lemma instr_implies_structural :
∀ e e',
is_step_instr e e' →
is_step e e' :=
begin
  introv Hinstr,
  induction Hinstr,
  { constructor, assumption },
  { constructor, assumption },
  { constructor, assumption },
  { constructor },
  { constructor },
end

theorem structural_eq_contextual :
∀ e e', (e ↦str e') ↔ (e ↦ctx e') :=
begin
  intros,
  split,
  { -- str → ctx
    intros Hstep,
    induction Hstep,
    repeat {
      apply (is_step_contextual.s_ctx ectx.empty); { constructor, try {assumption} }
    },
    -- deal with non-trivial ectx's
    {
      destruct Hstep_ih,
      intros E1 _ _ Heq1 Heq2 Hinst,
      apply (is_step_contextual.s_ctx (ectx.plus_l E1 _)),
      { unfold instantiate_ectx, rw Heq1, },
      { unfold instantiate_ectx, rw Heq2, },
      assumption,
    },
    {
      destruct Hstep_ih,
      intros E2 _ _ Heq1 Heq2 Hinst,
      apply (is_step_contextual.s_ctx (ectx.plus_r Hstep_v1 E2 _)),
      { unfold instantiate_ectx, rw Heq1, },
      { unfold instantiate_ectx, rw Heq2, },
      assumption,
      assumption,
    },
    {
      destruct Hstep_ih,
      intros E1 _ _ Heq1 Heq2 Hinst,
      apply (is_step_contextual.s_ctx (ectx.times_l E1 _)),
      { unfold instantiate_ectx, rw Heq1, },
      { unfold instantiate_ectx, rw Heq2, },
      assumption,
    },
    {
      destruct Hstep_ih,
      intros E2 _ _ Heq1 Heq2 Hinst,
      apply (is_step_contextual.s_ctx (ectx.times_r Hstep_v1 E2 _)),
      { unfold instantiate_ectx, rw Heq1, },
      { unfold instantiate_ectx, rw Heq2, },
      assumption,
      assumption,
    },
    {
      destruct Hstep_ih,
      intros E1 _ _ Heq1 Heq2 Hinst,
      apply (is_step_contextual.s_ctx (ectx.cat_l E1 _)),
      { unfold instantiate_ectx, rw Heq1, },
      { unfold instantiate_ectx, rw Heq2, },
      assumption,
    },
    {
      destruct Hstep_ih,
      intros E2 _ _ Heq1 Heq2 Hinst,
      apply (is_step_contextual.s_ctx (ectx.cat_r Hstep_v1 E2 _)),
      { unfold instantiate_ectx, rw Heq1, },
      { unfold instantiate_ectx, rw Heq2, },
      assumption,
      assumption,
    },
    {
      destruct Hstep_ih,
      intros E _ _ Heq1 Heq2 Hinst,
      apply (is_step_contextual.s_ctx (ectx.len E)),
      { unfold instantiate_ectx, rw Heq1, },
      { unfold instantiate_ectx, rw Heq2, },
      assumption,
    },
  },
  { -- other direction
    intros Hstep,
    destruct Hstep,
    clear Hstep,
    introv Heq1 Heq2 Hinstr,
    revert e e',
    induction E,
    {
      introv Heq1 Heq2,
      rw [Heq1, Heq2],
      unfold instantiate_ectx,
      apply instr_implies_structural,
      assumption,
    },
    repeat {
      introv Heq1 Heq2,
      rw [Heq1, Heq2],
      unfold instantiate_ectx,
      constructor, try { assumption },
      apply E_ih; refl
    },
  }
end

-- FIXME: notation needs to be surrounded by parens to work correctly
notation Γ ` ⊢ `:90 e:90 ` : `:90 τ:90 := has_type Γ e τ

theorem typing_unicity :
∀ (Γ:context) e (τa τb:typ),
Γ ⊢ e : τa →
has_type Γ e τb →
(τa = τb) :=
begin
intros Γ _ _ _ H1 H2,
revert τb,
induction H1,
case has_type.t_let : x e1 e2 Γ _ _ He1 He2 Hih1 Hih2 {
  dedup,
  intros τb H2,
  cases H2,
  specialize Hih1 H2_τ1 H2_He1,
  subst Hih1,
  specialize Hih2 _ H2_He2,
  assumption,
},
{
  intros τb H2,
  cases H2,
  rw H2_ᾰ at H1_ᾰ,
  injection H1_ᾰ,
  rw h_1,
},
{
  intros τb H2,
  cases H2,
  refl,
},
{
  intros τb H2,
  cases H2,
  refl,
},
{
  intros τb H2,
  cases H2,
  refl,
},
{
  intros τb H2,
  cases H2,
  refl,
},
{
  intros τb H2,
  cases H2,
  refl,
},
{
  intros τb H2,
  cases H2,
  refl,
}
end

def context_included (Γ Γ':context) : Prop :=
  ∀ x y,
    Γ x = some y → Γ' x = some y

def included_update :
∀ Γ Γ' x τ,
 context_included Γ Γ' →
 context_included (x ↦ τ ; Γ) (x ↦ τ ; Γ')
 :=
begin
 introv,
 intros Hinc,
 unfold context_included update_context at *,
 intros x1 y Hlookup,
 by_cases (x = x1),
 { rwa if_pos at *; assumption,
 },
 rw if_neg at * ; try { assumption },
 { apply Hinc, assumption }
end

-- XXX: we aren't modding out by α-equivalence, so the statement is slightly
-- stronger so that the inductive hypothesis is stronger; without this,
-- weakening for `let` doesn't work.
theorem weakening' :
∀ (Γ Γ':context) e' τ',
context_included Γ Γ' →
Γ ⊢ e' : τ' →
Γ' ⊢ e' : τ'
:=
begin
  introv,
  intros Hinc Hty,
  revert Γ',
  induction Hty,
  case has_type.t_let : x e1 e2 Γ _ _ He1 He2 Hih1 Hih2 {
    intros Γ' Hinc,
    specialize Hih1 _ Hinc,
    specialize Hih2 (x ↦ Hty_τ1 ; Γ') _,
    {
      apply included_update,
      assumption,
    },
    apply has_type.t_let,
    assumption,
    assumption,
  },
  {
    intros Γ' Hinc,
    apply has_type.t_var,
    apply Hinc,
    assumption,
  },
  {
    intros Γ' Hinc,
    apply has_type.t_lit_str,
  },
  {
    intros Γ' Hinc,
    apply has_type.t_lit_num,
  },
  {
    intros Γ' Hinc,
    apply has_type.t_plus,
    { apply Hty_ih_ᾰ, assumption },
    { apply Hty_ih_ᾰ_1, assumption },
  },
  {
    intros Γ' Hinc,
    apply has_type.t_times,
    { apply Hty_ih_ᾰ, assumption },
    { apply Hty_ih_ᾰ_1, assumption },
  },
  {
    intros Γ' Hinc,
    apply has_type.t_cat,
    { apply Hty_ih_ᾰ, assumption },
    { apply Hty_ih_ᾰ_1, assumption },
  },
  {
    intros Γ' Hinc,
    apply has_type.t_len,
    { apply Hty_ih, assumption },
  },
end

def empty_ctx : context := λ _, none

-- FIXME: This lemma is false.
-- Counterexample:
-- x:str ⊢ let x := len(x) in (x + x) : num
-- ⊢ let x := len(x) in (x + x) : num
--
-- Let's use this to get a counterexample to type-safety:
-- let x := "some string" in
--   let x := len(x) in (x + x)
--
-- This type-checks to type num. But, the dynamics will get stuck after
-- substituting the first let, because the substitution won't do anything
-- because x "shadows" the other x.
--
-- Here's how to fix it TODO:
-- The type system should be aware of how shadowing works. So,
-- let x := e1 in (let x := (e1') in e2') should actually evaluate to
-- (let x := [x/e1]e1' in e2'). The substitution should happen on the argument,
-- but not on the "body" of the "let"

-- FIXME: Another bug:
-- The proof wants to make use of the (false) fact that Γ ⊢ e:τ →
-- y↦τ';Γ ⊢ e:τ. Counterexample
--  (y:string) ⊢ len(y) : num
--  ¬((y:num) (y:string) ⊢ len(y) : num)
--
-- let y := e in e2
--
-- let x := len(y) in (x + x)
--
-- Γ ⊢ x : τ
-- (x↦τ; Γ) ⊢ let y:=
--
-- (partial) proof context
-- Hty'_ih_He2 :
--   ∀ (Γ_1 : context),
--     Γ_1 ⊢ e : τ → (x↦τ;Γ_1) = (Hty'_x↦Hty'_τ1;(x↦τ;Γ)) → Γ_1 ⊢ substitute x e Hty'_e2 : Hty'_τ2,
-- Hty'_ih_He1 : Γ ⊢ substitute x e Hty'_e1 : Hty'_τ1,
-- h : ¬Hty'_x = x
-- ⊢ Γ ⊢ elet Hty'_x (substitute x e Hty'_e1) (substitute x e Hty'_e2) : Hty'_τ2
--
-- Given:
-- Γ ⊢ ex : τ
-- (x↦τ;Γ) ⊢ let (y := ey) in e2
-- WTS:
-- (y↦τy;Γ) ⊢ ex : τ, which will help prove
-- Γ ⊢ let (y := [x/ex]ey) in [x/ex]e2
--
-- Example with Γ ⊢ ex:τ but ¬((y↦τy;Γ) ⊢ ex:τ):
-- Γ=(y:string), τ=num, τy=num, ex=len(y)
-- Γ ⊢ len(y)
--
-- Γ ⊢ len(y):num
-- (x:num);Γ ⊢ let (y := 10) in (y + x)
-- WTS: Γ ⊢ subst x (len(y))  let (y:=10) in (y + x)
--    ↔ Γ ⊢ let y:=10 in y + len(y)
--
-- This is what we might hope corresponds to the counterexample:
-- let y:="blah" in
--  let x:=len(y) in
--   (let y:=10 in (y + x))
--
-- But, when y:=blah if first substituted, and by the time we're looking at
-- (let x:=len("blah") in ...) there's no problem redefining y to a different type.
-- This would only be problematic if we allowed the substitution (x:=len(y)) to
-- happen before the outer substitution (y:="blah"). But because that can't
-- happen, we can conclude that for type safety, we only care about substituting
-- CLOSED terms, so can weaken this lemma to the case where e' is typed with
-- empty_ctx. TODO

lemma substitution_property :
  ∀ Γ e τ x e' τ',
  empty_ctx ⊢ e : τ →
  ((x ↦ τ; Γ) ⊢ e' : τ') →
  Γ ⊢ (substitute x e e') : τ'
  :=
begin
  introv Hty Hty',
  -- XXX: doing this so the induction doesn't mess stuff up
  generalize h : (x↦τ;Γ) = y,
  rw h at *,
  induction Hty' generalizing Γ,
  {
    subst h,
    unfold substitute,
    by_cases (x = Hty'_x),
    {
      subst h,
      unfold update_context at *,
      simp at *,
      rw <- Hty'_ᾰ,
      apply weakening',
      tactic.swap, assumption,
      intros x y He, exfalso,
      unfold empty_ctx at *,
      contradiction,
    },
    {
      unfold update_context at *,
      rw if_neg at *, tactic.swap,
      { assumption },
      { constructor, assumption },
      { assumption },
    },
  },
  repeat { constructor, done }, -- for literals
  repeat {
    subst h,
    unfold substitute,
    constructor,
    { apply Hty'_ih_ᾰ, trivial, },
    { apply Hty'_ih_ᾰ_1, trivial, },
  },
  {
    subst h,
    unfold substitute,
    apply has_type.t_len,
    apply Hty'_ih,
    trivial,
  },
  {
    subst h,
    unfold substitute,

    specialize (Hty'_ih_He1 _ _),
    tactic.rotate_right 2,
    { refl },

    by_cases (Hty'_x = x),
    { -- XXX: this is where let variable shadowing matters
      subst Hty'_x,
      simp *,
      -- in this case, we can prove that τ = Hty'_τ1
      constructor,
      { apply Hty'_ih_He1 },
      {
        have heq : ((x↦Hty'_τ1;(x↦τ;Γ)) = (x↦Hty'_τ1 ; Γ)),
        {
          apply funext,
          intros,
          unfold update_context,
          by_cases (x = x_1),
          { simp *, },
          { repeat { rw if_neg h }, }
        },
        rw <- heq,
        assumption
      }
    },
    {
      specialize (Hty'_ih_He2 (Hty'_x↦Hty'_τ1;Γ) _),
      tactic.rotate_right 2,
      {
        apply funext,
        intros,
        unfold update_context,
        by_cases Hx : (x = x_1),
        {
          simp *,
          rw <- Hx,
          rw if_neg,
          assumption
        },
        { repeat { rw (if_neg Hx) } },
      },
      rw if_neg, tactic.swap,
      { by_cases (x = Hty'_x), rw h at *, contradiction,
        assumption },
      constructor,
      { assumption },
      { assumption },
    },
  }
end

-- This does induction over the typing relation, and "inversion" (really cases)
-- over step relation. This is not the best way to prove preservation.
theorem type_preservation1 :
  ∀ e τ e',
  empty_ctx ⊢ e : τ →
  (e ↦str e') →
  empty_ctx ⊢ e' : τ :=
begin
  introv Hty Hstep,
  revert e',
  generalize h : empty_ctx = Γ,
  rw h at *,
  induction Hty; try { by { introv Hstep, exfalso; cases Hstep }};
  try {
    subst h,
    introv Hstep,
    cases Hstep,
    { constructor },
    { constructor,
      { apply Hty_ih_ᾰ, refl, assumption },
      { assumption },
    },
    { constructor,
      { assumption },
      { apply Hty_ih_ᾰ_1, refl, assumption },
    }
  },
  {
    subst h,
    introv Hstep,
    cases Hstep,
    { constructor },
    { constructor,
      apply Hty_ih,
      refl,
      assumption
    }
  },
  {
    subst h,
    introv Hstep,
    cases Hstep,
    {
      apply substitution_property,
      { assumption },
      { assumption },
    }
  },
end

-- This version does induction over the step relation. Inversion for typing
-- judgements is nice (e.g. cases Hty turns the unknown τ into something more
-- specific), but there are more top-level cases because there are more step
-- rules than typing rules.
theorem type_preservation2 :
  ∀ e τ e',
  empty_ctx ⊢ e : τ →
  (e ↦str e') →
  empty_ctx ⊢ e' : τ :=
begin
  introv Hty Hstep,
  revert τ,
  induction Hstep;
  try { by {
    intros τ Hty,
    cases Hty,
    constructor
  }},
  {
    introv Hty, cases Hty,
    apply substitution_property; assumption
  },
  repeat {
    introv Hty, cases Hty,
    constructor; assumption <|> {apply Hstep_ih, assumption }
  },
end

-- This version uses contextual dynamics.
theorem type_preservation3 :
  ∀ e τ e',
  empty_ctx ⊢ e : τ →
  (e ↦ctx e') →
  empty_ctx ⊢ e' : τ :=
begin
  introv Hty Hstep,
  destruct Hstep,
  clear Hstep,
  introv Heq1 Heq2 Hstep,
  revert τ e e',
  induction E,
  {
    intros τ e e' Hty Heq1 Heq2,
    subst Heq1,
    subst Heq2,
    unfold instantiate_ectx at *,
    cases Hstep,
    repeat { cases Hty, constructor },
    cases Hty,
    apply substitution_property; assumption
  },
  repeat {
    intros τ e e' Hty Heq1 Heq2,
    subst Heq1, subst Heq2,
    unfold instantiate_ectx at *,
    cases Hty,
    constructor,
    repeat { assumption <|> { apply E_ih; try {refl}, assumption } }
  },
end

def canonical_form : typ → exp → Prop
| num e := ∃ n, e = lit_num n
| str e := ∃ s, e = lit_str s
.

lemma canonical_forms :
  ∀ e τ,
  empty_ctx ⊢ e : τ →
  is_val e →
  canonical_form τ e :=
begin
introv Hty Hval,
destruct τ,
repeat {
  introv Hre, subst Hre,
  cases Hval; cases Hty,
  existsi _, refl
}
end

lemma progress :
  ∀ e τ,
  empty_ctx ⊢ e : τ →
  is_val e ∨ (∃ e', e ↦str e') :=
begin
  introv Hty,
  generalize h : empty_ctx = y,
  rw h at *,
  induction Hty with Γ; subst h,
  { exfalso, unfold empty_ctx at *, contradiction },
  { left, constructor },
  { left, constructor },
  repeat { simp at *,
    right,
    specialize Hty_ih_ᾰ _, trivial,
    specialize Hty_ih_ᾰ_1 _, trivial,
    cases Hty_ih_ᾰ,
    { -- case: e1 can't take a step
      cases Hty_ih_ᾰ_1,
      {
        have Hnum1 : _ := (canonical_forms _ _ Hty_ᾰ Hty_ih_ᾰ),
        unfold canonical_form at Hnum1,
        cases Hnum1 with n1,
        subst Hnum1_h,
        have Hnum2 : _ := (canonical_forms _ _ Hty_ᾰ_1 Hty_ih_ᾰ_1),
        cases Hnum2 with n2,
        subst Hnum2_h,
        constructor,
        { constructor, refl }
      },
      { -- case: e2 can take a step
        cases Hty_ih_ᾰ_1,
        existsi _,
        { apply is_step.s_plus_r <|>
          apply is_step.s_times_r <|>
          apply is_step.s_cat_r,
          repeat {assumption}
        }
      }
    },
    { -- case: e1 can take a step
      cases Hty_ih_ᾰ,
      existsi _,
      { apply is_step.s_plus_l <|>
        apply is_step.s_times_l <|>
        apply is_step.s_cat_l, assumption }
    }
  },
  {
    right,
    specialize Hty_ih _, trivial,
    cases Hty_ih,
    { -- go from len("literal") to 7
      have Hstr1: _ := (canonical_forms _ _ Hty_ᾰ Hty_ih),
      unfold canonical_form at Hstr1,
      cases Hstr1 with s,
      subst Hstr1_h,
      existsi _,
      apply is_step.s_len,
    },
    { -- take a step inside the `len`
      cases Hty_ih with e',
      existsi _,
      apply is_step.s_len_i,
      assumption,
    }
  },
  { -- let binding
    right,
    existsi _,
    apply is_step.s_let,
  },
end

theorem type_safety :
  ∀ e τ,
  empty_ctx ⊢ e : τ →
 (∀ e', (e ↦str e') → empty_ctx ⊢ e' : τ) ∧
 (is_val e ∨ ∃ e', e ↦str e') :=
begin
  introv Hty,
  split,
  { intros, apply type_preservation1; assumption },
  { apply progress, assumption },
end


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Mario Carneiro

Separation properties of topological spaces.
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.subset_properties
import Mathlib.PostPort

universes u l u_1 u_2 v

namespace Mathlib

/--
`separated` is a predicate on pairs of sub`set`s of a topological space.  It holds if the two
sub`set`s are contained in disjoint open sets.
-/
def separated {α : Type u} [topological_space α] : set α → set α → Prop :=
  fun (s t : set α) => ∃ (U : set α), ∃ (V : set α), is_open U ∧ is_open V ∧ s ⊆ U ∧ t ⊆ V ∧ disjoint U V

namespace separated

theorem symm {α : Type u} [topological_space α] {s : set α} {t : set α} : separated s t → separated t s := sorry

theorem comm {α : Type u} [topological_space α] (s : set α) (t : set α) : separated s t ↔ separated t s :=
  { mp := symm, mpr := symm }

theorem empty_right {α : Type u} [topological_space α] (a : set α) : separated a ∅ := sorry

theorem empty_left {α : Type u} [topological_space α] (a : set α) : separated ∅ a :=
  symm (empty_right a)

theorem union_left {α : Type u} [topological_space α] {a : set α} {b : set α} {c : set α} : separated a c → separated b c → separated (a ∪ b) c := sorry

theorem union_right {α : Type u} [topological_space α] {a : set α} {b : set α} {c : set α} (ab : separated a b) (ac : separated a c) : separated a (b ∪ c) :=
  symm (union_left (symm ab) (symm ac))

end separated

/-- A T₀ space, also known as a Kolmogorov space, is a topological space
  where for every pair `x ≠ y`, there is an open set containing one but not the other. -/
class t0_space (α : Type u) [topological_space α]
where
  t0 : ∀ (x y : α), x ≠ y → ∃ (U : set α), is_open U ∧ xor (x ∈ U) (y ∈ U)

theorem exists_open_singleton_of_open_finset {α : Type u} [topological_space α] [t0_space α] (s : finset α) (sne : finset.nonempty s) (hso : is_open ↑s) : ∃ (x : α), ∃ (H : x ∈ s), is_open (singleton x) := sorry

theorem exists_open_singleton_of_fintype {α : Type u} [topological_space α] [t0_space α] [f : fintype α] [ha : Nonempty α] : ∃ (x : α), is_open (singleton x) := sorry

protected instance subtype.t0_space {α : Type u} [topological_space α] [t0_space α] {p : α → Prop} : t0_space (Subtype p) :=
  t0_space.mk fun (x y : Subtype p) (hxy : x ≠ y) => sorry

/-- A T₁ space, also known as a Fréchet space, is a topological space
  where every singleton set is closed. Equivalently, for every pair
  `x ≠ y`, there is an open set containing `x` and not `y`. -/
class t1_space (α : Type u) [topological_space α]
where
  t1 : ∀ (x : α), is_closed (singleton x)

theorem is_closed_singleton {α : Type u} [topological_space α] [t1_space α] {x : α} : is_closed (singleton x) :=
  t1_space.t1 x

theorem is_open_compl_singleton {α : Type u} [topological_space α] [t1_space α] {x : α} : is_open (singleton xᶜ) :=
  is_closed_singleton

theorem is_open_ne {α : Type u} [topological_space α] [t1_space α] {x : α} : is_open (set_of fun (y : α) => y ≠ x) :=
  is_open_compl_singleton

protected instance subtype.t1_space {α : Type u} [topological_space α] [t1_space α] {p : α → Prop} : t1_space (Subtype p) :=
  t1_space.mk fun (_x : Subtype p) => sorry

protected instance t1_space.t0_space {α : Type u} [topological_space α] [t1_space α] : t0_space α :=
  t0_space.mk
    fun (x y : α) (h : x ≠ y) =>
      Exists.intro (set_of fun (z : α) => z ≠ y)
        { left := is_open_ne, right := Or.inl { left := h, right := not_not_intro rfl } }

theorem compl_singleton_mem_nhds {α : Type u} [topological_space α] [t1_space α] {x : α} {y : α} (h : y ≠ x) : singleton xᶜ ∈ nhds y :=
  mem_nhds_sets is_closed_singleton
    (eq.mpr (id (Eq._oldrec (Eq.refl (y ∈ (singleton xᶜ))) (set.mem_compl_eq (singleton x) y)))
      (eq.mpr (id (Eq._oldrec (Eq.refl (¬y ∈ singleton x)) (propext set.mem_singleton_iff))) h))

@[simp] theorem closure_singleton {α : Type u} [topological_space α] [t1_space α] {a : α} : closure (singleton a) = singleton a :=
  is_closed.closure_eq is_closed_singleton

theorem is_closed_map_const {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [t1_space β] {y : β} : is_closed_map (function.const α y) := sorry

theorem discrete_of_t1_of_finite {X : Type u_1} [topological_space X] [t1_space X] [fintype X] : discrete_topology X := sorry

theorem singleton_mem_nhds_within_of_mem_discrete {α : Type u} [topological_space α] {s : set α} [discrete_topology ↥s] {x : α} (hx : x ∈ s) : singleton x ∈ nhds_within x s := sorry

theorem nhds_within_of_mem_discrete {α : Type u} [topological_space α] {s : set α} [discrete_topology ↥s] {x : α} (hx : x ∈ s) : nhds_within x s = pure x :=
  le_antisymm (iff.mpr filter.le_pure_iff (singleton_mem_nhds_within_of_mem_discrete hx)) (pure_le_nhds_within hx)

theorem filter.has_basis.exists_inter_eq_singleton_of_mem_discrete {α : Type u} [topological_space α] {ι : Type u_1} {p : ι → Prop} {t : ι → set α} {s : set α} [discrete_topology ↥s] {x : α} (hb : filter.has_basis (nhds x) p t) (hx : x ∈ s) : ∃ (i : ι), ∃ (hi : p i), t i ∩ s = singleton x := sorry

/-- A point `x` in a discrete subset `s` of a topological space admits a neighbourhood
that only meets `s` at `x`.  -/
theorem nhds_inter_eq_singleton_of_mem_discrete {α : Type u} [topological_space α] {s : set α} [discrete_topology ↥s] {x : α} (hx : x ∈ s) : ∃ (U : set α), ∃ (H : U ∈ nhds x), U ∩ s = singleton x := sorry

/-- For point `x` in a discrete subset `s` of a topological space, there is a set `U`
such that
1. `U` is a punctured neighborhood of `x` (ie. `U ∪ {x}` is a neighbourhood of `x`),
2. `U` is disjoint from `s`.
-/
theorem disjoint_nhds_within_of_mem_discrete {α : Type u} [topological_space α] {s : set α} [discrete_topology ↥s] {x : α} (hx : x ∈ s) : ∃ (U : set α), ∃ (H : U ∈ nhds_within x (singleton xᶜ)), disjoint U s := sorry

/-- A T₂ space, also known as a Hausdorff space, is one in which for every
  `x ≠ y` there exists disjoint open sets around `x` and `y`. This is
  the most widely used of the separation axioms. -/
class t2_space (α : Type u) [topological_space α]
where
  t2 : ∀ (x y : α), x ≠ y → ∃ (u : set α), ∃ (v : set α), is_open u ∧ is_open v ∧ x ∈ u ∧ y ∈ v ∧ u ∩ v = ∅

theorem t2_separation {α : Type u} [topological_space α] [t2_space α] {x : α} {y : α} (h : x ≠ y) : ∃ (u : set α), ∃ (v : set α), is_open u ∧ is_open v ∧ x ∈ u ∧ y ∈ v ∧ u ∩ v = ∅ :=
  t2_space.t2 x y h

protected instance t2_space.t1_space {α : Type u} [topological_space α] [t2_space α] : t1_space α :=
  t1_space.mk fun (x : α) => iff.mpr is_open_iff_forall_mem_open fun (y : α) (hxy : y ∈ (singleton xᶜ)) => sorry

theorem eq_of_nhds_ne_bot {α : Type u} [topological_space α] [ht : t2_space α] {x : α} {y : α} (h : filter.ne_bot (nhds x ⊓ nhds y)) : x = y := sorry

theorem t2_iff_nhds {α : Type u} [topological_space α] : t2_space α ↔ ∀ {x y : α}, filter.ne_bot (nhds x ⊓ nhds y) → x = y := sorry

theorem t2_iff_ultrafilter {α : Type u} [topological_space α] : t2_space α ↔ ∀ {x y : α} (f : ultrafilter α), ↑f ≤ nhds x → ↑f ≤ nhds y → x = y := sorry

theorem is_closed_diagonal {α : Type u} [topological_space α] [t2_space α] : is_closed (set.diagonal α) := sorry

theorem t2_iff_is_closed_diagonal {α : Type u} [topological_space α] : t2_space α ↔ is_closed (set.diagonal α) := sorry

theorem finset_disjoint_finset_opens_of_t2 {α : Type u} [topological_space α] [t2_space α] (s : finset α) (t : finset α) : disjoint s t → separated ↑s ↑t := sorry

theorem point_disjoint_finset_opens_of_t2 {α : Type u} [topological_space α] [t2_space α] {x : α} {s : finset α} (h : ¬x ∈ s) : separated (singleton x) ↑s := sorry

@[simp] theorem nhds_eq_nhds_iff {α : Type u} [topological_space α] {a : α} {b : α} [t2_space α] : nhds a = nhds b ↔ a = b := sorry

@[simp] theorem nhds_le_nhds_iff {α : Type u} [topological_space α] {a : α} {b : α} [t2_space α] : nhds a ≤ nhds b ↔ a = b := sorry

theorem tendsto_nhds_unique {α : Type u} {β : Type v} [topological_space α] [t2_space α] {f : β → α} {l : filter β} {a : α} {b : α} [filter.ne_bot l] (ha : filter.tendsto f l (nhds a)) (hb : filter.tendsto f l (nhds b)) : a = b :=
  eq_of_nhds_ne_bot (filter.ne_bot_of_le (le_inf ha hb))

theorem tendsto_nhds_unique' {α : Type u} {β : Type v} [topological_space α] [t2_space α] {f : β → α} {l : filter β} {a : α} {b : α} (hl : filter.ne_bot l) (ha : filter.tendsto f l (nhds a)) (hb : filter.tendsto f l (nhds b)) : a = b :=
  eq_of_nhds_ne_bot (filter.ne_bot_of_le (le_inf ha hb))

/-!
### Properties of `Lim` and `lim`

In this section we use explicit `nonempty α` instances for `Lim` and `lim`. This way the lemmas
are useful without a `nonempty α` instance.
-/

theorem Lim_eq {α : Type u} [topological_space α] [t2_space α] {f : filter α} {a : α} [filter.ne_bot f] (h : f ≤ nhds a) : Lim f = a :=
  tendsto_nhds_unique (le_nhds_Lim (Exists.intro a h)) h

theorem Lim_eq_iff {α : Type u} [topological_space α] [t2_space α] {f : filter α} [filter.ne_bot f] (h : ∃ (a : α), f ≤ nhds a) {a : α} : Lim f = a ↔ f ≤ nhds a :=
  { mp := fun (c : Lim f = a) => c ▸ le_nhds_Lim h, mpr := Lim_eq }

theorem ultrafilter.Lim_eq_iff_le_nhds {α : Type u} [topological_space α] [t2_space α] [compact_space α] {x : α} {F : ultrafilter α} : ultrafilter.Lim F = x ↔ ↑F ≤ nhds x :=
  { mp := fun (h : ultrafilter.Lim F = x) => h ▸ ultrafilter.le_nhds_Lim F, mpr := Lim_eq }

theorem is_open_iff_ultrafilter' {α : Type u} [topological_space α] [t2_space α] [compact_space α] (U : set α) : is_open U ↔ ∀ (F : ultrafilter α), ultrafilter.Lim F ∈ U → U ∈ ultrafilter.to_filter F := sorry

theorem filter.tendsto.lim_eq {α : Type u} {β : Type v} [topological_space α] [t2_space α] {a : α} {f : filter β} [filter.ne_bot f] {g : β → α} (h : filter.tendsto g f (nhds a)) : lim f g = a :=
  Lim_eq h

theorem filter.lim_eq_iff {α : Type u} {β : Type v} [topological_space α] [t2_space α] {f : filter β} [filter.ne_bot f] {g : β → α} (h : ∃ (a : α), filter.tendsto g f (nhds a)) {a : α} : lim f g = a ↔ filter.tendsto g f (nhds a) :=
  { mp := fun (c : lim f g = a) => c ▸ tendsto_nhds_lim h, mpr := filter.tendsto.lim_eq }

theorem continuous.lim_eq {α : Type u} {β : Type v} [topological_space α] [t2_space α] [topological_space β] {f : β → α} (h : continuous f) (a : β) : lim (nhds a) f = f a :=
  filter.tendsto.lim_eq (continuous.tendsto h a)

@[simp] theorem Lim_nhds {α : Type u} [topological_space α] [t2_space α] (a : α) : Lim (nhds a) = a :=
  Lim_eq (le_refl (nhds a))

@[simp] theorem lim_nhds_id {α : Type u} [topological_space α] [t2_space α] (a : α) : lim (nhds a) id = a :=
  Lim_nhds a

@[simp] theorem Lim_nhds_within {α : Type u} [topological_space α] [t2_space α] {a : α} {s : set α} (h : a ∈ closure s) : Lim (nhds_within a s) = a :=
  Lim_eq inf_le_left

@[simp] theorem lim_nhds_within_id {α : Type u} [topological_space α] [t2_space α] {a : α} {s : set α} (h : a ∈ closure s) : lim (nhds_within a s) id = a :=
  Lim_nhds_within h

/-!
### Instances of `t2_space` typeclass

We use two lemmas to prove that various standard constructions generate Hausdorff spaces from
Hausdorff spaces:

* `separated_by_continuous` says that two points `x y : α` can be separated by open neighborhoods
  provided that there exists a continuous map `f`: α → β` with a Hausdorff codomain such that
  `f x ≠ f y`. We use this lemma to prove that topological spaces defined using `induced` are
  Hausdorff spaces.

* `separated_by_open_embedding` says that for an open embedding `f : α → β` of a Hausdorff space
  `α`, the images of two distinct points `x y : α`, `x ≠ y` can be separated by open neighborhoods.
  We use this lemma to prove that topological spaces defined using `coinduced` are Hausdorff spaces.
-/

protected instance t2_space_discrete {α : Type u_1} [topological_space α] [discrete_topology α] : t2_space α := sorry

theorem separated_by_continuous {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [t2_space β] {f : α → β} (hf : continuous f) {x : α} {y : α} (h : f x ≠ f y) : ∃ (u : set α), ∃ (v : set α), is_open u ∧ is_open v ∧ x ∈ u ∧ y ∈ v ∧ u ∩ v = ∅ := sorry

theorem separated_by_open_embedding {α : Type u_1} {β : Type u_2} [topological_space α] [topological_space β] [t2_space α] {f : α → β} (hf : open_embedding f) {x : α} {y : α} (h : x ≠ y) : ∃ (u : set β), ∃ (v : set β), is_open u ∧ is_open v ∧ f x ∈ u ∧ f y ∈ v ∧ u ∩ v = ∅ := sorry

protected instance subtype.t2_space {α : Type u_1} {p : α → Prop} [t : topological_space α] [t2_space α] : t2_space (Subtype p) :=
  t2_space.mk fun (x y : Subtype p) (h : x ≠ y) => separated_by_continuous continuous_subtype_val (mt subtype.eq h)

protected instance prod.t2_space {α : Type u_1} {β : Type u_2} [t₁ : topological_space α] [t2_space α] [t₂ : topological_space β] [t2_space β] : t2_space (α × β) :=
  t2_space.mk fun (_x : α × β) => sorry

protected instance sum.t2_space {α : Type u_1} {β : Type u_2} [t₁ : topological_space α] [t2_space α] [t₂ : topological_space β] [t2_space β] : t2_space (α ⊕ β) := sorry

protected instance Pi.t2_space {α : Type u_1} {β : α → Type v} [t₂ : (a : α) → topological_space (β a)] [∀ (a : α), t2_space (β a)] : t2_space ((a : α) → β a) :=
  t2_space.mk fun (x y : (a : α) → β a) (h : x ≠ y) => sorry

protected instance sigma.t2_space {ι : Type u_1} {α : ι → Type u_2} [(i : ι) → topological_space (α i)] [∀ (a : ι), t2_space (α a)] : t2_space (sigma fun (i : ι) => α i) := sorry

theorem is_closed_eq {α : Type u} {β : Type v} [topological_space α] [topological_space β] [t2_space α] {f : β → α} {g : β → α} (hf : continuous f) (hg : continuous g) : is_closed (set_of fun (x : β) => f x = g x) :=
  iff.mp continuous_iff_is_closed (continuous.prod_mk hf hg) (set.diagonal α) is_closed_diagonal

/-- If two continuous maps are equal on `s`, then they are equal on the closure of `s`. -/
theorem set.eq_on.closure {α : Type u} {β : Type v} [topological_space α] [topological_space β] [t2_space α] {s : set β} {f : β → α} {g : β → α} (h : set.eq_on f g s) (hf : continuous f) (hg : continuous g) : set.eq_on f g (closure s) :=
  closure_minimal h (is_closed_eq hf hg)

/-- If two continuous functions are equal on a dense set, then they are equal. -/
theorem continuous.ext_on {α : Type u} {β : Type v} [topological_space α] [topological_space β] [t2_space α] {s : set β} (hs : dense s) {f : β → α} {g : β → α} (hf : continuous f) (hg : continuous g) (h : set.eq_on f g s) : f = g :=
  funext fun (x : β) => set.eq_on.closure h hf hg (hs x)

theorem diagonal_eq_range_diagonal_map {α : Type u_1} : (set_of fun (p : α × α) => prod.fst p = prod.snd p) = set.range fun (x : α) => (x, x) := sorry

theorem prod_subset_compl_diagonal_iff_disjoint {α : Type u_1} {s : set α} {t : set α} : set.prod s t ⊆ ((set_of fun (p : α × α) => prod.fst p = prod.snd p)ᶜ) ↔ s ∩ t = ∅ := sorry

theorem compact_compact_separated {α : Type u} [topological_space α] [t2_space α] {s : set α} {t : set α} (hs : is_compact s) (ht : is_compact t) (hst : s ∩ t = ∅) : ∃ (u : set α), ∃ (v : set α), is_open u ∧ is_open v ∧ s ⊆ u ∧ t ⊆ v ∧ u ∩ v = ∅ := sorry

/-- In a `t2_space`, every compact set is closed. -/
theorem is_compact.is_closed {α : Type u} [topological_space α] [t2_space α] {s : set α} (hs : is_compact s) : is_closed s := sorry

theorem is_compact.inter {α : Type u} [topological_space α] [t2_space α] {s : set α} {t : set α} (hs : is_compact s) (ht : is_compact t) : is_compact (s ∩ t) :=
  is_compact.inter_right hs (is_compact.is_closed ht)

/-- If a compact set is covered by two open sets, then we can cover it by two compact subsets. -/
theorem is_compact.binary_compact_cover {α : Type u} [topological_space α] [t2_space α] {K : set α} {U : set α} {V : set α} (hK : is_compact K) (hU : is_open U) (hV : is_open V) (h2K : K ⊆ U ∪ V) : ∃ (K₁ : set α), ∃ (K₂ : set α), is_compact K₁ ∧ is_compact K₂ ∧ K₁ ⊆ U ∧ K₂ ⊆ V ∧ K = K₁ ∪ K₂ := sorry

/-- For every finite open cover `Uᵢ` of a compact set, there exists a compact cover `Kᵢ ⊆ Uᵢ`. -/
theorem is_compact.finite_compact_cover {α : Type u} [topological_space α] [t2_space α] {s : set α} (hs : is_compact s) {ι : Type u_1} (t : finset ι) (U : ι → set α) (hU : ∀ (i : ι), i ∈ t → is_open (U i)) (hsC : s ⊆ set.Union fun (i : ι) => set.Union fun (H : i ∈ t) => U i) : ∃ (K : ι → set α),
  (∀ (i : ι), is_compact (K i)) ∧ (∀ (i : ι), K i ⊆ U i) ∧ s = set.Union fun (i : ι) => set.Union fun (H : i ∈ t) => K i := sorry

theorem locally_compact_of_compact_nhds {α : Type u} [topological_space α] [t2_space α] (h : ∀ (x : α), ∃ (s : set α), s ∈ nhds x ∧ is_compact s) : locally_compact_space α := sorry

protected instance locally_compact_of_compact {α : Type u} [topological_space α] [t2_space α] [compact_space α] : locally_compact_space α :=
  locally_compact_of_compact_nhds
    fun (x : α) => Exists.intro set.univ { left := mem_nhds_sets is_open_univ trivial, right := compact_univ }

/-- In a locally compact T₂ space, every point has an open neighborhood with compact closure -/
theorem exists_open_with_compact_closure {α : Type u} [topological_space α] [locally_compact_space α] [t2_space α] (x : α) : ∃ (U : set α), is_open U ∧ x ∈ U ∧ is_compact (closure U) := sorry

/-- In a locally compact T₂ space, every compact set is contained in the interior of a compact
  set. -/
theorem exists_compact_superset {α : Type u} [topological_space α] [locally_compact_space α] [t2_space α] {K : set α} (hK : is_compact K) : ∃ (K' : set α), is_compact K' ∧ K ⊆ interior K' := sorry

/-- A T₃ space, also known as a regular space (although this condition sometimes
  omits T₂), is one in which for every closed `C` and `x ∉ C`, there exist
  disjoint open sets containing `x` and `C` respectively. -/
class regular_space (α : Type u) [topological_space α]
extends t1_space α
where
  regular : ∀ {s : set α} {a : α}, is_closed s → ¬a ∈ s → ∃ (t : set α), is_open t ∧ s ⊆ t ∧ nhds_within a t = ⊥

theorem nhds_is_closed {α : Type u} [topological_space α] [regular_space α] {a : α} {s : set α} (h : s ∈ nhds a) : ∃ (t : set α), ∃ (H : t ∈ nhds a), t ⊆ s ∧ is_closed t := sorry

theorem closed_nhds_basis {α : Type u} [topological_space α] [regular_space α] (a : α) : filter.has_basis (nhds a) (fun (s : set α) => s ∈ nhds a ∧ is_closed s) id := sorry

protected instance subtype.regular_space {α : Type u} [topological_space α] [regular_space α] {p : α → Prop} : regular_space (Subtype p) := sorry

protected instance regular_space.t2_space (α : Type u) [topological_space α] [regular_space α] : t2_space α :=
  t2_space.mk fun (x y : α) (hxy : x ≠ y) => sorry

theorem disjoint_nested_nhds {α : Type u} [topological_space α] [regular_space α] {x : α} {y : α} (h : x ≠ y) : ∃ (U₁ : set α),
  ∃ (V₁ : set α),
    ∃ (H : U₁ ∈ nhds x),
      ∃ (H : V₁ ∈ nhds x),
        ∃ (U₂ : set α),
          ∃ (V₂ : set α),
            ∃ (H : U₂ ∈ nhds y),
              ∃ (H : V₂ ∈ nhds y),
                is_closed V₁ ∧ is_closed V₂ ∧ is_open U₁ ∧ is_open U₂ ∧ V₁ ⊆ U₁ ∧ V₂ ⊆ U₂ ∧ U₁ ∩ U₂ = ∅ := sorry

/-- A T₄ space, also known as a normal space (although this condition sometimes
  omits T₂), is one in which for every pair of disjoint closed sets `C` and `D`,
  there exist disjoint open sets containing `C` and `D` respectively. -/
class normal_space (α : Type u) [topological_space α]
extends t1_space α
where
  normal : ∀ (s t : set α),
  is_closed s →
    is_closed t → disjoint s t → ∃ (u : set α), ∃ (v : set α), is_open u ∧ is_open v ∧ s ⊆ u ∧ t ⊆ v ∧ disjoint u v

theorem normal_separation {α : Type u} [topological_space α] [normal_space α] (s : set α) (t : set α) (H1 : is_closed s) (H2 : is_closed t) (H3 : disjoint s t) : ∃ (u : set α), ∃ (v : set α), is_open u ∧ is_open v ∧ s ⊆ u ∧ t ⊆ v ∧ disjoint u v :=
  normal_space.normal s t H1 H2 H3

protected instance normal_space.regular_space {α : Type u} [topological_space α] [normal_space α] : regular_space α :=
  regular_space.mk fun (s : set α) (x : α) (hs : is_closed s) (hxs : ¬x ∈ s) => sorry

-- We can't make this an instance because it could cause an instance loop.

theorem normal_of_compact_t2 {α : Type u} [topological_space α] [compact_space α] [t2_space α] : normal_space α := sorry

/-- In a compact t2 space, the connected component of a point equals the intersection of all
its clopen neighbourhoods. -/
theorem connected_component_eq_Inter_clopen {α : Type u} [topological_space α] [t2_space α] [compact_space α] {x : α} : connected_component x = set.Inter fun (Z : Subtype fun (Z : set α) => is_clopen Z ∧ x ∈ Z) => ↑Z := sorry