/-
Copyright (c) 2022 Joël Riou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joël Riou
-/

import for_mathlib.algebraic_topology.homotopical_algebra.model_category

noncomputable theory

open category_theory category_theory.limits opposite

namespace algebraic_topology

namespace model_category

variables {C : Type*} [category C] [model_category C] (A B X Y : C)

abbreviation is_cofibrant (B : C) := cofibration (initial.to B)
abbreviation is_fibrant (B : C) := fibration (terminal.from B)

variables {A B X Y}

namespace cofibration

lemma from_initial (f : A ⟶ B) [is_cofibrant B] (hA : is_initial A) : cofibration f :=
begin
  have fac : f = (is_initial.unique_up_to_iso hA initial_is_initial).hom ≫ initial.to B :=
    by apply hA.hom_ext,
  rw fac,
  apply_instance,
end

end cofibration

namespace fibration

lemma from_terminal (f : X ⟶ Y) [is_fibrant X] (hY : is_terminal Y) : fibration f :=
begin
  have fac : f = terminal.from X ≫ (is_terminal.unique_up_to_iso terminal_is_terminal hY).hom :=
    by apply hY.hom_ext,
  rw fac,
  apply_instance,
end

end fibration

namespace is_cofibrant

lemma of_initial (hA : is_initial A) : is_cofibrant A :=
begin
  change cofibration (initial.to A),
  rw [show initial.to A = (is_initial.unique_up_to_iso initial_is_initial hA).hom,
    by apply subsingleton.elim],
  apply_instance,
end

lemma mk (f : A ⟶ B) [cofibration f] (hA : is_initial A) : is_cofibrant B :=
begin
  change cofibration (initial.to B),
  rw [show initial.to B = initial.to A ≫ f, by apply subsingleton.elim],
  haveI : is_cofibrant A := of_initial hA,
  apply_instance,
end

end is_cofibrant

namespace is_fibrant

lemma of_terminal (hY : is_terminal Y) : is_fibrant Y :=
begin
  change fibration (terminal.from Y),
  rw [show terminal.from Y = (is_terminal.unique_up_to_iso hY terminal_is_terminal).hom,
    by apply subsingleton.elim],
  apply_instance,
end

lemma mk (f : X ⟶ Y) [fibration f] (hY : is_terminal Y) : is_fibrant X :=
begin
  change fibration (terminal.from X),
  rw [show terminal.from X = f ≫ terminal.from Y, by apply subsingleton.elim],
  haveI : is_fibrant Y := of_terminal hY,
  apply_instance,
end

lemma op (hX : is_fibrant X) : is_cofibrant (op X) :=
begin
  haveI : cofibration (terminal.from X).op := fibration.op infer_instance,
  exact is_cofibrant.mk (terminal.from X).op (initial_op_of_terminal terminal_is_terminal),
end

lemma unop {X : Cᵒᵖ} (hX : is_fibrant X) : is_cofibrant X.unop :=
begin
  haveI : cofibration (terminal.from X).unop := fibration.unop infer_instance,
  exact is_cofibrant.mk (terminal.from X).unop (initial_unop_of_terminal terminal_is_terminal),
end

end is_fibrant

namespace is_cofibrant

lemma op (hB : is_cofibrant B) : is_fibrant (op B) :=
begin
  haveI : fibration (initial.to B).op := cofibration.op infer_instance,
  exact is_fibrant.mk (initial.to B).op (terminal_op_of_initial initial_is_initial),
end

lemma unop {B : Cᵒᵖ} (hB : is_cofibrant B) : is_fibrant B.unop :=
begin
  haveI : fibration (initial.to B).unop := cofibration.unop infer_instance,
  exact is_fibrant.mk (initial.to B).unop (terminal_unop_of_initial initial_is_initial),
end

end is_cofibrant

instance cofibration_coprod_inl [hB : is_cofibrant B] : cofibration (coprod.inl : A ⟶ A ⨿ B) :=
⟨cof_is_stable_under_cobase_change.coprod_inl A B hB.property⟩

instance cofibration_coprod_inr [hA : is_cofibrant A] : cofibration (coprod.inr : B ⟶ A ⨿ B) :=
⟨cof_is_stable_under_cobase_change.coprod_inr A B hA.property⟩

instance fibration_prod_fst [hY : is_fibrant Y] : fibration (limits.prod.fst : X ⨯ Y ⟶ X) :=
⟨fib_is_stable_under_base_change.prod_fst X Y hY.property⟩

instance fibration_prod_snd [hX : is_fibrant X] : fibration (limits.prod.snd : X ⨯ Y ⟶ Y) :=
⟨fib_is_stable_under_base_change.prod_snd X Y hX.property⟩

instance : is_fibrant (terminal C) :=
by { haveI : is_iso (terminal.from (terminal C)) := by convert is_iso.id _, apply_instance, }

instance : is_cofibrant (initial C) :=
by { haveI : is_iso (initial.to (initial C)) := by convert is_iso.id _, apply_instance, }

instance is_fibrant_CM5a_obj {X Y : C} (f : X ⟶ Y) [is_fibrant Y] :
  is_fibrant (CM5a.obj f) :=
begin
  change fibration _,
  have eq : terminal.from (factorisation_axiom.obj CM5a f) = (CM5a.p f) ≫ terminal.from _ :=
    subsingleton.elim _ _,
  rw eq,
  apply_instance,
end

instance is_fibrant_CM5b_obj {X Y : C} (f : X ⟶ Y) [is_fibrant Y] :
  is_fibrant (CM5b.obj f) :=
begin
  change fibration _,
  have eq : terminal.from (factorisation_axiom.obj CM5b f) = (CM5b.p f) ≫ terminal.from _ :=
    subsingleton.elim _ _,
  rw eq,
  apply_instance,
end

instance is_cofibrant_CM5a_obj {X Y : C} (f : X ⟶ Y) [is_cofibrant X] :
  is_cofibrant (CM5a.obj f) :=
begin
  change cofibration _,
  have eq : initial.to (factorisation_axiom.obj CM5a f) = initial.to _ ≫ (CM5a.i f) :=
    subsingleton.elim _ _,
  rw eq,
  apply_instance,
end

instance is_cofibrant_CM5b_obj {X Y : C} (f : X ⟶ Y) [is_cofibrant X] :
  is_cofibrant (CM5b.obj f) :=
begin
  change cofibration _,
  have eq : initial.to (factorisation_axiom.obj CM5b f) = initial.to _ ≫ (CM5b.i f) :=
    subsingleton.elim _ _,
  rw eq,
  apply_instance,
end

end model_category

end algebraic_topology


/-
Copyright (c) 2020 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon
-/
import control.monad.basic
import data.part
import order.hom.order
import data.nat.order.basic
import tactic.wlog

/-!
# Omega Complete Partial Orders

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

An omega-complete partial order is a partial order with a supremum
operation on increasing sequences indexed by natural numbers (which we
call `ωSup`). In this sense, it is strictly weaker than join complete
semi-lattices as only ω-sized totally ordered sets have a supremum.

The concept of an omega-complete partial order (ωCPO) is useful for the
formalization of the semantics of programming languages. Its notion of
supremum helps define the meaning of recursive procedures.

## Main definitions

 * class `omega_complete_partial_order`
 * `ite`, `map`, `bind`, `seq` as continuous morphisms

## Instances of `omega_complete_partial_order`

 * `part`
 * every `complete_lattice`
 * pi-types
 * product types
 * `monotone_hom`
 * `continuous_hom` (with notation →𝒄)
   * an instance of `omega_complete_partial_order (α →𝒄 β)`
 * `continuous_hom.of_fun`
 * `continuous_hom.of_mono`
 * continuous functions:
   * `id`
   * `ite`
   * `const`
   * `part.bind`
   * `part.map`
   * `part.seq`

## References

 * [Chain-complete posets and directed sets with applications][markowsky1976]
 * [Recursive definitions of partial functions and their computations][cadiou1972]
 * [Semantics of Programming Languages: Structures and Techniques][gunter1992]
-/

universes u v

local attribute [-simp] part.bind_eq_bind part.map_eq_map
open_locale classical

namespace order_hom

variables (α : Type*) (β : Type*) {γ : Type*} {φ : Type*}
variables [preorder α] [preorder β] [preorder γ] [preorder φ]

variables {β γ}

variables {α} {α' : Type*} {β' : Type*} [preorder α'] [preorder β']

/-- `part.bind` as a monotone function -/
@[simps]
def bind {β γ} (f : α →o part β) (g : α →o β → part γ) : α →o part γ :=
{ to_fun := λ x, f x >>= g x,
  monotone' :=
  begin
    intros x y h a,
    simp only [and_imp, exists_prop, part.bind_eq_bind, part.mem_bind_iff,
               exists_imp_distrib],
    intros b hb ha,
    refine ⟨b, f.monotone h _ hb, g.monotone h _ _ ha⟩,
  end }

end order_hom

namespace omega_complete_partial_order

/-- A chain is a monotone sequence.

See the definition on page 114 of [gunter1992]. -/
def chain (α : Type u) [preorder α] :=
ℕ →o α

namespace chain

variables {α : Type u} {β : Type v} {γ : Type*}
variables [preorder α] [preorder β] [preorder γ]

instance : has_coe_to_fun (chain α) (λ _, ℕ → α) := order_hom.has_coe_to_fun

instance [inhabited α] : inhabited (chain α) :=
⟨⟨default, λ _ _ _, le_rfl⟩⟩

instance : has_mem α (chain α) :=
⟨λ a (c : ℕ →o α), ∃ i, a = c i⟩

variables (c c' : chain α)
variables (f : α →o β)
variables (g : β →o γ)

instance : has_le (chain α) :=
{ le := λ x y, ∀ i, ∃ j, x i ≤ y j }

/-- `map` function for `chain` -/
@[simps {fully_applied := ff}] def map : chain β :=
f.comp c

variables {f}

lemma mem_map (x : α) : x ∈ c → f x ∈ chain.map c f :=
λ ⟨i,h⟩, ⟨i, h.symm ▸ rfl⟩

lemma exists_of_mem_map {b : β} : b ∈ c.map f → ∃ a, a ∈ c ∧ f a = b :=
λ ⟨i,h⟩, ⟨c i, ⟨i, rfl⟩, h.symm⟩

lemma mem_map_iff {b : β} : b ∈ c.map f ↔ ∃ a, a ∈ c ∧ f a = b :=
⟨ exists_of_mem_map _, λ h, by { rcases h with ⟨w,h,h'⟩, subst b, apply mem_map c _ h, } ⟩

@[simp]
lemma map_id : c.map order_hom.id = c :=
order_hom.comp_id _

lemma map_comp : (c.map f).map g = c.map (g.comp f) := rfl

@[mono]
lemma map_le_map {g : α →o β} (h : f ≤ g) : c.map f ≤ c.map g :=
λ i, by simp [mem_map_iff]; intros; existsi i; apply h

/-- `chain.zip` pairs up the elements of two chains that have the same index -/
@[simps]
def zip (c₀ : chain α) (c₁ : chain β) : chain (α × β) :=
order_hom.prod c₀ c₁

end chain

end omega_complete_partial_order

open omega_complete_partial_order

section prio
set_option extends_priority 50

/-- An omega-complete partial order is a partial order with a supremum
operation on increasing sequences indexed by natural numbers (which we
call `ωSup`). In this sense, it is strictly weaker than join complete
semi-lattices as only ω-sized totally ordered sets have a supremum.

See the definition on page 114 of [gunter1992]. -/
class omega_complete_partial_order (α : Type*) extends partial_order α :=
(ωSup     : chain α → α)
(le_ωSup  : ∀(c:chain α), ∀ i, c i ≤ ωSup c)
(ωSup_le  : ∀(c:chain α) x, (∀ i, c i ≤ x) → ωSup c ≤ x)

end prio

namespace omega_complete_partial_order
variables {α : Type u} {β : Type v} {γ : Type*}
variables [omega_complete_partial_order α]

/-- Transfer a `omega_complete_partial_order` on `β` to a `omega_complete_partial_order` on `α`
using a strictly monotone function `f : β →o α`, a definition of ωSup and a proof that `f` is
continuous with regard to the provided `ωSup` and the ωCPO on `α`. -/
@[reducible]
protected def lift [partial_order β] (f : β →o α)
  (ωSup₀ : chain β → β)
  (h : ∀ x y, f x ≤ f y → x ≤ y)
  (h' : ∀ c, f (ωSup₀ c) = ωSup (c.map f)) : omega_complete_partial_order β :=
{ ωSup := ωSup₀,
  ωSup_le := λ c x hx, h _ _ (by rw h'; apply ωSup_le; intro; apply f.monotone (hx i)),
  le_ωSup := λ c i, h _ _ (by rw h'; apply le_ωSup (c.map f)) }

lemma le_ωSup_of_le {c : chain α} {x : α} (i : ℕ) (h : x ≤ c i) : x ≤ ωSup c :=
le_trans h (le_ωSup c _)

lemma ωSup_total {c : chain α} {x : α} (h : ∀ i, c i ≤ x ∨ x ≤ c i) : ωSup c ≤ x ∨ x ≤ ωSup c :=
classical.by_cases
  (assume : ∀ i, c i ≤ x, or.inl (ωSup_le _ _ this))
  (assume : ¬ ∀ i, c i ≤ x,
    have ∃ i, ¬ c i ≤ x,
      by simp only [not_forall] at this ⊢; assumption,
    let ⟨i, hx⟩ := this in
    have x ≤ c i, from (h i).resolve_left hx,
    or.inr $ le_ωSup_of_le _ this)

@[mono]
lemma ωSup_le_ωSup_of_le {c₀ c₁ : chain α} (h : c₀ ≤ c₁) : ωSup c₀ ≤ ωSup c₁ :=
ωSup_le _ _ $
λ i, Exists.rec_on (h i) $
λ j h, le_trans h (le_ωSup _ _)

lemma ωSup_le_iff (c : chain α) (x : α) : ωSup c ≤ x ↔ (∀ i, c i ≤ x) :=
begin
  split; intros,
  { transitivity ωSup c,
    exact le_ωSup _ _, assumption },
  exact ωSup_le _ _ ‹_›,
end

/-- A subset `p : α → Prop` of the type closed under `ωSup` induces an
`omega_complete_partial_order` on the subtype `{a : α // p a}`. -/
def subtype {α : Type*} [omega_complete_partial_order α] (p : α → Prop)
  (hp : ∀ (c : chain α), (∀ i ∈ c, p i) → p (ωSup c)) :
  omega_complete_partial_order (subtype p) :=
omega_complete_partial_order.lift
  (order_hom.subtype.val p)
  (λ c, ⟨ωSup _, hp (c.map (order_hom.subtype.val p)) (λ i ⟨n, q⟩, q.symm ▸ (c n).2)⟩)
  (λ x y h, h)
  (λ c, rfl)

section continuity
open chain

variables [omega_complete_partial_order β]
variables [omega_complete_partial_order γ]

/-- A monotone function `f : α →o β` is continuous if it distributes over ωSup.

In order to distinguish it from the (more commonly used) continuity from topology
(see topology/basic.lean), the present definition is often referred to as
"Scott-continuity" (referring to Dana Scott). It corresponds to continuity
in Scott topological spaces (not defined here). -/
def continuous (f : α →o β) : Prop :=
∀ c : chain α, f (ωSup c) = ωSup (c.map f)

/-- `continuous' f` asserts that `f` is both monotone and continuous. -/
def continuous' (f : α → β) : Prop :=
∃ hf : monotone f, continuous ⟨f, hf⟩

lemma continuous'.to_monotone {f : α → β} (hf : continuous' f) : monotone f := hf.fst

lemma continuous.of_bundled (f : α → β) (hf : monotone f)
  (hf' : continuous ⟨f, hf⟩) : continuous' f := ⟨hf, hf'⟩

lemma continuous.of_bundled' (f : α →o β) (hf' : continuous f) : continuous' f :=
⟨f.mono, hf'⟩

lemma continuous'.to_bundled (f : α → β) (hf : continuous' f) :
  continuous ⟨f, hf.to_monotone⟩ := hf.snd

@[simp, norm_cast] lemma continuous'_coe : ∀ {f : α →o β}, continuous' f ↔ continuous f
| ⟨f, hf⟩ := ⟨λ ⟨hf', hc⟩, hc, λ hc, ⟨hf, hc⟩⟩

variables (f : α →o β) (g : β →o γ)

lemma continuous_id : continuous (@order_hom.id α _) :=
by intro; rw c.map_id; refl

lemma continuous_comp (hfc : continuous f) (hgc : continuous g) : continuous (g.comp f):=
begin
  dsimp [continuous] at *, intro,
  rw [hfc,hgc,chain.map_comp]
end

lemma id_continuous' : continuous' (@id α) :=
continuous_id.of_bundled' _

lemma continuous_const (x : β) : continuous (order_hom.const α x) :=
λ c, eq_of_forall_ge_iff $ λ z, by simp [ωSup_le_iff]

lemma const_continuous' (x: β) : continuous' (function.const α x) :=
continuous.of_bundled' (order_hom.const α x) (continuous_const x)

end continuity

end omega_complete_partial_order

namespace part

variables {α : Type u} {β : Type v} {γ : Type*}
open omega_complete_partial_order

lemma eq_of_chain {c : chain (part α)} {a b : α} (ha : some a ∈ c) (hb : some b ∈ c) : a = b :=
begin
  cases ha with i ha, replace ha := ha.symm,
  cases hb with j hb, replace hb := hb.symm,
  wlog h : i ≤ j, { exact (this j hb i ha (le_of_not_le h)).symm },
  rw [eq_some_iff] at ha hb,
  have := c.monotone h _ ha, apply mem_unique this hb
end

/-- The (noncomputable) `ωSup` definition for the `ω`-CPO structure on `part α`. -/
protected noncomputable def ωSup (c : chain (part α)) : part α :=
if h : ∃a, some a ∈ c then some (classical.some h) else none

lemma ωSup_eq_some {c : chain (part α)} {a : α} (h : some a ∈ c) : part.ωSup c = some a :=
have ∃a, some a ∈ c, from ⟨a, h⟩,
have a' : some (classical.some this) ∈ c, from classical.some_spec this,
calc part.ωSup c = some (classical.some this) : dif_pos this
                ... = some a : congr_arg _ (eq_of_chain a' h)

lemma ωSup_eq_none {c : chain (part α)} (h : ¬∃a, some a ∈ c) : part.ωSup c = none :=
dif_neg h

lemma mem_chain_of_mem_ωSup {c : chain (part α)} {a : α} (h : a ∈ part.ωSup c) : some a ∈ c :=
begin
  simp [part.ωSup] at h, split_ifs at h,
  { have h' := classical.some_spec h_1,
    rw ← eq_some_iff at h, rw ← h, exact h' },
  { rcases h with ⟨ ⟨ ⟩ ⟩ }
end

noncomputable instance omega_complete_partial_order : omega_complete_partial_order (part α) :=
{ ωSup    := part.ωSup,
  le_ωSup := λ c i, by { intros x hx, rw ← eq_some_iff at hx ⊢,
                         rw [ωSup_eq_some, ← hx], rw ← hx, exact ⟨i,rfl⟩ },
  ωSup_le := by { rintros c x hx a ha, replace ha := mem_chain_of_mem_ωSup ha,
                  cases ha with i ha, apply hx i, rw ← ha, apply mem_some } }

section inst

lemma mem_ωSup (x : α) (c : chain (part α)) : x ∈ ωSup c ↔ some x ∈ c :=
begin
  simp [omega_complete_partial_order.ωSup,part.ωSup],
  split,
  { split_ifs, swap, rintro ⟨⟨⟩⟩,
    intro h', have hh := classical.some_spec h,
    simp at h', subst x, exact hh },
  { intro h,
    have h' : ∃ (a : α), some a ∈ c := ⟨_,h⟩,
    rw dif_pos h', have hh := classical.some_spec h',
    rw eq_of_chain hh h, simp }
end

end inst

end part

namespace pi

variables {α : Type*} {β : α → Type*} {γ : Type*}

open omega_complete_partial_order omega_complete_partial_order.chain

instance [∀a, omega_complete_partial_order (β a)] : omega_complete_partial_order (Πa, β a) :=
{ ωSup    := λc a, ωSup (c.map (pi.eval_order_hom a)),
  ωSup_le := assume c f hf a, ωSup_le _ _ $ by { rintro i, apply hf },
  le_ωSup := assume c i x, le_ωSup_of_le _ $ le_rfl }

namespace omega_complete_partial_order

variables [∀ x, omega_complete_partial_order $ β x]
variables [omega_complete_partial_order γ]

lemma flip₁_continuous'
  (f : ∀ x : α, γ → β x) (a : α) (hf : continuous' (λ x y, f y x)) :
  continuous' (f a) :=
continuous.of_bundled _
  (λ x y h, hf.to_monotone h a)
  (λ c, congr_fun (hf.to_bundled _ c) a)

lemma flip₂_continuous'
  (f : γ → Π x, β x) (hf : ∀ x, continuous' (λ g, f g x)) : continuous' f :=
continuous.of_bundled _
  (λ x y h a, (hf a).to_monotone h)
  (by intro c; ext a; apply (hf a).to_bundled _ c)

end omega_complete_partial_order

end pi

namespace prod

open omega_complete_partial_order
variables {α : Type*} {β : Type*} {γ : Type*}
variables [omega_complete_partial_order α]
variables [omega_complete_partial_order β]
variables [omega_complete_partial_order γ]

/-- The supremum of a chain in the product `ω`-CPO. -/
@[simps]
protected def ωSup (c : chain (α × β)) : α × β :=
(ωSup (c.map order_hom.fst), ωSup (c.map order_hom.snd))

@[simps ωSup_fst ωSup_snd]
instance : omega_complete_partial_order (α × β) :=
{ ωSup := prod.ωSup,
  ωSup_le := λ c ⟨x,x'⟩ h, ⟨ωSup_le _ _ $ λ i, (h i).1, ωSup_le _ _ $ λ i, (h i).2⟩,
  le_ωSup := λ c i,
    ⟨le_ωSup (c.map order_hom.fst) i, le_ωSup (c.map order_hom.snd) i⟩ }

end prod

open omega_complete_partial_order

namespace complete_lattice
variables (α : Type u)

/-- Any complete lattice has an `ω`-CPO structure where the countable supremum is a special case
of arbitrary suprema. -/

@[priority 100] -- see Note [lower instance priority]
instance [complete_lattice α] : omega_complete_partial_order α :=
{ ωSup    := λc, ⨆ i, c i,
  ωSup_le := λ ⟨c, _⟩ s hs, by simp only [supr_le_iff, order_hom.coe_fun_mk] at ⊢ hs;
    intros i; apply hs i,
  le_ωSup := assume ⟨c, _⟩ i, by simp only [order_hom.coe_fun_mk]; apply le_supr_of_le i; refl }

variables {α} {β : Type v} [omega_complete_partial_order α] [complete_lattice β]

lemma Sup_continuous (s : set $ α →o β) (hs : ∀ f ∈ s, continuous f) :
  continuous (Sup s) :=
begin
  intro c, apply eq_of_forall_ge_iff, intro z,
  suffices : (∀ (f ∈ s) n, (f : _) (c n) ≤ z) ↔ (∀ n (f ∈ s), (f : _) (c n) ≤ z),
    by simpa [ωSup_le_iff, hs _ _ _] { contextual := tt },
  exact ⟨λ H n f hf, H f hf n, λ H f hf n, H n f hf⟩
end

lemma supr_continuous {ι : Sort*} {f : ι → α →o β} (h : ∀ i, continuous (f i)) :
  continuous (⨆ i, f i) :=
Sup_continuous _ $ set.forall_range_iff.2 h

theorem Sup_continuous' (s : set (α → β)) (hc : ∀ f ∈ s, continuous' f) :
  continuous' (Sup s) :=
begin
  lift s to set (α →o β) using λ f hf, (hc f hf).to_monotone,
  simp only [set.ball_image_iff, continuous'_coe] at hc,
  rw [Sup_image],
  norm_cast,
  exact supr_continuous (λ f, supr_continuous (λ hf, hc f hf)),
end

lemma sup_continuous {f g : α →o β} (hf : continuous f) (hg : continuous g) :
  continuous (f ⊔ g) :=
begin
  rw ← Sup_pair, apply Sup_continuous,
  rintro f (rfl|rfl|_); assumption
end

lemma top_continuous :
  continuous (⊤ : α →o β) :=
begin
  intro c, apply eq_of_forall_ge_iff, intro z,
  simp only [ωSup_le_iff, forall_const, chain.map_coe, (∘), function.const,
             order_hom.has_top_top, order_hom.const_coe_coe],
end

lemma bot_continuous :
  continuous (⊥ : α →o β) :=
begin
  rw ← Sup_empty,
  exact Sup_continuous _ (λ f hf, hf.elim),
end

end complete_lattice

namespace complete_lattice
variables {α β : Type*} [omega_complete_partial_order α] [complete_linear_order β]

lemma inf_continuous (f g : α →o β) (hf : continuous f) (hg : continuous g) : continuous (f ⊓ g) :=
begin
  refine λ c, eq_of_forall_ge_iff (λ z, _),
  simp only [inf_le_iff, hf c, hg c, ωSup_le_iff, ←forall_or_distrib_left, ←forall_or_distrib_right,
             function.comp_app, chain.map_coe, order_hom.has_inf_inf_coe],
  exact ⟨λ h _, h _ _, λ h i j, (h (max i j)).imp (le_trans $ f.mono $ c.mono $ le_max_left _ _)
    (le_trans $ g.mono $ c.mono $ le_max_right _ _)⟩,
end

lemma inf_continuous' {f g : α → β} (hf : continuous' f) (hg : continuous' g) :
  continuous' (f ⊓ g) :=
⟨_, inf_continuous _ _ hf.snd hg.snd⟩

end complete_lattice

namespace omega_complete_partial_order

variables {α : Type u} {α' : Type*} {β : Type v} {β' : Type*} {γ : Type*} {φ : Type*}

variables [omega_complete_partial_order α] [omega_complete_partial_order β]
variables [omega_complete_partial_order γ] [omega_complete_partial_order φ]
variables [omega_complete_partial_order α'] [omega_complete_partial_order β']

namespace order_hom

/-- The `ωSup` operator for monotone functions. -/
@[simps]
protected def ωSup (c : chain (α →o β)) : α →o β :=
{ to_fun := λ a, ωSup (c.map (order_hom.apply a)),
  monotone' := λ x y h, ωSup_le_ωSup_of_le (chain.map_le_map _ $ λ a, a.monotone h) }

@[simps ωSup_coe]
instance omega_complete_partial_order : omega_complete_partial_order (α →o β) :=
omega_complete_partial_order.lift order_hom.coe_fn_hom order_hom.ωSup
  (λ x y h, h) (λ c, rfl)

end order_hom

section
variables (α β)

/-- A monotone function on `ω`-continuous partial orders is said to be continuous
if for every chain `c : chain α`, `f (⊔ i, c i) = ⊔ i, f (c i)`.
This is just the bundled version of `order_hom.continuous`. -/
structure continuous_hom extends order_hom α β :=
(cont : continuous (order_hom.mk to_fun monotone'))

attribute [nolint doc_blame] continuous_hom.to_order_hom

infixr ` →𝒄 `:25 := continuous_hom -- Input: \r\MIc

instance : has_coe_to_fun (α →𝒄 β) (λ _, α → β) := ⟨λ f, f.to_order_hom.to_fun⟩

instance : has_coe (α →𝒄 β) (α →o β) :=
{ coe :=  continuous_hom.to_order_hom }

instance : partial_order (α →𝒄 β) :=
partial_order.lift (λ f, f.to_order_hom.to_fun) $ by rintro ⟨⟨⟩⟩ ⟨⟨⟩⟩ h; congr; exact h

/-- See Note [custom simps projection]. We need to specify this projection explicitly in this case,
  because it is a composition of multiple projections. -/
def continuous_hom.simps.apply (h : α →𝒄 β) : α → β := h

initialize_simps_projections continuous_hom
  (to_order_hom_to_fun → apply, -to_order_hom)

end

namespace continuous_hom

theorem congr_fun {f g : α →𝒄 β} (h : f = g) (x : α) : f x = g x :=
congr_arg (λ h : α →𝒄 β, h x) h

theorem congr_arg (f : α →𝒄 β) {x y : α} (h : x = y) : f x = f y :=
congr_arg (λ x : α, f x) h

protected lemma monotone (f : α →𝒄 β) : monotone f := f.monotone'

@[mono] lemma apply_mono {f g : α →𝒄 β} {x y : α} (h₁ : f ≤ g) (h₂ : x ≤ y) : f x ≤ g y :=
order_hom.apply_mono (show (f : α →o β) ≤ g, from h₁) h₂

lemma ite_continuous' {p : Prop} [hp : decidable p] (f g : α → β)
  (hf : continuous' f) (hg : continuous' g) : continuous' (λ x, if p then f x else g x) :=
by split_ifs; simp *

lemma ωSup_bind {β γ : Type v} (c : chain α) (f : α →o part β) (g : α →o β → part γ) :
  ωSup (c.map (f.bind g)) = ωSup (c.map f) >>= ωSup (c.map g) :=
begin
  apply eq_of_forall_ge_iff, intro x,
  simp only [ωSup_le_iff, part.bind_le, chain.mem_map_iff, and_imp, order_hom.bind_coe,
    exists_imp_distrib],
  split; intro h''',
  { intros b hb, apply ωSup_le _ _ _,
    rintros i y hy, simp only [part.mem_ωSup] at hb,
    rcases hb with ⟨j,hb⟩, replace hb := hb.symm,
    simp only [part.eq_some_iff, chain.map_coe, function.comp_app, order_hom.apply_coe]
      at hy hb,
    replace hb : b ∈ f (c (max i j))   := f.mono (c.mono (le_max_right i j)) _ hb,
    replace hy : y ∈ g (c (max i j)) b := g.mono (c.mono (le_max_left i j)) _ _ hy,
    apply h''' (max i j),
    simp only [exists_prop, part.bind_eq_bind, part.mem_bind_iff, chain.map_coe,
               function.comp_app, order_hom.bind_coe],
    exact ⟨_,hb,hy⟩, },
  { intros i, intros y hy,
    simp only [exists_prop, part.bind_eq_bind, part.mem_bind_iff, chain.map_coe,
               function.comp_app, order_hom.bind_coe] at hy,
    rcases hy with ⟨b,hb₀,hb₁⟩,
    apply h''' b _,
    { apply le_ωSup (c.map g) _ _ _ hb₁ },
    { apply le_ωSup (c.map f) i _ hb₀ } },
end

lemma bind_continuous' {β γ : Type v} (f : α → part β) (g : α → β → part γ) :
  continuous' f → continuous' g →
  continuous' (λ x, f x >>= g x)
| ⟨hf,hf'⟩ ⟨hg,hg'⟩ :=
continuous.of_bundled' (order_hom.bind ⟨f,hf⟩ ⟨g,hg⟩)
  (by intro c; rw [ωSup_bind, ← hf', ← hg']; refl)

lemma map_continuous' {β γ : Type v} (f : β → γ) (g : α → part β)
  (hg : continuous' g) :
  continuous' (λ x, f <$> g x) :=
by simp only [map_eq_bind_pure_comp];
   apply bind_continuous' _ _ hg;
   apply const_continuous'

lemma seq_continuous' {β γ : Type v} (f : α → part (β → γ)) (g : α → part β)
  (hf : continuous' f) (hg : continuous' g) :
  continuous' (λ x, f x <*> g x) :=
by simp only [seq_eq_bind_map];
   apply bind_continuous' _ _ hf;
   apply pi.omega_complete_partial_order.flip₂_continuous'; intro;
   apply map_continuous' _ _ hg

lemma continuous (F : α →𝒄 β) (C : chain α) : F (ωSup C) = ωSup (C.map F) :=
continuous_hom.cont _ _

/-- Construct a continuous function from a bare function, a continuous function, and a proof that
they are equal. -/
@[simps, reducible]
def of_fun (f : α → β) (g : α →𝒄 β) (h : f = g) : α →𝒄 β :=
by refine {to_order_hom := {to_fun := f, ..}, ..}; subst h; rcases g with ⟨⟨⟩⟩; assumption

/-- Construct a continuous function from a monotone function with a proof of continuity. -/
@[simps, reducible]
def of_mono (f : α →o β) (h : ∀ c : chain α, f (ωSup c) = ωSup (c.map f)) : α →𝒄 β :=
{ to_fun := f,
  monotone' := f.monotone,
  cont := h }

/-- The identity as a continuous function. -/
@[simps]
def id : α →𝒄 α :=
of_mono order_hom.id continuous_id

/-- The composition of continuous functions. -/
@[simps]
def comp (f : β →𝒄 γ) (g : α →𝒄 β) : α →𝒄 γ :=
of_mono (order_hom.comp (↑f) (↑g)) (continuous_comp _ _ g.cont f.cont)

@[ext]
protected lemma ext (f g : α →𝒄 β) (h : ∀ x, f x = g x) : f = g :=
by cases f; cases g; congr; ext; apply h

protected lemma coe_inj (f g : α →𝒄 β) (h : (f : α → β) = g) : f = g :=
continuous_hom.ext _ _ $ _root_.congr_fun h

@[simp]
lemma comp_id (f : β →𝒄 γ) : f.comp id = f := by ext; refl

@[simp]
lemma id_comp (f : β →𝒄 γ) : id.comp f = f := by ext; refl

@[simp]
lemma comp_assoc (f : γ →𝒄 φ) (g : β →𝒄 γ) (h : α →𝒄 β) : f.comp (g.comp h) = (f.comp g).comp h :=
by ext; refl

@[simp]
lemma coe_apply (a : α) (f : α →𝒄 β) : (f : α →o β) a = f a := rfl

/-- `function.const` is a continuous function. -/
def const (x : β) : α →𝒄 β :=
of_mono (order_hom.const _ x) (continuous_const x)

@[simp] theorem const_apply (f : β) (a : α) : const f a = f := rfl

instance [inhabited β] : inhabited (α →𝒄 β) :=
⟨ const default ⟩

/-- The map from continuous functions to monotone functions is itself a monotone function. -/
@[simps]
def to_mono : (α →𝒄 β) →o (α →o β) :=
{ to_fun := λ f, f,
  monotone' := λ x y h, h }

/-- When proving that a chain of applications is below a bound `z`, it suffices to consider the
functions and values being selected from the same index in the chains.

This lemma is more specific than necessary, i.e. `c₀` only needs to be a
chain of monotone functions, but it is only used with continuous functions. -/
@[simp]
lemma forall_forall_merge (c₀ : chain (α →𝒄 β)) (c₁ : chain α) (z : β) :
  (∀ (i j : ℕ), (c₀ i) (c₁ j) ≤ z) ↔ ∀ (i : ℕ), (c₀ i) (c₁ i) ≤ z :=
begin
  split; introv h,
  { apply h },
  { apply le_trans _ (h (max i j)),
    transitivity c₀ i (c₁ (max i j)),
    { apply (c₀ i).monotone, apply c₁.monotone, apply le_max_right },
    { apply c₀.monotone, apply le_max_left } }
end

@[simp]
lemma forall_forall_merge' (c₀ : chain (α →𝒄 β)) (c₁ : chain α) (z : β) :
  (∀ (j i : ℕ), (c₀ i) (c₁ j) ≤ z) ↔ ∀ (i : ℕ), (c₀ i) (c₁ i) ≤ z :=
by rw [forall_swap,forall_forall_merge]

/-- The `ωSup` operator for continuous functions, which takes the pointwise countable supremum
of the functions in the `ω`-chain. -/
@[simps]
protected def ωSup (c : chain (α →𝒄 β)) : α →𝒄 β :=
continuous_hom.of_mono (ωSup $ c.map to_mono)
begin
  intro c',
  apply eq_of_forall_ge_iff, intro z,
  simp only [ωSup_le_iff, (c _).continuous, chain.map_coe, order_hom.apply_coe,
    to_mono_coe, coe_apply, order_hom.omega_complete_partial_order_ωSup_coe,
    forall_forall_merge, forall_forall_merge', (∘), function.eval],
end

@[simps ωSup]
instance : omega_complete_partial_order (α →𝒄 β) :=
omega_complete_partial_order.lift continuous_hom.to_mono continuous_hom.ωSup
  (λ x y h, h) (λ c, rfl)

namespace prod

/-- The application of continuous functions as a continuous function.  -/
@[simps]
def apply : (α →𝒄 β) × α →𝒄 β :=
{ to_fun := λ f, f.1 f.2,
  monotone' := λ x y h, by {dsimp, transitivity y.fst x.snd; [apply h.1, apply y.1.monotone h.2]},
  cont := begin
    intro c,
    apply le_antisymm,
    { apply ωSup_le, intros i,
      dsimp,
      rw (c _).fst.continuous,
      apply ωSup_le, intros j,
      apply le_ωSup_of_le (max i j),
      apply apply_mono,
      exact monotone_fst (order_hom.mono _ (le_max_left _ _)),
      exact monotone_snd (order_hom.mono _ (le_max_right _ _)), },
    { apply ωSup_le, intros i,
      apply le_ωSup_of_le i,
      dsimp,
      apply order_hom.mono _,
      apply le_ωSup_of_le i,
      reflexivity, }
  end }

end prod

lemma ωSup_def (c : chain (α →𝒄 β)) (x : α) : ωSup c x = continuous_hom.ωSup c x := rfl

lemma ωSup_apply_ωSup (c₀ : chain (α →𝒄 β)) (c₁ : chain α) :
  ωSup c₀ (ωSup c₁) = prod.apply (ωSup (c₀.zip c₁)) :=
by simp [prod.apply_apply, prod.ωSup_zip]

/-- A family of continuous functions yields a continuous family of functions. -/
@[simps]
def flip {α : Type*} (f : α → β →𝒄 γ) : β →𝒄 α → γ :=
{ to_fun := λ x y, f y x,
  monotone' := λ x y h a, (f a).monotone h,
  cont := by intro; ext; change f x _ = _; rw [(f x).continuous ]; refl, }

/-- `part.bind` as a continuous function. -/
@[simps { rhs_md := reducible }]
noncomputable def bind {β γ : Type v}
  (f : α →𝒄 part β) (g : α →𝒄 β → part γ) : α →𝒄 part γ :=
of_mono (order_hom.bind (↑f) (↑g)) $ λ c, begin
  rw [order_hom.bind, ← order_hom.bind, ωSup_bind, ← f.continuous, ← g.continuous],
  refl
end

/-- `part.map` as a continuous function. -/
@[simps {rhs_md := reducible}]
noncomputable def map {β γ : Type v} (f : β → γ) (g : α →𝒄 part β) : α →𝒄 part γ :=
of_fun (λ x, f <$> g x) (bind g (const (pure ∘ f))) $
by ext; simp only [map_eq_bind_pure_comp, bind_apply, order_hom.bind_coe, const_apply,
  order_hom.const_coe_coe, coe_apply]

/-- `part.seq` as a continuous function. -/
@[simps {rhs_md := reducible}]
noncomputable def seq {β γ : Type v} (f : α →𝒄 part (β → γ)) (g : α →𝒄 part β) :
  α →𝒄 part γ :=
of_fun (λ x, f x <*> g x) (bind f $ (flip $ _root_.flip map g))
  (by ext; simp only [seq_eq_bind_map, flip, part.bind_eq_bind, map_apply, part.mem_bind_iff,
                      bind_apply, order_hom.bind_coe, coe_apply, flip_apply]; refl)

end continuous_hom

end omega_complete_partial_order


namespace Foo

def y := 10

def _root_.Bla.f (x : Nat) := x + y

#check Bla.f

example : Bla.f 5 = 15 := rfl

def _root_.g (x : Nat) :=
  match x with
  | 0 => 1
  | x+1 => 2*g x

def _root_.Boo.g (x : Nat) :=
  match x with
  | 0 => 1
  | x+1 => 3 * Boo.g x

protected def _root_.h (x : Nat) := x -- Error

example : g 3 = 8 := rfl

example : Boo.g 2 = 9 := rfl

end Foo

def _root_ (y : Nat) := y + 1 -- Error

def _root_._root_ (y : Nat) := y -- Error

def _root_.f._root_ (y : Nat) := y -- Error

protected def _root_.h (x : Nat) := x -- Error

protected def _root_.Boo.h (x : Nat) := x

example : Boo.h x = x := rfl

#check h -- Error

#check f -- Error

open Bla

#check f -- Ok

namespace Test

mutual

  def _root_.isEven (x : Nat) :=
    match x with
    | 0 => true
    | x+1 => isOdd x

  def _root_.isOdd (x : Nat) :=
    match x with
    | 0 => false
    | x+1 => isEven x

end

private def _root_.prv (x : Nat) := x + x + x

example : prv 5 = 15 := rfl

end Test

example : isEven 0  = true := by simp! [isOdd, isEven]
example : isOdd 1   = true := by simp! [isOdd, isEven]
example : isEven 2  = true := by simp! [isOdd, isEven]

example : prv 5 = 15 := rfl

set_option pp.raw true in
#check prv

namespace Ex

@[scoped simp] theorem _root_.isEven_of_isOdd (x : Nat) : isEven (x+1) = isOdd x := by simp [isEven]

@[scoped simp] theorem _root_.isOdd_of_isEven (x : Nat) : isOdd (x+1) = isEven x := by simp [isOdd]

example : isEven (x+1+1) = isEven x := by simp -- Ok

end Ex

example : isEven (x+1+1) = isEven x := by simp; done -- Error

open Ex in
example : isEven (x+1+1) = isEven x := by simp -- Ok

example : isEven (x+1+1) = isEven x := by simp; done -- Error

namespace Foo

def _root_.Bla.g (x : Nat) : Nat :=
  match x with
  | 0 => 1
  | .succ x => h x + g x
where
  h (x : Nat) :=
    match x with
    | 0 => 2
    | .succ x => 2 * g x

def _root_.Bla.g' (x : Nat) : Nat :=
  match x with
  | 0 => 1
  | .succ x => g' x

end Foo


/-
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

! This file was ported from Lean 3 source module data.quot
! leanprover-community/mathlib commit 6ed6abbde29b8f630001a1b481603f657a3384f1
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Logic.Relator

/-!
# Quotient types

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

This module extends the core library's treatment of quotient types (`init.data.quot`).

## Tags

quotient
-/

variable {α : Sort _} {β : Sort _}

open Function

namespace Setoid

#print Setoid.ext /-
theorem ext {α : Sort _} :
    ∀ {s t : Setoid α}, (∀ a b, @Setoid.r α s a b ↔ @Setoid.r α t a b) → s = t
  | ⟨r, _⟩, ⟨p, _⟩, Eq =>
    by
    have : r = p := funext fun a => funext fun b => propext <| Eq a b
    subst this
#align setoid.ext Setoid.ext
-/

end Setoid

namespace Quot

variable {ra : α → α → Prop} {rb : β → β → Prop} {φ : Quot ra → Quot rb → Sort _}

-- mathport name: mk
local notation:arg "⟦" a "⟧" => Quot.mk _ a

instance (r : α → α → Prop) [Inhabited α] : Inhabited (Quot r) :=
  ⟨⟦default⟧⟩

instance [Subsingleton α] : Subsingleton (Quot ra) :=
  ⟨fun x => Quot.inductionOn x fun y => Quot.ind fun b => congr_arg _ (Subsingleton.elim _ _)⟩

#print Quot.hrecOn₂ /-
/-- Recursion on two `quotient` arguments `a` and `b`, result type depends on `⟦a⟧` and `⟦b⟧`. -/
protected def hrecOn₂ (qa : Quot ra) (qb : Quot rb) (f : ∀ a b, φ ⟦a⟧ ⟦b⟧)
    (ca : ∀ {b a₁ a₂}, ra a₁ a₂ → HEq (f a₁ b) (f a₂ b))
    (cb : ∀ {a b₁ b₂}, rb b₁ b₂ → HEq (f a b₁) (f a b₂)) : φ qa qb :=
  Quot.hrecOn qa (fun a => Quot.hrecOn qb (f a) fun b₁ b₂ pb => cb pb) fun a₁ a₂ pa =>
    Quot.inductionOn qb fun b =>
      calc
        HEq (@Quot.hrecOn _ _ (φ _) ⟦b⟧ (f a₁) (@cb _)) (f a₁ b) := by simp [hEq_self_iff_true]
        HEq _ (f a₂ b) := (ca pa)
        HEq _ (@Quot.hrecOn _ _ (φ _) ⟦b⟧ (f a₂) (@cb _)) := by simp [hEq_self_iff_true]

#align quot.hrec_on₂ Quot.hrecOn₂
-/

#print Quot.map /-
/-- Map a function `f : α → β` such that `ra x y` implies `rb (f x) (f y)`
to a map `quot ra → quot rb`. -/
protected def map (f : α → β) (h : (ra ⇒ rb) f f) : Quot ra → Quot rb :=
  Quot.lift (fun x => ⟦f x⟧) fun x y (h₁ : ra x y) => Quot.sound <| h h₁
#align quot.map Quot.map
-/

#print Quot.mapRight /-
/-- If `ra` is a subrelation of `ra'`, then we have a natural map `quot ra → quot ra'`. -/
protected def mapRight {ra' : α → α → Prop} (h : ∀ a₁ a₂, ra a₁ a₂ → ra' a₁ a₂) :
    Quot ra → Quot ra' :=
  Quot.map id h
#align quot.map_right Quot.mapRight
-/

#print Quot.factor /-
/-- Weaken the relation of a quotient. This is the same as `quot.map id`. -/
def factor {α : Type _} (r s : α → α → Prop) (h : ∀ x y, r x y → s x y) : Quot r → Quot s :=
  Quot.lift (Quot.mk s) fun x y rxy => Quot.sound (h x y rxy)
#align quot.factor Quot.factor
-/

#print Quot.factor_mk_eq /-
theorem factor_mk_eq {α : Type _} (r s : α → α → Prop) (h : ∀ x y, r x y → s x y) :
    factor r s h ∘ Quot.mk _ = Quot.mk _ :=
  rfl
#align quot.factor_mk_eq Quot.factor_mk_eq
-/

variable {γ : Sort _} {r : α → α → Prop} {s : β → β → Prop}

/- warning: quot.lift_mk clashes with quot.lift_beta -> Quot.lift_mk
Case conversion may be inaccurate. Consider using '#align quot.lift_mk Quot.lift_mkₓ'. -/
#print Quot.lift_mk /-
/-- **Alias** of `quot.lift_beta`. -/
theorem lift_mk (f : α → γ) (h : ∀ a₁ a₂, r a₁ a₂ → f a₁ = f a₂) (a : α) :
    Quot.lift f h (Quot.mk r a) = f a :=
  rfl
#align quot.lift_mk Quot.lift_mk
-/

#print Quot.liftOn_mk /-
@[simp]
theorem liftOn_mk (a : α) (f : α → γ) (h : ∀ a₁ a₂, r a₁ a₂ → f a₁ = f a₂) :
    Quot.liftOn (Quot.mk r a) f h = f a :=
  rfl
#align quot.lift_on_mk Quot.liftOn_mk
-/

#print Quot.surjective_lift /-
@[simp]
theorem surjective_lift {f : α → γ} (h : ∀ a₁ a₂, r a₁ a₂ → f a₁ = f a₂) :
    Surjective (lift f h) ↔ Surjective f :=
  ⟨fun hf => hf.comp Quot.exists_rep, fun hf y =>
    let ⟨x, hx⟩ := hf y
    ⟨Quot.mk _ x, hx⟩⟩
#align quot.surjective_lift Quot.surjective_lift
-/

#print Quot.lift₂ /-
/-- Descends a function `f : α → β → γ` to quotients of `α` and `β`. -/
@[reducible, elab_as_elim]
protected def lift₂ (f : α → β → γ) (hr : ∀ a b₁ b₂, s b₁ b₂ → f a b₁ = f a b₂)
    (hs : ∀ a₁ a₂ b, r a₁ a₂ → f a₁ b = f a₂ b) (q₁ : Quot r) (q₂ : Quot s) : γ :=
  Quot.lift (fun a => Quot.lift (f a) (hr a))
    (fun a₁ a₂ ha => funext fun q => Quot.inductionOn q fun b => hs a₁ a₂ b ha) q₁ q₂
#align quot.lift₂ Quot.lift₂
-/

/- warning: quot.lift₂_mk -> Quot.lift₂_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {γ : Sort.{u3}} {r : α -> α -> Prop} {s : β -> β -> Prop} (f : α -> β -> γ) (hr : forall (a : α) (b₁ : β) (b₂ : β), (s b₁ b₂) -> (Eq.{u3} γ (f a b₁) (f a b₂))) (hs : forall (a₁ : α) (a₂ : α) (b : β), (r a₁ a₂) -> (Eq.{u3} γ (f a₁ b) (f a₂ b))) (a : α) (b : β), Eq.{u3} γ (Quot.lift₂.{u1, u2, u3} α β γ (fun (a₁ : α) (a₂ : α) => r a₁ a₂) (fun (b₁ : β) (b₂ : β) => s b₁ b₂) f hr hs (Quot.mk.{u1} α r a) (Quot.mk.{u2} β s b)) (f a b)
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} {γ : Sort.{u3}} {r : α -> α -> Prop} {s : β -> β -> Prop} (f : α -> β -> γ) (hr : forall (a : α) (b₁ : β) (b₂ : β), (s b₁ b₂) -> (Eq.{u3} γ (f a b₁) (f a b₂))) (hs : forall (a₁ : α) (a₂ : α) (b : β), (r a₁ a₂) -> (Eq.{u3} γ (f a₁ b) (f a₂ b))) (a : α) (b : β), Eq.{u3} γ (Quot.lift₂.{u2, u1, u3} α β γ (fun (a₁ : α) (a₂ : α) => r a₁ a₂) (fun (b₁ : β) (b₂ : β) => s b₁ b₂) f hr hs (Quot.mk.{u2} α r a) (Quot.mk.{u1} β s b)) (f a b)
Case conversion may be inaccurate. Consider using '#align quot.lift₂_mk Quot.lift₂_mkₓ'. -/
@[simp]
theorem lift₂_mk (f : α → β → γ) (hr : ∀ a b₁ b₂, s b₁ b₂ → f a b₁ = f a b₂)
    (hs : ∀ a₁ a₂ b, r a₁ a₂ → f a₁ b = f a₂ b) (a : α) (b : β) :
    Quot.lift₂ f hr hs (Quot.mk r a) (Quot.mk s b) = f a b :=
  rfl
#align quot.lift₂_mk Quot.lift₂_mk

#print Quot.liftOn₂ /-
/-- Descends a function `f : α → β → γ` to quotients of `α` and `β` and applies it. -/
@[reducible, elab_as_elim]
protected def liftOn₂ (p : Quot r) (q : Quot s) (f : α → β → γ)
    (hr : ∀ a b₁ b₂, s b₁ b₂ → f a b₁ = f a b₂) (hs : ∀ a₁ a₂ b, r a₁ a₂ → f a₁ b = f a₂ b) : γ :=
  Quot.lift₂ f hr hs p q
#align quot.lift_on₂ Quot.liftOn₂
-/

/- warning: quot.lift_on₂_mk -> Quot.liftOn₂_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {γ : Sort.{u3}} {r : α -> α -> Prop} {s : β -> β -> Prop} (a : α) (b : β) (f : α -> β -> γ) (hr : forall (a : α) (b₁ : β) (b₂ : β), (s b₁ b₂) -> (Eq.{u3} γ (f a b₁) (f a b₂))) (hs : forall (a₁ : α) (a₂ : α) (b : β), (r a₁ a₂) -> (Eq.{u3} γ (f a₁ b) (f a₂ b))), Eq.{u3} γ (Quot.liftOn₂.{u1, u2, u3} α β γ r s (Quot.mk.{u1} α r a) (Quot.mk.{u2} β s b) f hr hs) (f a b)
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} {γ : Sort.{u3}} {r : α -> α -> Prop} {s : β -> β -> Prop} (a : α) (b : β) (f : α -> β -> γ) (hr : forall (a : α) (b₁ : β) (b₂ : β), (s b₁ b₂) -> (Eq.{u3} γ (f a b₁) (f a b₂))) (hs : forall (a₁ : α) (a₂ : α) (b : β), (r a₁ a₂) -> (Eq.{u3} γ (f a₁ b) (f a₂ b))), Eq.{u3} γ (Quot.liftOn₂.{u2, u1, u3} α β γ r s (Quot.mk.{u2} α r a) (Quot.mk.{u1} β s b) f hr hs) (f a b)
Case conversion may be inaccurate. Consider using '#align quot.lift_on₂_mk Quot.liftOn₂_mkₓ'. -/
@[simp]
theorem liftOn₂_mk (a : α) (b : β) (f : α → β → γ) (hr : ∀ a b₁ b₂, s b₁ b₂ → f a b₁ = f a b₂)
    (hs : ∀ a₁ a₂ b, r a₁ a₂ → f a₁ b = f a₂ b) :
    Quot.liftOn₂ (Quot.mk r a) (Quot.mk s b) f hr hs = f a b :=
  rfl
#align quot.lift_on₂_mk Quot.liftOn₂_mk

variable {t : γ → γ → Prop}

#print Quot.map₂ /-
/-- Descends a function `f : α → β → γ` to quotients of `α` and `β` wih values in a quotient of
`γ`. -/
protected def map₂ (f : α → β → γ) (hr : ∀ a b₁ b₂, s b₁ b₂ → t (f a b₁) (f a b₂))
    (hs : ∀ a₁ a₂ b, r a₁ a₂ → t (f a₁ b) (f a₂ b)) (q₁ : Quot r) (q₂ : Quot s) : Quot t :=
  Quot.lift₂ (fun a b => Quot.mk t <| f a b) (fun a b₁ b₂ hb => Quot.sound (hr a b₁ b₂ hb))
    (fun a₁ a₂ b ha => Quot.sound (hs a₁ a₂ b ha)) q₁ q₂
#align quot.map₂ Quot.map₂
-/

/- warning: quot.map₂_mk -> Quot.map₂_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {γ : Sort.{u3}} {r : α -> α -> Prop} {s : β -> β -> Prop} {t : γ -> γ -> Prop} (f : α -> β -> γ) (hr : forall (a : α) (b₁ : β) (b₂ : β), (s b₁ b₂) -> (t (f a b₁) (f a b₂))) (hs : forall (a₁ : α) (a₂ : α) (b : β), (r a₁ a₂) -> (t (f a₁ b) (f a₂ b))) (a : α) (b : β), Eq.{u3} (Quot.{u3} γ t) (Quot.map₂.{u1, u2, u3} α β γ (fun (a₁ : α) (a₂ : α) => r a₁ a₂) (fun (b₁ : β) (b₂ : β) => s b₁ b₂) t f hr hs (Quot.mk.{u1} α r a) (Quot.mk.{u2} β s b)) (Quot.mk.{u3} γ t (f a b))
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} {γ : Sort.{u3}} {r : α -> α -> Prop} {s : β -> β -> Prop} {t : γ -> γ -> Prop} (f : α -> β -> γ) (hr : forall (a : α) (b₁ : β) (b₂ : β), (s b₁ b₂) -> (t (f a b₁) (f a b₂))) (hs : forall (a₁ : α) (a₂ : α) (b : β), (r a₁ a₂) -> (t (f a₁ b) (f a₂ b))) (a : α) (b : β), Eq.{u3} (Quot.{u3} γ t) (Quot.map₂.{u2, u1, u3} α β γ (fun (a₁ : α) (a₂ : α) => r a₁ a₂) (fun (b₁ : β) (b₂ : β) => s b₁ b₂) t f hr hs (Quot.mk.{u2} α r a) (Quot.mk.{u1} β s b)) (Quot.mk.{u3} γ t (f a b))
Case conversion may be inaccurate. Consider using '#align quot.map₂_mk Quot.map₂_mkₓ'. -/
@[simp]
theorem map₂_mk (f : α → β → γ) (hr : ∀ a b₁ b₂, s b₁ b₂ → t (f a b₁) (f a b₂))
    (hs : ∀ a₁ a₂ b, r a₁ a₂ → t (f a₁ b) (f a₂ b)) (a : α) (b : β) :
    Quot.map₂ f hr hs (Quot.mk r a) (Quot.mk s b) = Quot.mk t (f a b) :=
  rfl
#align quot.map₂_mk Quot.map₂_mk

#print Quot.recOnSubsingleton₂ /-
/-- A binary version of `quot.rec_on_subsingleton`. -/
@[reducible, elab_as_elim]
protected def recOnSubsingleton₂ {φ : Quot r → Quot s → Sort _}
    [h : ∀ a b, Subsingleton (φ ⟦a⟧ ⟦b⟧)] (q₁ : Quot r) (q₂ : Quot s) (f : ∀ a b, φ ⟦a⟧ ⟦b⟧) :
    φ q₁ q₂ :=
  @Quot.recOnSubsingleton' _ r (fun q => φ q q₂) (fun a => Quot.ind (h a) q₂) q₁ fun a =>
    Quot.recOnSubsingleton' q₂ fun b => f a b
#align quot.rec_on_subsingleton₂ Quot.recOnSubsingleton₂
-/

/- warning: quot.induction_on₂ -> Quot.induction_on₂ is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {r : α -> α -> Prop} {s : β -> β -> Prop} {δ : (Quot.{u1} α r) -> (Quot.{u2} β s) -> Prop} (q₁ : Quot.{u1} α r) (q₂ : Quot.{u2} β s), (forall (a : α) (b : β), δ (Quot.mk.{u1} α r a) (Quot.mk.{u2} β s b)) -> (δ q₁ q₂)
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} {r : α -> α -> Prop} {s : β -> β -> Prop} {δ : (Quot.{u2} α r) -> (Quot.{u1} β s) -> Prop} (q₁ : Quot.{u2} α r) (q₂ : Quot.{u1} β s), (forall (a : α) (b : β), δ (Quot.mk.{u2} α r a) (Quot.mk.{u1} β s b)) -> (δ q₁ q₂)
Case conversion may be inaccurate. Consider using '#align quot.induction_on₂ Quot.induction_on₂ₓ'. -/
@[elab_as_elim]
protected theorem induction_on₂ {δ : Quot r → Quot s → Prop} (q₁ : Quot r) (q₂ : Quot s)
    (h : ∀ a b, δ (Quot.mk r a) (Quot.mk s b)) : δ q₁ q₂ :=
  Quot.ind (fun a₁ => Quot.ind (fun a₂ => h a₁ a₂) q₂) q₁
#align quot.induction_on₂ Quot.induction_on₂

/- warning: quot.induction_on₃ -> Quot.induction_on₃ is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {γ : Sort.{u3}} {r : α -> α -> Prop} {s : β -> β -> Prop} {t : γ -> γ -> Prop} {δ : (Quot.{u1} α r) -> (Quot.{u2} β s) -> (Quot.{u3} γ t) -> Prop} (q₁ : Quot.{u1} α r) (q₂ : Quot.{u2} β s) (q₃ : Quot.{u3} γ t), (forall (a : α) (b : β) (c : γ), δ (Quot.mk.{u1} α r a) (Quot.mk.{u2} β s b) (Quot.mk.{u3} γ t c)) -> (δ q₁ q₂ q₃)
but is expected to have type
  forall {α : Sort.{u3}} {β : Sort.{u2}} {γ : Sort.{u1}} {r : α -> α -> Prop} {s : β -> β -> Prop} {t : γ -> γ -> Prop} {δ : (Quot.{u3} α r) -> (Quot.{u2} β s) -> (Quot.{u1} γ t) -> Prop} (q₁ : Quot.{u3} α r) (q₂ : Quot.{u2} β s) (q₃ : Quot.{u1} γ t), (forall (a : α) (b : β) (c : γ), δ (Quot.mk.{u3} α r a) (Quot.mk.{u2} β s b) (Quot.mk.{u1} γ t c)) -> (δ q₁ q₂ q₃)
Case conversion may be inaccurate. Consider using '#align quot.induction_on₃ Quot.induction_on₃ₓ'. -/
@[elab_as_elim]
protected theorem induction_on₃ {δ : Quot r → Quot s → Quot t → Prop} (q₁ : Quot r) (q₂ : Quot s)
    (q₃ : Quot t) (h : ∀ a b c, δ (Quot.mk r a) (Quot.mk s b) (Quot.mk t c)) : δ q₁ q₂ q₃ :=
  Quot.ind (fun a₁ => Quot.ind (fun a₂ => Quot.ind (fun a₃ => h a₁ a₂ a₃) q₃) q₂) q₁
#align quot.induction_on₃ Quot.induction_on₃

instance (r : α → α → Prop) (f : α → Prop) (h : ∀ a b, r a b → f a = f b) [hf : DecidablePred f] :
    DecidablePred (Quot.lift f h) := fun q => Quot.recOnSubsingleton' q hf

/-- Note that this provides `decidable_rel (quot.lift₂ f ha hb)` when `α = β`. -/
instance (r : α → α → Prop) (s : β → β → Prop) (f : α → β → Prop)
    (ha : ∀ a b₁ b₂, s b₁ b₂ → f a b₁ = f a b₂) (hb : ∀ a₁ a₂ b, r a₁ a₂ → f a₁ b = f a₂ b)
    [hf : ∀ a, DecidablePred (f a)] (q₁ : Quot r) : DecidablePred (Quot.lift₂ f ha hb q₁) :=
  fun q₂ => Quot.recOnSubsingleton₂ q₁ q₂ hf

instance (r : α → α → Prop) (q : Quot r) (f : α → Prop) (h : ∀ a b, r a b → f a = f b)
    [DecidablePred f] : Decidable (Quot.liftOn q f h) :=
  Quot.lift.decidablePred _ _ _ _

instance (r : α → α → Prop) (s : β → β → Prop) (q₁ : Quot r) (q₂ : Quot s) (f : α → β → Prop)
    (ha : ∀ a b₁ b₂, s b₁ b₂ → f a b₁ = f a b₂) (hb : ∀ a₁ a₂ b, r a₁ a₂ → f a₁ b = f a₂ b)
    [∀ a, DecidablePred (f a)] : Decidable (Quot.liftOn₂ q₁ q₂ f ha hb) :=
  Quot.lift₂.decidablePred _ _ _ _ _ _ _

end Quot

namespace Quotient

variable [sa : Setoid α] [sb : Setoid β]

variable {φ : Quotient sa → Quotient sb → Sort _}

instance (s : Setoid α) [Inhabited α] : Inhabited (Quotient s) :=
  ⟨⟦default⟧⟩

instance (s : Setoid α) [Subsingleton α] : Subsingleton (Quotient s) :=
  Quot.Subsingleton

instance {α : Type _} [Setoid α] : IsEquiv α (· ≈ ·)
    where
  refl := Setoid.refl
  symm a b := Setoid.symm
  trans a b c := Setoid.trans

#print Quotient.hrecOn₂ /-
/-- Induction on two `quotient` arguments `a` and `b`, result type depends on `⟦a⟧` and `⟦b⟧`. -/
protected def hrecOn₂ (qa : Quotient sa) (qb : Quotient sb) (f : ∀ a b, φ ⟦a⟧ ⟦b⟧)
    (c : ∀ a₁ b₁ a₂ b₂, a₁ ≈ a₂ → b₁ ≈ b₂ → HEq (f a₁ b₁) (f a₂ b₂)) : φ qa qb :=
  Quot.hrecOn₂ qa qb f (fun _ _ _ p => c _ _ _ _ p (Setoid.refl _)) fun _ _ _ p =>
    c _ _ _ _ (Setoid.refl _) p
#align quotient.hrec_on₂ Quotient.hrecOn₂
-/

#print Quotient.map /-
/-- Map a function `f : α → β` that sends equivalent elements to equivalent elements
to a function `quotient sa → quotient sb`. Useful to define unary operations on quotients. -/
protected def map (f : α → β) (h : ((· ≈ ·) ⇒ (· ≈ ·)) f f) : Quotient sa → Quotient sb :=
  Quot.map f h
#align quotient.map Quotient.map
-/

/- warning: quotient.map_mk -> Quotient.map_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} [sa : Setoid.{u1} α] [sb : Setoid.{u2} β] (f : α -> β) (h : Relator.LiftFun.{u1, u1, u2, u2} α α β β (HasEquivₓ.Equiv.{u1} α (setoidHasEquiv.{u1} α sa)) (HasEquivₓ.Equiv.{u2} β (setoidHasEquiv.{u2} β sb)) f f) (x : α), Eq.{u2} (Quotient.{u2} β sb) (Quotient.map.{u1, u2} α β sa sb f h (Quotient.mk'.{u1} α sa x)) (Quotient.mk'.{u2} β sb (f x))
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} [sa : Setoid.{u2} α] [sb : Setoid.{u1} β] (f : α -> β) (h : Relator.LiftFun.{u2, u2, u1, u1} α α β β (fun (x._@.Mathlib.Data.Quot._hyg.4515 : α) (x._@.Mathlib.Data.Quot._hyg.4517 : α) => HasEquiv.Equiv.{u2, 0} α (instHasEquiv.{u2} α sa) x._@.Mathlib.Data.Quot._hyg.4515 x._@.Mathlib.Data.Quot._hyg.4517) (fun (x._@.Mathlib.Data.Quot._hyg.4530 : β) (x._@.Mathlib.Data.Quot._hyg.4532 : β) => HasEquiv.Equiv.{u1, 0} β (instHasEquiv.{u1} β sb) x._@.Mathlib.Data.Quot._hyg.4530 x._@.Mathlib.Data.Quot._hyg.4532) f f) (x : α), Eq.{u1} (Quotient.{u1} β sb) (Quotient.map.{u2, u1} α β sa sb f h (Quotient.mk.{u2} α sa x)) (Quotient.mk.{u1} β sb (f x))
Case conversion may be inaccurate. Consider using '#align quotient.map_mk Quotient.map_mkₓ'. -/
@[simp]
theorem map_mk (f : α → β) (h : ((· ≈ ·) ⇒ (· ≈ ·)) f f) (x : α) :
    Quotient.map f h (⟦x⟧ : Quotient sa) = (⟦f x⟧ : Quotient sb) :=
  rfl
#align quotient.map_mk Quotient.map_mk

variable {γ : Sort _} [sc : Setoid γ]

#print Quotient.map₂ /-
/-- Map a function `f : α → β → γ` that sends equivalent elements to equivalent elements
to a function `f : quotient sa → quotient sb → quotient sc`.
Useful to define binary operations on quotients. -/
protected def map₂ (f : α → β → γ) (h : ((· ≈ ·) ⇒ (· ≈ ·) ⇒ (· ≈ ·)) f f) :
    Quotient sa → Quotient sb → Quotient sc :=
  Quotient.lift₂ (fun x y => ⟦f x y⟧) fun x₁ y₁ x₂ y₂ h₁ h₂ => Quot.sound <| h h₁ h₂
#align quotient.map₂ Quotient.map₂
-/

/- warning: quotient.map₂_mk -> Quotient.map₂_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} [sa : Setoid.{u1} α] [sb : Setoid.{u2} β] {γ : Sort.{u3}} [sc : Setoid.{u3} γ] (f : α -> β -> γ) (h : Relator.LiftFun.{u1, u1, imax u2 u3, imax u2 u3} α α (β -> γ) (β -> γ) (HasEquivₓ.Equiv.{u1} α (setoidHasEquiv.{u1} α sa)) (Relator.LiftFun.{u2, u2, u3, u3} β β γ γ (HasEquivₓ.Equiv.{u2} β (setoidHasEquiv.{u2} β sb)) (HasEquivₓ.Equiv.{u3} γ (setoidHasEquiv.{u3} γ sc))) f f) (x : α) (y : β), Eq.{u3} (Quotient.{u3} γ sc) (Quotient.map₂.{u1, u2, u3} α β sa sb γ sc f h (Quotient.mk'.{u1} α sa x) (Quotient.mk'.{u2} β sb y)) (Quotient.mk'.{u3} γ sc (f x y))
but is expected to have type
  forall {α : Sort.{u3}} {β : Sort.{u2}} [sa : Setoid.{u3} α] [sb : Setoid.{u2} β] {γ : Sort.{u1}} [sc : Setoid.{u1} γ] (f : α -> β -> γ) (h : Relator.LiftFun.{u3, u3, imax u2 u1, imax u2 u1} α α (β -> γ) (β -> γ) (fun (x._@.Mathlib.Data.Quot._hyg.4742 : α) (x._@.Mathlib.Data.Quot._hyg.4744 : α) => HasEquiv.Equiv.{u3, 0} α (instHasEquiv.{u3} α sa) x._@.Mathlib.Data.Quot._hyg.4742 x._@.Mathlib.Data.Quot._hyg.4744) (Relator.LiftFun.{u2, u2, u1, u1} β β γ γ (fun (x._@.Mathlib.Data.Quot._hyg.4760 : β) (x._@.Mathlib.Data.Quot._hyg.4762 : β) => HasEquiv.Equiv.{u2, 0} β (instHasEquiv.{u2} β sb) x._@.Mathlib.Data.Quot._hyg.4760 x._@.Mathlib.Data.Quot._hyg.4762) (fun (x._@.Mathlib.Data.Quot._hyg.4775 : γ) (x._@.Mathlib.Data.Quot._hyg.4777 : γ) => HasEquiv.Equiv.{u1, 0} γ (instHasEquiv.{u1} γ sc) x._@.Mathlib.Data.Quot._hyg.4775 x._@.Mathlib.Data.Quot._hyg.4777)) f f) (x : α) (y : β), Eq.{u1} (Quotient.{u1} γ sc) (Quotient.map₂.{u3, u2, u1} α β sa sb γ sc f h (Quotient.mk.{u3} α sa x) (Quotient.mk.{u2} β sb y)) (Quotient.mk.{u1} γ sc (f x y))
Case conversion may be inaccurate. Consider using '#align quotient.map₂_mk Quotient.map₂_mkₓ'. -/
@[simp]
theorem map₂_mk (f : α → β → γ) (h : ((· ≈ ·) ⇒ (· ≈ ·) ⇒ (· ≈ ·)) f f) (x : α) (y : β) :
    Quotient.map₂ f h (⟦x⟧ : Quotient sa) (⟦y⟧ : Quotient sb) = (⟦f x y⟧ : Quotient sc) :=
  rfl
#align quotient.map₂_mk Quotient.map₂_mk

include sa

instance (f : α → Prop) (h : ∀ a b, a ≈ b → f a = f b) [DecidablePred f] :
    DecidablePred (Quotient.lift f h) :=
  Quot.lift.decidablePred _ _ _

include sb

/-- Note that this provides `decidable_rel (quotient.lift₂ f h)` when `α = β`. -/
instance (f : α → β → Prop) (h : ∀ a₁ b₁ a₂ b₂, a₁ ≈ a₂ → b₁ ≈ b₂ → f a₁ b₁ = f a₂ b₂)
    [hf : ∀ a, DecidablePred (f a)] (q₁ : Quotient sa) : DecidablePred (Quotient.lift₂ f h q₁) :=
  fun q₂ => Quotient.recOnSubsingleton₂ q₁ q₂ hf

omit sb

instance (q : Quotient sa) (f : α → Prop) (h : ∀ a b, a ≈ b → f a = f b) [DecidablePred f] :
    Decidable (Quotient.liftOn q f h) :=
  Quotient.lift.decidablePred _ _ _

instance (q₁ : Quotient sa) (q₂ : Quotient sb) (f : α → β → Prop)
    (h : ∀ a₁ b₁ a₂ b₂, a₁ ≈ a₂ → b₁ ≈ b₂ → f a₁ b₁ = f a₂ b₂) [∀ a, DecidablePred (f a)] :
    Decidable (Quotient.liftOn₂ q₁ q₂ f h) :=
  Quotient.lift₂.decidablePred _ _ _ _

end Quotient

#print Quot.eq /-
theorem Quot.eq {α : Type _} {r : α → α → Prop} {x y : α} :
    Quot.mk r x = Quot.mk r y ↔ EqvGen r x y :=
  ⟨Quot.exact r, Quot.EqvGen_sound⟩
#align quot.eq Quot.eq
-/

#print Quotient.eq' /-
@[simp]
theorem Quotient.eq' [r : Setoid α] {x y : α} : ⟦x⟧ = ⟦y⟧ ↔ x ≈ y :=
  ⟨Quotient.exact, Quotient.sound⟩
#align quotient.eq Quotient.eq'
-/

#print forall_quotient_iff /-
theorem forall_quotient_iff {α : Type _} [r : Setoid α] {p : Quotient r → Prop} :
    (∀ a : Quotient r, p a) ↔ ∀ a : α, p ⟦a⟧ :=
  ⟨fun h x => h _, fun h a => a.inductionOn h⟩
#align forall_quotient_iff forall_quotient_iff
-/

/- warning: quotient.lift_mk -> Quotient.lift_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} [s : Setoid.{u1} α] (f : α -> β) (h : forall (a : α) (b : α), (HasEquivₓ.Equiv.{u1} α (setoidHasEquiv.{u1} α s) a b) -> (Eq.{u2} β (f a) (f b))) (x : α), Eq.{u2} β (Quotient.lift.{u1, u2} α β s f h (Quotient.mk'.{u1} α s x)) (f x)
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} [s : Setoid.{u2} α] (f : α -> β) (h : forall (a : α) (b : α), (HasEquiv.Equiv.{u2, 0} α (instHasEquiv.{u2} α s) a b) -> (Eq.{u1} β (f a) (f b))) (x : α), Eq.{u1} β (Quotient.lift.{u2, u1} α β s f h (Quotient.mk.{u2} α s x)) (f x)
Case conversion may be inaccurate. Consider using '#align quotient.lift_mk Quotient.lift_mkₓ'. -/
@[simp]
theorem Quotient.lift_mk [s : Setoid α] (f : α → β) (h : ∀ a b : α, a ≈ b → f a = f b) (x : α) :
    Quotient.lift f h (Quotient.mk' x) = f x :=
  rfl
#align quotient.lift_mk Quotient.lift_mk

/- warning: quotient.lift_comp_mk -> Quotient.lift_comp_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} [_inst_1 : Setoid.{u1} α] (f : α -> β) (h : forall (a : α) (b : α), (HasEquivₓ.Equiv.{u1} α (setoidHasEquiv.{u1} α _inst_1) a b) -> (Eq.{u2} β (f a) (f b))), Eq.{imax u1 u2} (α -> β) (Function.comp.{u1, u1, u2} α (Quotient.{u1} α _inst_1) β (Quotient.lift.{u1, u2} α β _inst_1 f h) (Quotient.mk'.{u1} α _inst_1)) f
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} [_inst_1 : Setoid.{u2} α] (f : α -> β) (h : forall (a : α) (b : α), (HasEquiv.Equiv.{u2, 0} α (instHasEquiv.{u2} α _inst_1) a b) -> (Eq.{u1} β (f a) (f b))), Eq.{imax u2 u1} (α -> β) (Function.comp.{u2, u2, u1} α (Quotient.{u2} α _inst_1) β (Quotient.lift.{u2, u1} α β _inst_1 f h) (Quotient.mk.{u2} α _inst_1)) f
Case conversion may be inaccurate. Consider using '#align quotient.lift_comp_mk Quotient.lift_comp_mkₓ'. -/
@[simp]
theorem Quotient.lift_comp_mk [Setoid α] (f : α → β) (h : ∀ a b : α, a ≈ b → f a = f b) :
    Quotient.lift f h ∘ Quotient.mk' = f :=
  rfl
#align quotient.lift_comp_mk Quotient.lift_comp_mk

/- warning: quotient.lift₂_mk -> Quotient.lift₂_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {γ : Sort.{u3}} [_inst_1 : Setoid.{u1} α] [_inst_2 : Setoid.{u2} β] (f : α -> β -> γ) (h : forall (a₁ : α) (a₂ : β) (b₁ : α) (b₂ : β), (HasEquivₓ.Equiv.{u1} α (setoidHasEquiv.{u1} α _inst_1) a₁ b₁) -> (HasEquivₓ.Equiv.{u2} β (setoidHasEquiv.{u2} β _inst_2) a₂ b₂) -> (Eq.{u3} γ (f a₁ a₂) (f b₁ b₂))) (a : α) (b : β), Eq.{u3} γ (Quotient.lift₂.{u1, u2, u3} α β γ _inst_1 _inst_2 f h (Quotient.mk'.{u1} α _inst_1 a) (Quotient.mk'.{u2} β _inst_2 b)) (f a b)
but is expected to have type
  forall {α : Sort.{u3}} {β : Sort.{u2}} {γ : Sort.{u1}} [_inst_1 : Setoid.{u3} α] [_inst_2 : Setoid.{u2} β] (f : α -> β -> γ) (h : forall (a₁ : α) (a₂ : β) (b₁ : α) (b₂ : β), (HasEquiv.Equiv.{u3, 0} α (instHasEquiv.{u3} α _inst_1) a₁ b₁) -> (HasEquiv.Equiv.{u2, 0} β (instHasEquiv.{u2} β _inst_2) a₂ b₂) -> (Eq.{u1} γ (f a₁ a₂) (f b₁ b₂))) (a : α) (b : β), Eq.{u1} γ (Quotient.lift₂.{u3, u2, u1} α β γ _inst_1 _inst_2 f h (Quotient.mk.{u3} α _inst_1 a) (Quotient.mk.{u2} β _inst_2 b)) (f a b)
Case conversion may be inaccurate. Consider using '#align quotient.lift₂_mk Quotient.lift₂_mkₓ'. -/
@[simp]
theorem Quotient.lift₂_mk {α : Sort _} {β : Sort _} {γ : Sort _} [Setoid α] [Setoid β]
    (f : α → β → γ)
    (h : ∀ (a₁ : α) (a₂ : β) (b₁ : α) (b₂ : β), a₁ ≈ b₁ → a₂ ≈ b₂ → f a₁ a₂ = f b₁ b₂) (a : α)
    (b : β) : Quotient.lift₂ f h (Quotient.mk' a) (Quotient.mk' b) = f a b :=
  rfl
#align quotient.lift₂_mk Quotient.lift₂_mk

/- warning: quotient.lift_on_mk -> Quotient.liftOn_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} [s : Setoid.{u1} α] (f : α -> β) (h : forall (a : α) (b : α), (HasEquivₓ.Equiv.{u1} α (setoidHasEquiv.{u1} α s) a b) -> (Eq.{u2} β (f a) (f b))) (x : α), Eq.{u2} β (Quotient.liftOn.{u1, u2} α β s (Quotient.mk'.{u1} α s x) f h) (f x)
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} [s : Setoid.{u2} α] (f : α -> β) (h : forall (a : α) (b : α), (HasEquiv.Equiv.{u2, 0} α (instHasEquiv.{u2} α s) a b) -> (Eq.{u1} β (f a) (f b))) (x : α), Eq.{u1} β (Quotient.liftOn.{u2, u1} α β s (Quotient.mk.{u2} α s x) f h) (f x)
Case conversion may be inaccurate. Consider using '#align quotient.lift_on_mk Quotient.liftOn_mkₓ'. -/
@[simp]
theorem Quotient.liftOn_mk [s : Setoid α] (f : α → β) (h : ∀ a b : α, a ≈ b → f a = f b) (x : α) :
    Quotient.liftOn (Quotient.mk' x) f h = f x :=
  rfl
#align quotient.lift_on_mk Quotient.liftOn_mk

/- warning: quotient.lift_on₂_mk -> Quotient.liftOn₂_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} [_inst_1 : Setoid.{u1} α] (f : α -> α -> β) (h : forall (a₁ : α) (a₂ : α) (b₁ : α) (b₂ : α), (HasEquivₓ.Equiv.{u1} α (setoidHasEquiv.{u1} α _inst_1) a₁ b₁) -> (HasEquivₓ.Equiv.{u1} α (setoidHasEquiv.{u1} α _inst_1) a₂ b₂) -> (Eq.{u2} β (f a₁ a₂) (f b₁ b₂))) (x : α) (y : α), Eq.{u2} β (Quotient.liftOn₂.{u1, u1, u2} α α β _inst_1 _inst_1 (Quotient.mk'.{u1} α _inst_1 x) (Quotient.mk'.{u1} α _inst_1 y) f h) (f x y)
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} [_inst_1 : Setoid.{u2} α] (f : α -> α -> β) (h : forall (a₁ : α) (a₂ : α) (b₁ : α) (b₂ : α), (HasEquiv.Equiv.{u2, 0} α (instHasEquiv.{u2} α _inst_1) a₁ b₁) -> (HasEquiv.Equiv.{u2, 0} α (instHasEquiv.{u2} α _inst_1) a₂ b₂) -> (Eq.{u1} β (f a₁ a₂) (f b₁ b₂))) (x : α) (y : α), Eq.{u1} β (Quotient.liftOn₂.{u2, u2, u1} α α β _inst_1 _inst_1 (Quotient.mk.{u2} α _inst_1 x) (Quotient.mk.{u2} α _inst_1 y) f h) (f x y)
Case conversion may be inaccurate. Consider using '#align quotient.lift_on₂_mk Quotient.liftOn₂_mkₓ'. -/
@[simp]
theorem Quotient.liftOn₂_mk {α : Sort _} {β : Sort _} [Setoid α] (f : α → α → β)
    (h : ∀ a₁ a₂ b₁ b₂ : α, a₁ ≈ b₁ → a₂ ≈ b₂ → f a₁ a₂ = f b₁ b₂) (x y : α) :
    Quotient.liftOn₂ (Quotient.mk' x) (Quotient.mk' y) f h = f x y :=
  rfl
#align quotient.lift_on₂_mk Quotient.liftOn₂_mk

#print surjective_quot_mk /-
/-- `quot.mk r` is a surjective function. -/
theorem surjective_quot_mk (r : α → α → Prop) : Surjective (Quot.mk r) :=
  Quot.exists_rep
#align surjective_quot_mk surjective_quot_mk
-/

#print surjective_quotient_mk /-
/-- `quotient.mk` is a surjective function. -/
theorem surjective_quotient_mk (α : Sort _) [s : Setoid α] :
    Surjective (Quotient.mk' : α → Quotient s) :=
  Quot.exists_rep
#align surjective_quotient_mk surjective_quotient_mk
-/

#print Quot.out /-
/-- Choose an element of the equivalence class using the axiom of choice.
  Sound but noncomputable. -/
noncomputable def Quot.out {r : α → α → Prop} (q : Quot r) : α :=
  Classical.choose (Quot.exists_rep q)
#align quot.out Quot.out
-/

/-- Unwrap the VM representation of a quotient to obtain an element of the equivalence class.
  Computable but unsound. -/
unsafe def quot.unquot {r : α → α → Prop} : Quot r → α :=
  unchecked_cast
#align quot.unquot quot.unquot

#print Quot.out_eq /-
@[simp]
theorem Quot.out_eq {r : α → α → Prop} (q : Quot r) : Quot.mk r q.out = q :=
  Classical.choose_spec (Quot.exists_rep q)
#align quot.out_eq Quot.out_eq
-/

#print Quotient.out /-
/-- Choose an element of the equivalence class using the axiom of choice.
  Sound but noncomputable. -/
noncomputable def Quotient.out [s : Setoid α] : Quotient s → α :=
  Quot.out
#align quotient.out Quotient.out
-/

#print Quotient.out_eq /-
@[simp]
theorem Quotient.out_eq [s : Setoid α] (q : Quotient s) : ⟦q.out⟧ = q :=
  q.out_eq
#align quotient.out_eq Quotient.out_eq
-/

#print Quotient.mk_out /-
theorem Quotient.mk_out [s : Setoid α] (a : α) : ⟦a⟧.out ≈ a :=
  Quotient.exact (Quotient.out_eq _)
#align quotient.mk_out Quotient.mk_out
-/

#print Quotient.mk_eq_iff_out /-
theorem Quotient.mk_eq_iff_out [s : Setoid α] {x : α} {y : Quotient s} :
    ⟦x⟧ = y ↔ x ≈ Quotient.out y :=
  by
  refine' Iff.trans _ Quotient.eq'
  rw [Quotient.out_eq y]
#align quotient.mk_eq_iff_out Quotient.mk_eq_iff_out
-/

#print Quotient.eq_mk_iff_out /-
theorem Quotient.eq_mk_iff_out [s : Setoid α] {x : Quotient s} {y : α} :
    x = ⟦y⟧ ↔ Quotient.out x ≈ y :=
  by
  refine' Iff.trans _ Quotient.eq'
  rw [Quotient.out_eq x]
#align quotient.eq_mk_iff_out Quotient.eq_mk_iff_out
-/

#print Quotient.out_equiv_out /-
@[simp]
theorem Quotient.out_equiv_out {s : Setoid α} {x y : Quotient s} : x.out ≈ y.out ↔ x = y := by
  rw [← Quotient.eq_mk_iff_out, Quotient.out_eq]
#align quotient.out_equiv_out Quotient.out_equiv_out
-/

#print Quotient.out_injective /-
theorem Quotient.out_injective {s : Setoid α} : Injective (@Quotient.out α s) := fun a b h =>
  Quotient.out_equiv_out.1 <| h ▸ Setoid.refl _
#align quotient.out_injective Quotient.out_injective
-/

#print Quotient.out_inj /-
@[simp]
theorem Quotient.out_inj {s : Setoid α} {x y : Quotient s} : x.out = y.out ↔ x = y :=
  ⟨fun h => Quotient.out_injective h, fun h => h ▸ rfl⟩
#align quotient.out_inj Quotient.out_inj
-/

section Pi

#print piSetoid /-
instance piSetoid {ι : Sort _} {α : ι → Sort _} [∀ i, Setoid (α i)] : Setoid (∀ i, α i)
    where
  R a b := ∀ i, a i ≈ b i
  iseqv :=
    ⟨fun a i => Setoid.refl _, fun a b h i => Setoid.symm (h _), fun a b c h₁ h₂ i =>
      Setoid.trans (h₁ _) (h₂ _)⟩
#align pi_setoid piSetoid
-/

#print Quotient.choice /-
/-- Given a function `f : Π i, quotient (S i)`, returns the class of functions `Π i, α i` sending
each `i` to an element of the class `f i`. -/
noncomputable def Quotient.choice {ι : Type _} {α : ι → Type _} [S : ∀ i, Setoid (α i)]
    (f : ∀ i, Quotient (S i)) : @Quotient (∀ i, α i) (by infer_instance) :=
  ⟦fun i => (f i).out⟧
#align quotient.choice Quotient.choice
-/

/- warning: quotient.choice_eq -> Quotient.choice_eq is a dubious translation:
lean 3 declaration is
  forall {ι : Type.{u1}} {α : ι -> Type.{u2}} [_inst_1 : forall (i : ι), Setoid.{succ u2} (α i)] (f : forall (i : ι), α i), Eq.{max (succ u1) (succ u2)} (Quotient.{max (succ u1) (succ u2)} (forall (i : ι), α i) (piSetoid.{succ u1, succ u2} ι (fun (i : ι) => α i) (fun (i : ι) => _inst_1 i))) (Quotient.choice.{u1, u2} ι (fun (i : ι) => α i) (fun (i : ι) => _inst_1 i) (fun (i : ι) => Quotient.mk'.{succ u2} (α i) (_inst_1 i) (f i))) (Quotient.mk'.{max (succ u1) (succ u2)} (forall (i : ι), α i) (piSetoid.{succ u1, succ u2} ι (fun (i : ι) => α i) (fun (i : ι) => _inst_1 i)) f)
but is expected to have type
  forall {ι : Type.{u2}} {α : ι -> Type.{u1}} [_inst_1 : forall (i : ι), Setoid.{succ u1} (α i)] (f : forall (i : ι), α i), Eq.{max (succ u2) (succ u1)} (Quotient.{max (succ u2) (succ u1)} (forall (i : ι), α i) (inferInstance.{max (succ u2) (succ u1)} (Setoid.{max (succ u2) (succ u1)} (forall (i : ι), α i)) (piSetoid.{succ u2, succ u1} ι (fun (i : ι) => α i) (fun (i : ι) => _inst_1 i)))) (Quotient.choice.{u2, u1} ι (fun (i : ι) => α i) (fun (i : ι) => _inst_1 i) (fun (i : ι) => Quotient.mk.{succ u1} (α i) (_inst_1 i) (f i))) (Quotient.mk.{max (succ u2) (succ u1)} (forall (i : ι), α i) (inferInstance.{max (succ u2) (succ u1)} (Setoid.{max (succ u2) (succ u1)} (forall (i : ι), α i)) (piSetoid.{succ u2, succ u1} ι (fun (i : ι) => α i) (fun (i : ι) => _inst_1 i))) f)
Case conversion may be inaccurate. Consider using '#align quotient.choice_eq Quotient.choice_eqₓ'. -/
@[simp]
theorem Quotient.choice_eq {ι : Type _} {α : ι → Type _} [∀ i, Setoid (α i)] (f : ∀ i, α i) :
    (Quotient.choice fun i => ⟦f i⟧) = ⟦f⟧ :=
  Quotient.sound fun i => Quotient.mk_out _
#align quotient.choice_eq Quotient.choice_eq

/- warning: quotient.induction_on_pi -> Quotient.induction_on_pi is a dubious translation:
lean 3 declaration is
  forall {ι : Type.{u1}} {α : ι -> Sort.{u2}} [s : forall (i : ι), Setoid.{u2} (α i)] {p : (forall (i : ι), Quotient.{u2} (α i) (s i)) -> Prop} (f : forall (i : ι), Quotient.{u2} (α i) (s i)), (forall (a : forall (i : ι), α i), p (fun (i : ι) => Quotient.mk'.{u2} (α i) (s i) (a i))) -> (p f)
but is expected to have type
  forall {ι : Type.{u2}} {α : ι -> Sort.{u1}} [s : forall (i : ι), Setoid.{u1} (α i)] {p : (forall (i : ι), Quotient.{u1} (α i) (s i)) -> Prop} (f : forall (i : ι), Quotient.{u1} (α i) (s i)), (forall (a : forall (i : ι), α i), p (fun (i : ι) => Quotient.mk.{u1} (α i) (s i) (a i))) -> (p f)
Case conversion may be inaccurate. Consider using '#align quotient.induction_on_pi Quotient.induction_on_piₓ'. -/
@[elab_as_elim]
theorem Quotient.induction_on_pi {ι : Type _} {α : ι → Sort _} [s : ∀ i, Setoid (α i)]
    {p : (∀ i, Quotient (s i)) → Prop} (f : ∀ i, Quotient (s i))
    (h : ∀ a : ∀ i, α i, p fun i => ⟦a i⟧) : p f :=
  by
  rw [← (funext fun i => Quotient.out_eq (f i) : (fun i => ⟦(f i).out⟧) = f)]
  apply h
#align quotient.induction_on_pi Quotient.induction_on_pi

end Pi

#print nonempty_quotient_iff /-
theorem nonempty_quotient_iff (s : Setoid α) : Nonempty (Quotient s) ↔ Nonempty α :=
  ⟨fun ⟨a⟩ => Quotient.inductionOn a Nonempty.intro, fun ⟨a⟩ => ⟨⟦a⟧⟩⟩
#align nonempty_quotient_iff nonempty_quotient_iff
-/

/-! ### Truncation -/

#print true_equivalence /-
theorem true_equivalence : @Equivalence α fun _ _ => True :=
  ⟨fun _ => trivial, fun _ _ _ => trivial, fun _ _ _ _ _ => trivial⟩
#align true_equivalence true_equivalence
-/

#print trueSetoid /-
/-- Always-true relation as a `setoid`.

Note that in later files the preferred spelling is `⊤ : setoid α`. -/
def trueSetoid : Setoid α :=
  ⟨_, true_equivalence⟩
#align true_setoid trueSetoid
-/

#print Trunc /-
/-- `trunc α` is the quotient of `α` by the always-true relation. This
  is related to the propositional truncation in HoTT, and is similar
  in effect to `nonempty α`, but unlike `nonempty α`, `trunc α` is data,
  so the VM representation is the same as `α`, and so this can be used to
  maintain computability. -/
def Trunc.{u} (α : Sort u) : Sort u :=
  @Quotient α trueSetoid
#align trunc Trunc
-/

namespace Trunc

#print Trunc.mk /-
/-- Constructor for `trunc α` -/
def mk (a : α) : Trunc α :=
  Quot.mk _ a
#align trunc.mk Trunc.mk
-/

instance [Inhabited α] : Inhabited (Trunc α) :=
  ⟨mk default⟩

#print Trunc.lift /-
/-- Any constant function lifts to a function out of the truncation -/
def lift (f : α → β) (c : ∀ a b : α, f a = f b) : Trunc α → β :=
  Quot.lift f fun a b _ => c a b
#align trunc.lift Trunc.lift
-/

#print Trunc.ind /-
theorem ind {β : Trunc α → Prop} : (∀ a : α, β (mk a)) → ∀ q : Trunc α, β q :=
  Quot.ind
#align trunc.ind Trunc.ind
-/

#print Trunc.lift_mk /-
protected theorem lift_mk (f : α → β) (c) (a : α) : lift f c (mk a) = f a :=
  rfl
#align trunc.lift_mk Trunc.lift_mk
-/

#print Trunc.liftOn /-
/-- Lift a constant function on `q : trunc α`. -/
@[reducible, elab_as_elim]
protected def liftOn (q : Trunc α) (f : α → β) (c : ∀ a b : α, f a = f b) : β :=
  lift f c q
#align trunc.lift_on Trunc.liftOn
-/

#print Trunc.induction_on /-
@[elab_as_elim]
protected theorem induction_on {β : Trunc α → Prop} (q : Trunc α) (h : ∀ a, β (mk a)) : β q :=
  ind h q
#align trunc.induction_on Trunc.induction_on
-/

#print Trunc.exists_rep /-
theorem exists_rep (q : Trunc α) : ∃ a : α, mk a = q :=
  Quot.exists_rep q
#align trunc.exists_rep Trunc.exists_rep
-/

/- warning: trunc.induction_on₂ -> Trunc.induction_on₂ is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {C : (Trunc.{u1} α) -> (Trunc.{u2} β) -> Prop} (q₁ : Trunc.{u1} α) (q₂ : Trunc.{u2} β), (forall (a : α) (b : β), C (Trunc.mk.{u1} α a) (Trunc.mk.{u2} β b)) -> (C q₁ q₂)
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} {C : (Trunc.{u2} α) -> (Trunc.{u1} β) -> Prop} (q₁ : Trunc.{u2} α) (q₂ : Trunc.{u1} β), (forall (a : α) (b : β), C (Trunc.mk.{u2} α a) (Trunc.mk.{u1} β b)) -> (C q₁ q₂)
Case conversion may be inaccurate. Consider using '#align trunc.induction_on₂ Trunc.induction_on₂ₓ'. -/
@[elab_as_elim]
protected theorem induction_on₂ {C : Trunc α → Trunc β → Prop} (q₁ : Trunc α) (q₂ : Trunc β)
    (h : ∀ a b, C (mk a) (mk b)) : C q₁ q₂ :=
  Trunc.induction_on q₁ fun a₁ => Trunc.induction_on q₂ (h a₁)
#align trunc.induction_on₂ Trunc.induction_on₂

#print Trunc.eq /-
protected theorem eq (a b : Trunc α) : a = b :=
  Trunc.induction_on₂ a b fun x y => Quot.sound trivial
#align trunc.eq Trunc.eq
-/

instance : Subsingleton (Trunc α) :=
  ⟨Trunc.eq⟩

#print Trunc.bind /-
/-- The `bind` operator for the `trunc` monad. -/
def bind (q : Trunc α) (f : α → Trunc β) : Trunc β :=
  Trunc.liftOn q f fun a b => Trunc.eq _ _
#align trunc.bind Trunc.bind
-/

#print Trunc.map /-
/-- A function `f : α → β` defines a function `map f : trunc α → trunc β`. -/
def map (f : α → β) (q : Trunc α) : Trunc β :=
  bind q (Trunc.mk ∘ f)
#align trunc.map Trunc.map
-/

instance : Monad Trunc where
  pure := @Trunc.mk
  bind := @Trunc.bind

instance : LawfulMonad Trunc where
  id_map α q := Trunc.eq _ _
  pure_bind α β q f := rfl
  bind_assoc α β γ x f g := Trunc.eq _ _

variable {C : Trunc α → Sort _}

#print Trunc.rec /-
/-- Recursion/induction principle for `trunc`. -/
@[reducible, elab_as_elim]
protected def rec (f : ∀ a, C (mk a))
    (h : ∀ a b : α, (Eq.ndrec (f a) (Trunc.eq (mk a) (mk b)) : C (mk b)) = f b) (q : Trunc α) :
    C q :=
  Quot.rec f (fun a b _ => h a b) q
#align trunc.rec Trunc.rec
-/

#print Trunc.recOn /-
/-- A version of `trunc.rec` taking `q : trunc α` as the first argument. -/
@[reducible, elab_as_elim]
protected def recOn (q : Trunc α) (f : ∀ a, C (mk a))
    (h : ∀ a b : α, (Eq.ndrec (f a) (Trunc.eq (mk a) (mk b)) : C (mk b)) = f b) : C q :=
  Trunc.rec f h q
#align trunc.rec_on Trunc.recOn
-/

#print Trunc.recOnSubsingleton /-
/-- A version of `trunc.rec_on` assuming the codomain is a `subsingleton`. -/
@[reducible, elab_as_elim]
protected def recOnSubsingleton [∀ a, Subsingleton (C (mk a))] (q : Trunc α) (f : ∀ a, C (mk a)) :
    C q :=
  Trunc.rec f (fun a b => Subsingleton.elim _ (f b)) q
#align trunc.rec_on_subsingleton Trunc.recOnSubsingleton
-/

#print Trunc.out /-
/-- Noncomputably extract a representative of `trunc α` (using the axiom of choice). -/
noncomputable def out : Trunc α → α :=
  Quot.out
#align trunc.out Trunc.out
-/

#print Trunc.out_eq /-
@[simp]
theorem out_eq (q : Trunc α) : mk q.out = q :=
  Trunc.eq _ _
#align trunc.out_eq Trunc.out_eq
-/

#print Trunc.nonempty /-
protected theorem nonempty (q : Trunc α) : Nonempty α :=
  nonempty_of_exists q.exists_rep
#align trunc.nonempty Trunc.nonempty
-/

end Trunc

/-! ### `quotient` with implicit `setoid` -/

namespace Quotient

variable {γ : Sort _} {φ : Sort _} {s₁ : Setoid α} {s₂ : Setoid β} {s₃ : Setoid γ}

/-! Versions of quotient definitions and lemmas ending in `'` use unification instead
of typeclass inference for inferring the `setoid` argument. This is useful when there are
several different quotient relations on a type, for example quotient groups, rings and modules. -/

#print Quotient.mk'' /-
/-- A version of `quotient.mk` taking `{s : setoid α}` as an implicit argument instead of an
instance argument. -/
protected def mk'' (a : α) : Quotient s₁ :=
  Quot.mk s₁.1 a
#align quotient.mk' Quotient.mk''
-/

#print Quotient.surjective_Quotient_mk'' /-
/-- `quotient.mk'` is a surjective function. -/
theorem surjective_Quotient_mk'' : Surjective (Quotient.mk'' : α → Quotient s₁) :=
  Quot.exists_rep
#align quotient.surjective_quotient_mk' Quotient.surjective_Quotient_mk''
-/

#print Quotient.liftOn' /-
/-- A version of `quotient.lift_on` taking `{s : setoid α}` as an implicit argument instead of an
instance argument. -/
@[elab_as_elim, reducible]
protected def liftOn' (q : Quotient s₁) (f : α → φ) (h : ∀ a b, @Setoid.r α s₁ a b → f a = f b) :
    φ :=
  Quotient.liftOn q f h
#align quotient.lift_on' Quotient.liftOn'
-/

/- warning: quotient.lift_on'_mk' -> Quotient.liftOn'_mk'' is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {φ : Sort.{u2}} {s₁ : Setoid.{u1} α} (f : α -> φ) (h : forall (a : α) (b : α), (Setoid.r.{u1} α s₁ a b) -> (Eq.{u2} φ (f a) (f b))) (x : α), Eq.{u2} φ (Quotient.liftOn'.{u1, u2} α φ s₁ (Quotient.mk''.{u1} α s₁ x) f h) (f x)
but is expected to have type
  forall {α : Sort.{u2}} {φ : Sort.{u1}} {s₁ : Setoid.{u2} α} (f : α -> φ) (h : forall (a : α) (b : α), (Setoid.r.{u2} α s₁ a b) -> (Eq.{u1} φ (f a) (f b))) (x : α), Eq.{u1} φ (Quotient.liftOn'.{u2, u1} α φ s₁ (Quotient.mk''.{u2} α s₁ x) f h) (f x)
Case conversion may be inaccurate. Consider using '#align quotient.lift_on'_mk' Quotient.liftOn'_mk''ₓ'. -/
@[simp]
protected theorem liftOn'_mk'' (f : α → φ) (h) (x : α) :
    Quotient.liftOn' (@Quotient.mk'' _ s₁ x) f h = f x :=
  rfl
#align quotient.lift_on'_mk' Quotient.liftOn'_mk''

/- warning: quotient.surjective_lift_on' -> Quotient.surjective_liftOn' is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {φ : Sort.{u2}} {s₁ : Setoid.{u1} α} {f : α -> φ} (h : forall (a : α) (b : α), (Setoid.r.{u1} α s₁ a b) -> (Eq.{u2} φ (f a) (f b))), Iff (Function.Surjective.{u1, u2} (Quotient.{u1} α s₁) φ (fun (x : Quotient.{u1} α s₁) => Quotient.liftOn'.{u1, u2} α φ s₁ x f h)) (Function.Surjective.{u1, u2} α φ f)
but is expected to have type
  forall {α : Sort.{u2}} {φ : Sort.{u1}} {s₁ : Setoid.{u2} α} {f : α -> φ} (h : forall (a : α) (b : α), (Setoid.r.{u2} α s₁ a b) -> (Eq.{u1} φ (f a) (f b))), Iff (Function.Surjective.{u2, u1} (Quotient.{u2} α s₁) φ (fun (x : Quotient.{u2} α s₁) => Quotient.liftOn'.{u2, u1} α φ s₁ x f h)) (Function.Surjective.{u2, u1} α φ f)
Case conversion may be inaccurate. Consider using '#align quotient.surjective_lift_on' Quotient.surjective_liftOn'ₓ'. -/
@[simp]
theorem surjective_liftOn' {f : α → φ} (h : ∀ a b, @Setoid.r α s₁ a b → f a = f b) :
    (Surjective fun x => Quotient.liftOn' x f h) ↔ Surjective f :=
  Quot.surjective_lift _
#align quotient.surjective_lift_on' Quotient.surjective_liftOn'

#print Quotient.liftOn₂' /-
/-- A version of `quotient.lift_on₂` taking `{s₁ : setoid α} {s₂ : setoid β}` as implicit arguments
instead of instance arguments. -/
@[elab_as_elim, reducible]
protected def liftOn₂' (q₁ : Quotient s₁) (q₂ : Quotient s₂) (f : α → β → γ)
    (h : ∀ a₁ a₂ b₁ b₂, @Setoid.r α s₁ a₁ b₁ → @Setoid.r β s₂ a₂ b₂ → f a₁ a₂ = f b₁ b₂) : γ :=
  Quotient.liftOn₂ q₁ q₂ f h
#align quotient.lift_on₂' Quotient.liftOn₂'
-/

/- warning: quotient.lift_on₂'_mk' -> Quotient.liftOn₂'_mk'' is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {γ : Sort.{u3}} {s₁ : Setoid.{u1} α} {s₂ : Setoid.{u2} β} (f : α -> β -> γ) (h : forall (a₁ : α) (a₂ : β) (b₁ : α) (b₂ : β), (Setoid.r.{u1} α s₁ a₁ b₁) -> (Setoid.r.{u2} β s₂ a₂ b₂) -> (Eq.{u3} γ (f a₁ a₂) (f b₁ b₂))) (a : α) (b : β), Eq.{u3} γ (Quotient.liftOn₂'.{u1, u2, u3} α β γ s₁ s₂ (Quotient.mk''.{u1} α s₁ a) (Quotient.mk''.{u2} β s₂ b) f h) (f a b)
but is expected to have type
  forall {α : Sort.{u3}} {β : Sort.{u2}} {γ : Sort.{u1}} {s₁ : Setoid.{u3} α} {s₂ : Setoid.{u2} β} (f : α -> β -> γ) (h : forall (a₁ : α) (a₂ : β) (b₁ : α) (b₂ : β), (Setoid.r.{u3} α s₁ a₁ b₁) -> (Setoid.r.{u2} β s₂ a₂ b₂) -> (Eq.{u1} γ (f a₁ a₂) (f b₁ b₂))) (a : α) (b : β), Eq.{u1} γ (Quotient.liftOn₂'.{u3, u2, u1} α β γ s₁ s₂ (Quotient.mk''.{u3} α s₁ a) (Quotient.mk''.{u2} β s₂ b) f h) (f a b)
Case conversion may be inaccurate. Consider using '#align quotient.lift_on₂'_mk' Quotient.liftOn₂'_mk''ₓ'. -/
@[simp]
protected theorem liftOn₂'_mk'' (f : α → β → γ) (h) (a : α) (b : β) :
    Quotient.liftOn₂' (@Quotient.mk'' _ s₁ a) (@Quotient.mk'' _ s₂ b) f h = f a b :=
  rfl
#align quotient.lift_on₂'_mk' Quotient.liftOn₂'_mk''

#print Quotient.ind' /-
/-- A version of `quotient.ind` taking `{s : setoid α}` as an implicit argument instead of an
instance argument. -/
@[elab_as_elim]
protected theorem ind' {p : Quotient s₁ → Prop} (h : ∀ a, p (Quotient.mk'' a)) (q : Quotient s₁) :
    p q :=
  Quotient.ind h q
#align quotient.ind' Quotient.ind'
-/

/- warning: quotient.ind₂' -> Quotient.ind₂' is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {s₁ : Setoid.{u1} α} {s₂ : Setoid.{u2} β} {p : (Quotient.{u1} α s₁) -> (Quotient.{u2} β s₂) -> Prop}, (forall (a₁ : α) (a₂ : β), p (Quotient.mk''.{u1} α s₁ a₁) (Quotient.mk''.{u2} β s₂ a₂)) -> (forall (q₁ : Quotient.{u1} α s₁) (q₂ : Quotient.{u2} β s₂), p q₁ q₂)
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} {s₁ : Setoid.{u2} α} {s₂ : Setoid.{u1} β} {p : (Quotient.{u2} α s₁) -> (Quotient.{u1} β s₂) -> Prop}, (forall (a₁ : α) (a₂ : β), p (Quotient.mk''.{u2} α s₁ a₁) (Quotient.mk''.{u1} β s₂ a₂)) -> (forall (q₁ : Quotient.{u2} α s₁) (q₂ : Quotient.{u1} β s₂), p q₁ q₂)
Case conversion may be inaccurate. Consider using '#align quotient.ind₂' Quotient.ind₂'ₓ'. -/
/-- A version of `quotient.ind₂` taking `{s₁ : setoid α} {s₂ : setoid β}` as implicit arguments
instead of instance arguments. -/
@[elab_as_elim]
protected theorem ind₂' {p : Quotient s₁ → Quotient s₂ → Prop}
    (h : ∀ a₁ a₂, p (Quotient.mk'' a₁) (Quotient.mk'' a₂)) (q₁ : Quotient s₁) (q₂ : Quotient s₂) :
    p q₁ q₂ :=
  Quotient.ind₂ h q₁ q₂
#align quotient.ind₂' Quotient.ind₂'

#print Quotient.inductionOn' /-
/-- A version of `quotient.induction_on` taking `{s : setoid α}` as an implicit argument instead
of an instance argument. -/
@[elab_as_elim]
protected theorem inductionOn' {p : Quotient s₁ → Prop} (q : Quotient s₁)
    (h : ∀ a, p (Quotient.mk'' a)) : p q :=
  Quotient.inductionOn q h
#align quotient.induction_on' Quotient.inductionOn'
-/

/- warning: quotient.induction_on₂' -> Quotient.inductionOn₂' is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {s₁ : Setoid.{u1} α} {s₂ : Setoid.{u2} β} {p : (Quotient.{u1} α s₁) -> (Quotient.{u2} β s₂) -> Prop} (q₁ : Quotient.{u1} α s₁) (q₂ : Quotient.{u2} β s₂), (forall (a₁ : α) (a₂ : β), p (Quotient.mk''.{u1} α s₁ a₁) (Quotient.mk''.{u2} β s₂ a₂)) -> (p q₁ q₂)
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} {s₁ : Setoid.{u2} α} {s₂ : Setoid.{u1} β} {p : (Quotient.{u2} α s₁) -> (Quotient.{u1} β s₂) -> Prop} (q₁ : Quotient.{u2} α s₁) (q₂ : Quotient.{u1} β s₂), (forall (a₁ : α) (a₂ : β), p (Quotient.mk''.{u2} α s₁ a₁) (Quotient.mk''.{u1} β s₂ a₂)) -> (p q₁ q₂)
Case conversion may be inaccurate. Consider using '#align quotient.induction_on₂' Quotient.inductionOn₂'ₓ'. -/
/-- A version of `quotient.induction_on₂` taking `{s₁ : setoid α} {s₂ : setoid β}` as implicit
arguments instead of instance arguments. -/
@[elab_as_elim]
protected theorem inductionOn₂' {p : Quotient s₁ → Quotient s₂ → Prop} (q₁ : Quotient s₁)
    (q₂ : Quotient s₂) (h : ∀ a₁ a₂, p (Quotient.mk'' a₁) (Quotient.mk'' a₂)) : p q₁ q₂ :=
  Quotient.induction_on₂ q₁ q₂ h
#align quotient.induction_on₂' Quotient.inductionOn₂'

/- warning: quotient.induction_on₃' -> Quotient.inductionOn₃' is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {γ : Sort.{u3}} {s₁ : Setoid.{u1} α} {s₂ : Setoid.{u2} β} {s₃ : Setoid.{u3} γ} {p : (Quotient.{u1} α s₁) -> (Quotient.{u2} β s₂) -> (Quotient.{u3} γ s₃) -> Prop} (q₁ : Quotient.{u1} α s₁) (q₂ : Quotient.{u2} β s₂) (q₃ : Quotient.{u3} γ s₃), (forall (a₁ : α) (a₂ : β) (a₃ : γ), p (Quotient.mk''.{u1} α s₁ a₁) (Quotient.mk''.{u2} β s₂ a₂) (Quotient.mk''.{u3} γ s₃ a₃)) -> (p q₁ q₂ q₃)
but is expected to have type
  forall {α : Sort.{u3}} {β : Sort.{u2}} {γ : Sort.{u1}} {s₁ : Setoid.{u3} α} {s₂ : Setoid.{u2} β} {s₃ : Setoid.{u1} γ} {p : (Quotient.{u3} α s₁) -> (Quotient.{u2} β s₂) -> (Quotient.{u1} γ s₃) -> Prop} (q₁ : Quotient.{u3} α s₁) (q₂ : Quotient.{u2} β s₂) (q₃ : Quotient.{u1} γ s₃), (forall (a₁ : α) (a₂ : β) (a₃ : γ), p (Quotient.mk''.{u3} α s₁ a₁) (Quotient.mk''.{u2} β s₂ a₂) (Quotient.mk''.{u1} γ s₃ a₃)) -> (p q₁ q₂ q₃)
Case conversion may be inaccurate. Consider using '#align quotient.induction_on₃' Quotient.inductionOn₃'ₓ'. -/
/-- A version of `quotient.induction_on₃` taking `{s₁ : setoid α} {s₂ : setoid β} {s₃ : setoid γ}`
as implicit arguments instead of instance arguments. -/
@[elab_as_elim]
protected theorem inductionOn₃' {p : Quotient s₁ → Quotient s₂ → Quotient s₃ → Prop}
    (q₁ : Quotient s₁) (q₂ : Quotient s₂) (q₃ : Quotient s₃)
    (h : ∀ a₁ a₂ a₃, p (Quotient.mk'' a₁) (Quotient.mk'' a₂) (Quotient.mk'' a₃)) : p q₁ q₂ q₃ :=
  Quotient.induction_on₃ q₁ q₂ q₃ h
#align quotient.induction_on₃' Quotient.inductionOn₃'

#print Quotient.recOnSubsingleton' /-
/-- A version of `quotient.rec_on_subsingleton` taking `{s₁ : setoid α}` as an implicit argument
instead of an instance argument. -/
@[elab_as_elim]
protected def recOnSubsingleton' {φ : Quotient s₁ → Sort _} [h : ∀ a, Subsingleton (φ ⟦a⟧)]
    (q : Quotient s₁) (f : ∀ a, φ (Quotient.mk'' a)) : φ q :=
  Quotient.recOnSubsingleton q f
#align quotient.rec_on_subsingleton' Quotient.recOnSubsingleton'
-/

#print Quotient.recOnSubsingleton₂' /-
/-- A version of `quotient.rec_on_subsingleton₂` taking `{s₁ : setoid α} {s₂ : setoid α}`
as implicit arguments instead of instance arguments. -/
@[reducible, elab_as_elim]
protected def recOnSubsingleton₂' {φ : Quotient s₁ → Quotient s₂ → Sort _}
    [h : ∀ a b, Subsingleton (φ ⟦a⟧ ⟦b⟧)] (q₁ : Quotient s₁) (q₂ : Quotient s₂)
    (f : ∀ a₁ a₂, φ (Quotient.mk'' a₁) (Quotient.mk'' a₂)) : φ q₁ q₂ :=
  Quotient.recOnSubsingleton₂ q₁ q₂ f
#align quotient.rec_on_subsingleton₂' Quotient.recOnSubsingleton₂'
-/

#print Quotient.hrecOn' /-
/-- Recursion on a `quotient` argument `a`, result type depends on `⟦a⟧`. -/
protected def hrecOn' {φ : Quotient s₁ → Sort _} (qa : Quotient s₁) (f : ∀ a, φ (Quotient.mk'' a))
    (c : ∀ a₁ a₂, a₁ ≈ a₂ → HEq (f a₁) (f a₂)) : φ qa :=
  Quot.hrecOn qa f c
#align quotient.hrec_on' Quotient.hrecOn'
-/

/- warning: quotient.hrec_on'_mk' -> Quotient.hrecOn'_mk'' is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {s₁ : Setoid.{u1} α} {φ : (Quotient.{u1} α s₁) -> Sort.{u2}} (f : forall (a : α), φ (Quotient.mk''.{u1} α s₁ a)) (c : forall (a₁ : α) (a₂ : α), (HasEquivₓ.Equiv.{u1} α (setoidHasEquiv.{u1} α s₁) a₁ a₂) -> (HEq.{u2} (φ (Quotient.mk''.{u1} α s₁ a₁)) (f a₁) (φ (Quotient.mk''.{u1} α s₁ a₂)) (f a₂))) (x : α), Eq.{u2} (φ (Quotient.mk''.{u1} α s₁ x)) (Quotient.hrecOn'.{u1, u2} α s₁ φ (Quotient.mk''.{u1} α s₁ x) f c) (f x)
but is expected to have type
  forall {α : Sort.{u2}} {s₁ : Setoid.{u2} α} {φ : (Quotient.{u2} α s₁) -> Sort.{u1}} (f : forall (a : α), φ (Quotient.mk''.{u2} α s₁ a)) (c : forall (a₁ : α) (a₂ : α), (HasEquiv.Equiv.{u2, 0} α (instHasEquiv.{u2} α s₁) a₁ a₂) -> (HEq.{u1} (φ (Quotient.mk''.{u2} α s₁ a₁)) (f a₁) (φ (Quotient.mk''.{u2} α s₁ a₂)) (f a₂))) (x : α), Eq.{u1} (φ (Quotient.mk''.{u2} α s₁ x)) (Quotient.hrecOn'.{u2, u1} α s₁ φ (Quotient.mk''.{u2} α s₁ x) f c) (f x)
Case conversion may be inaccurate. Consider using '#align quotient.hrec_on'_mk' Quotient.hrecOn'_mk''ₓ'. -/
@[simp]
theorem hrecOn'_mk'' {φ : Quotient s₁ → Sort _} (f : ∀ a, φ (Quotient.mk'' a))
    (c : ∀ a₁ a₂, a₁ ≈ a₂ → HEq (f a₁) (f a₂)) (x : α) : (Quotient.mk'' x).hrecOn' f c = f x :=
  rfl
#align quotient.hrec_on'_mk' Quotient.hrecOn'_mk''

#print Quotient.hrecOn₂' /-
/-- Recursion on two `quotient` arguments `a` and `b`, result type depends on `⟦a⟧` and `⟦b⟧`. -/
protected def hrecOn₂' {φ : Quotient s₁ → Quotient s₂ → Sort _} (qa : Quotient s₁)
    (qb : Quotient s₂) (f : ∀ a b, φ (Quotient.mk'' a) (Quotient.mk'' b))
    (c : ∀ a₁ b₁ a₂ b₂, a₁ ≈ a₂ → b₁ ≈ b₂ → HEq (f a₁ b₁) (f a₂ b₂)) : φ qa qb :=
  Quotient.hrecOn₂ qa qb f c
#align quotient.hrec_on₂' Quotient.hrecOn₂'
-/

/- warning: quotient.hrec_on₂'_mk' -> Quotient.hrecOn₂'_mk'' is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {s₁ : Setoid.{u1} α} {s₂ : Setoid.{u2} β} {φ : (Quotient.{u1} α s₁) -> (Quotient.{u2} β s₂) -> Sort.{u3}} (f : forall (a : α) (b : β), φ (Quotient.mk''.{u1} α s₁ a) (Quotient.mk''.{u2} β s₂ b)) (c : forall (a₁ : α) (b₁ : β) (a₂ : α) (b₂ : β), (HasEquivₓ.Equiv.{u1} α (setoidHasEquiv.{u1} α s₁) a₁ a₂) -> (HasEquivₓ.Equiv.{u2} β (setoidHasEquiv.{u2} β s₂) b₁ b₂) -> (HEq.{u3} (φ (Quotient.mk''.{u1} α s₁ a₁) (Quotient.mk''.{u2} β s₂ b₁)) (f a₁ b₁) (φ (Quotient.mk''.{u1} α s₁ a₂) (Quotient.mk''.{u2} β s₂ b₂)) (f a₂ b₂))) (x : α) (qb : Quotient.{u2} β s₂), Eq.{u3} (φ (Quotient.mk''.{u1} α s₁ x) qb) (Quotient.hrecOn₂'.{u1, u2, u3} α β s₁ s₂ φ (Quotient.mk''.{u1} α s₁ x) qb f c) (Quotient.hrecOn'.{u2, u3} β s₂ (fun (qb : Quotient.{u2} β s₂) => φ (Quotient.mk''.{u1} α s₁ x) qb) qb (f x) (fun (b₁ : β) (b₂ : β) => c x b₁ x b₂ (Setoid.refl.{u1} α s₁ x)))
but is expected to have type
  forall {α : Sort.{u3}} {β : Sort.{u2}} {s₁ : Setoid.{u3} α} {s₂ : Setoid.{u2} β} {φ : (Quotient.{u3} α s₁) -> (Quotient.{u2} β s₂) -> Sort.{u1}} (f : forall (a : α) (b : β), φ (Quotient.mk''.{u3} α s₁ a) (Quotient.mk''.{u2} β s₂ b)) (c : forall (a₁ : α) (b₁ : β) (a₂ : α) (b₂ : β), (HasEquiv.Equiv.{u3, 0} α (instHasEquiv.{u3} α s₁) a₁ a₂) -> (HasEquiv.Equiv.{u2, 0} β (instHasEquiv.{u2} β s₂) b₁ b₂) -> (HEq.{u1} (φ (Quotient.mk''.{u3} α s₁ a₁) (Quotient.mk''.{u2} β s₂ b₁)) (f a₁ b₁) (φ (Quotient.mk''.{u3} α s₁ a₂) (Quotient.mk''.{u2} β s₂ b₂)) (f a₂ b₂))) (x : α) (qb : Quotient.{u2} β s₂), Eq.{u1} (φ (Quotient.mk''.{u3} α s₁ x) qb) (Quotient.hrecOn₂'.{u3, u2, u1} α β s₁ s₂ φ (Quotient.mk''.{u3} α s₁ x) qb f c) (Quotient.hrecOn'.{u2, u1} β s₂ (φ (Quotient.mk''.{u3} α s₁ x)) qb (f x) (fun (b₁ : β) (b₂ : β) => c x b₁ x b₂ (Setoid.refl.{u3} α s₁ x)))
Case conversion may be inaccurate. Consider using '#align quotient.hrec_on₂'_mk' Quotient.hrecOn₂'_mk''ₓ'. -/
@[simp]
theorem hrecOn₂'_mk'' {φ : Quotient s₁ → Quotient s₂ → Sort _}
    (f : ∀ a b, φ (Quotient.mk'' a) (Quotient.mk'' b))
    (c : ∀ a₁ b₁ a₂ b₂, a₁ ≈ a₂ → b₁ ≈ b₂ → HEq (f a₁ b₁) (f a₂ b₂)) (x : α) (qb : Quotient s₂) :
    (Quotient.mk'' x).hrecOn₂' qb f c = qb.hrecOn' (f x) fun b₁ b₂ => c _ _ _ _ (Setoid.refl _) :=
  rfl
#align quotient.hrec_on₂'_mk' Quotient.hrecOn₂'_mk''

#print Quotient.map' /-
/-- Map a function `f : α → β` that sends equivalent elements to equivalent elements
to a function `quotient sa → quotient sb`. Useful to define unary operations on quotients. -/
protected def map' (f : α → β) (h : (s₁.R ⇒ s₂.R) f f) : Quotient s₁ → Quotient s₂ :=
  Quot.map f h
#align quotient.map' Quotient.map'
-/

/- warning: quotient.map'_mk' -> Quotient.map'_mk'' is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {s₁ : Setoid.{u1} α} {s₂ : Setoid.{u2} β} (f : α -> β) (h : Relator.LiftFun.{u1, u1, u2, u2} α α β β (Setoid.r.{u1} α s₁) (Setoid.r.{u2} β s₂) f f) (x : α), Eq.{u2} (Quotient.{u2} β s₂) (Quotient.map'.{u1, u2} α β s₁ s₂ f h (Quotient.mk''.{u1} α s₁ x)) (Quotient.mk''.{u2} β s₂ (f x))
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} {s₁ : Setoid.{u2} α} {s₂ : Setoid.{u1} β} (f : α -> β) (h : Relator.LiftFun.{u2, u2, u1, u1} α α β β (Setoid.r.{u2} α s₁) (Setoid.r.{u1} β s₂) f f) (x : α), Eq.{u1} (Quotient.{u1} β s₂) (Quotient.map'.{u2, u1} α β s₁ s₂ f h (Quotient.mk''.{u2} α s₁ x)) (Quotient.mk''.{u1} β s₂ (f x))
Case conversion may be inaccurate. Consider using '#align quotient.map'_mk' Quotient.map'_mk''ₓ'. -/
@[simp]
theorem map'_mk'' (f : α → β) (h) (x : α) :
    (Quotient.mk'' x : Quotient s₁).map' f h = (Quotient.mk'' (f x) : Quotient s₂) :=
  rfl
#align quotient.map'_mk' Quotient.map'_mk''

#print Quotient.map₂' /-
/-- A version of `quotient.map₂` using curly braces and unification. -/
protected def map₂' (f : α → β → γ) (h : (s₁.R ⇒ s₂.R ⇒ s₃.R) f f) :
    Quotient s₁ → Quotient s₂ → Quotient s₃ :=
  Quotient.map₂ f h
#align quotient.map₂' Quotient.map₂'
-/

/- warning: quotient.map₂'_mk' -> Quotient.map₂'_mk'' is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {γ : Sort.{u3}} {s₁ : Setoid.{u1} α} {s₂ : Setoid.{u2} β} {s₃ : Setoid.{u3} γ} (f : α -> β -> γ) (h : Relator.LiftFun.{u1, u1, imax u2 u3, imax u2 u3} α α (β -> γ) (β -> γ) (Setoid.r.{u1} α s₁) (Relator.LiftFun.{u2, u2, u3, u3} β β γ γ (Setoid.r.{u2} β s₂) (Setoid.r.{u3} γ s₃)) f f) (x : α), Eq.{imax u2 u3} ((Quotient.{u2} β s₂) -> (Quotient.{u3} γ s₃)) (Quotient.map₂'.{u1, u2, u3} α β γ s₁ s₂ s₃ f h (Quotient.mk''.{u1} α s₁ x)) (Quotient.map'.{u2, u3} β γ s₂ s₃ (f x) (h x x (Setoid.refl.{u1} α s₁ x)))
but is expected to have type
  forall {α : Sort.{u3}} {β : Sort.{u2}} {γ : Sort.{u1}} {s₁ : Setoid.{u3} α} {s₂ : Setoid.{u2} β} {s₃ : Setoid.{u1} γ} (f : α -> β -> γ) (h : Relator.LiftFun.{u3, u3, imax u2 u1, imax u2 u1} α α (β -> γ) (β -> γ) (Setoid.r.{u3} α s₁) (Relator.LiftFun.{u2, u2, u1, u1} β β γ γ (Setoid.r.{u2} β s₂) (Setoid.r.{u1} γ s₃)) f f) (x : α), Eq.{imax u2 u1} ((Quotient.{u2} β s₂) -> (Quotient.{u1} γ s₃)) (Quotient.map₂'.{u3, u2, u1} α β γ s₁ s₂ s₃ f h (Quotient.mk''.{u3} α s₁ x)) (Quotient.map'.{u2, u1} β γ s₂ s₃ (f x) (h x x (Setoid.refl.{u3} α s₁ x)))
Case conversion may be inaccurate. Consider using '#align quotient.map₂'_mk' Quotient.map₂'_mk''ₓ'. -/
@[simp]
theorem map₂'_mk'' (f : α → β → γ) (h) (x : α) :
    (Quotient.mk'' x : Quotient s₁).map₂' f h =
      (Quotient.map' (f x) (h (Setoid.refl x)) : Quotient s₂ → Quotient s₃) :=
  rfl
#align quotient.map₂'_mk' Quotient.map₂'_mk''

#print Quotient.exact' /-
theorem exact' {a b : α} : (Quotient.mk'' a : Quotient s₁) = Quotient.mk'' b → @Setoid.r _ s₁ a b :=
  Quotient.exact
#align quotient.exact' Quotient.exact'
-/

#print Quotient.sound' /-
theorem sound' {a b : α} : @Setoid.r _ s₁ a b → @Quotient.mk'' α s₁ a = Quotient.mk'' b :=
  Quotient.sound
#align quotient.sound' Quotient.sound'
-/

#print Quotient.eq'' /-
@[simp]
protected theorem eq'' {a b : α} : @Quotient.mk'' α s₁ a = Quotient.mk'' b ↔ @Setoid.r _ s₁ a b :=
  Quotient.eq'
#align quotient.eq' Quotient.eq''
-/

#print Quotient.out' /-
/-- A version of `quotient.out` taking `{s₁ : setoid α}` as an implicit argument instead of an
instance argument. -/
noncomputable def out' (a : Quotient s₁) : α :=
  Quotient.out a
#align quotient.out' Quotient.out'
-/

#print Quotient.out_eq' /-
@[simp]
theorem out_eq' (q : Quotient s₁) : Quotient.mk'' q.out' = q :=
  q.out_eq
#align quotient.out_eq' Quotient.out_eq'
-/

#print Quotient.mk_out' /-
theorem mk_out' (a : α) : @Setoid.r α s₁ (Quotient.mk'' a : Quotient s₁).out' a :=
  Quotient.exact (Quotient.out_eq _)
#align quotient.mk_out' Quotient.mk_out'
-/

section

variable [Setoid α]

protected theorem mk''_eq_mk' (x : α) : Quotient.mk'' x = ⟦x⟧ :=
  rfl
#align quotient.mk'_eq_mk Quotient.mk''_eq_mk'

/- warning: quotient.lift_on'_mk -> Quotient.liftOn'_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} [_inst_1 : Setoid.{u1} α] (x : α) (f : α -> β) (h : forall (a : α) (b : α), (Setoid.r.{u1} α _inst_1 a b) -> (Eq.{u2} β (f a) (f b))), Eq.{u2} β (Quotient.liftOn'.{u1, u2} α β _inst_1 (Quotient.mk'.{u1} α _inst_1 x) f h) (f x)
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u1}} [_inst_1 : Setoid.{u2} α] (x : α) (f : α -> β) (h : forall (a : α) (b : α), (Setoid.r.{u2} α _inst_1 a b) -> (Eq.{u1} β (f a) (f b))), Eq.{u1} β (Quotient.liftOn'.{u2, u1} α β _inst_1 (Quotient.mk.{u2} α _inst_1 x) f h) (f x)
Case conversion may be inaccurate. Consider using '#align quotient.lift_on'_mk Quotient.liftOn'_mkₓ'. -/
@[simp]
protected theorem liftOn'_mk (x : α) (f : α → β) (h) : ⟦x⟧.liftOn' f h = f x :=
  rfl
#align quotient.lift_on'_mk Quotient.liftOn'_mk

/- warning: quotient.lift_on₂'_mk -> Quotient.liftOn₂'_mk is a dubious translation:
lean 3 declaration is
  forall {α : Sort.{u1}} {β : Sort.{u2}} {γ : Sort.{u3}} [_inst_1 : Setoid.{u1} α] [_inst_2 : Setoid.{u2} β] (f : α -> β -> γ) (h : forall (a₁ : α) (a₂ : β) (b₁ : α) (b₂ : β), (Setoid.r.{u1} α _inst_1 a₁ b₁) -> (Setoid.r.{u2} β _inst_2 a₂ b₂) -> (Eq.{u3} γ (f a₁ a₂) (f b₁ b₂))) (a : α) (b : β), Eq.{u3} γ (Quotient.liftOn₂'.{u1, u2, u3} α β γ _inst_1 _inst_2 (Quotient.mk'.{u1} α _inst_1 a) (Quotient.mk'.{u2} β _inst_2 b) f h) (f a b)
but is expected to have type
  forall {α : Sort.{u2}} {β : Sort.{u3}} {γ : Sort.{u1}} [_inst_1 : Setoid.{u2} α] [_inst_2 : Setoid.{u3} β] (f : α -> β -> γ) (h : forall (a₁ : α) (a₂ : β) (b₁ : α) (b₂ : β), (Setoid.r.{u2} α _inst_1 a₁ b₁) -> (Setoid.r.{u3} β _inst_2 a₂ b₂) -> (Eq.{u1} γ (f a₁ a₂) (f b₁ b₂))) (a : α) (b : β), Eq.{u1} γ (Quotient.liftOn₂'.{u2, u3, u1} α β γ _inst_1 _inst_2 (Quotient.mk.{u2} α _inst_1 a) (Quotient.mk.{u3} β _inst_2 b) f h) (f a b)
Case conversion may be inaccurate. Consider using '#align quotient.lift_on₂'_mk Quotient.liftOn₂'_mkₓ'. -/
@[simp]
protected theorem liftOn₂'_mk [Setoid β] (f : α → β → γ) (h) (a : α) (b : β) :
    Quotient.liftOn₂' ⟦a⟧ ⟦b⟧ f h = f a b :=
  Quotient.liftOn₂'_mk'' _ _ _ _
#align quotient.lift_on₂'_mk Quotient.liftOn₂'_mk

#print Quotient.map'_mk /-
@[simp]
theorem map'_mk [Setoid β] (f : α → β) (h) (x : α) : ⟦x⟧.map' f h = ⟦f x⟧ :=
  rfl
#align quotient.map'_mk Quotient.map'_mk
-/

end

instance (q : Quotient s₁) (f : α → Prop) (h : ∀ a b, @Setoid.r α s₁ a b → f a = f b)
    [DecidablePred f] : Decidable (Quotient.liftOn' q f h) :=
  Quotient.lift.decidablePred _ _ q

instance (q₁ : Quotient s₁) (q₂ : Quotient s₂) (f : α → β → Prop)
    (h : ∀ a₁ b₁ a₂ b₂, @Setoid.r α s₁ a₁ a₂ → @Setoid.r β s₂ b₁ b₂ → f a₁ b₁ = f a₂ b₂)
    [∀ a, DecidablePred (f a)] : Decidable (Quotient.liftOn₂' q₁ q₂ f h) :=
  Quotient.lift₂.decidablePred _ _ _ _

end Quotient


/-
Copyright (c) 2020 Patrick Massot. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Patrick Massot
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.uniform_space.separation
import Mathlib.PostPort

universes u_1 u_2 u_3

namespace Mathlib

/-!
# Compact separated uniform spaces

## Main statements

* `compact_space_uniformity`: On a separated compact uniform space, the topology determines the
  uniform structure, entourages are exactly the neighborhoods of the diagonal.
* `uniform_space_of_compact_t2`: every compact T2 topological structure is induced by a uniform
  structure. This uniform structure is described in the previous item.
* Heine-Cantor theorem: continuous functions on compact separated uniform spaces with values in
  uniform spaces are automatically uniformly continuous. There are several variations, the main one
  is `compact_space.uniform_continuous_of_continuous`.

## Implementation notes

The construction `uniform_space_of_compact_t2` is not declared as an instance, as it would badly
loop.

## tags

uniform space, uniform continuity, compact space
-/

/-!
### Uniformity on compact separated spaces
-/

/-- On a separated compact uniform space, the topology determines the uniform structure, entourages
are exactly the neighborhoods of the diagonal. -/
theorem compact_space_uniformity {α : Type u_1} [uniform_space α] [compact_space α]
    [separated_space α] : uniformity α = supr fun (x : α) => nhds (x, x) :=
  sorry

theorem unique_uniformity_of_compact_t2 {α : Type u_1} [t : topological_space α] [compact_space α]
    [t2_space α] {u : uniform_space α} {u' : uniform_space α}
    (h : uniform_space.to_topological_space = t) (h' : uniform_space.to_topological_space = t) :
    u = u' :=
  sorry

/-- The unique uniform structure inducing a given compact Hausdorff topological structure. -/
def uniform_space_of_compact_t2 {α : Type (max (max u_1 u_2 u_3) u_2)} [topological_space α]
    [compact_space α] [t2_space α] : uniform_space α :=
  uniform_space.mk (uniform_space.core.mk (supr fun (x : α) => nhds (x, x)) sorry sorry sorry) sorry

/-!
### Heine-Cantor theorem
-/

/-- Heine-Cantor: a continuous function on a compact separated uniform space is uniformly
continuous. -/
theorem compact_space.uniform_continuous_of_continuous {α : Type u_1} {β : Type u_2}
    [uniform_space α] [uniform_space β] [compact_space α] [separated_space α] {f : α → β}
    (h : continuous f) : uniform_continuous f :=
  sorry

/-- Heine-Cantor: a continuous function on a compact separated set of a uniform space is
uniformly continuous. -/
theorem is_compact.uniform_continuous_on_of_continuous' {α : Type u_1} {β : Type u_2}
    [uniform_space α] [uniform_space β] {s : set α} {f : α → β} (hs : is_compact s)
    (hs' : is_separated s) (hf : continuous_on f s) : uniform_continuous_on f s :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (uniform_continuous_on f s))
        (propext uniform_continuous_on_iff_restrict)))
    (compact_space.uniform_continuous_of_continuous
      (eq.mp
        (Eq._oldrec (Eq.refl (continuous_on f s)) (propext continuous_on_iff_continuous_restrict))
        hf))

/-- Heine-Cantor: a continuous function on a compact set of a separated uniform space
is uniformly continuous. -/
theorem is_compact.uniform_continuous_on_of_continuous {α : Type u_1} {β : Type u_2}
    [uniform_space α] [uniform_space β] [separated_space α] {s : set α} {f : α → β}
    (hs : is_compact s) (hf : continuous_on f s) : uniform_continuous_on f s :=
  is_compact.uniform_continuous_on_of_continuous' hs (is_separated_of_separated_space s) hf

end Mathlib


import Mathlib.Tactic.Basic

example : True := by
  triv

example : 2 + 2 = 4 := by
  triv

-- Verify the difference in behaviour between `triv` and `trivial`.
example (P : Prop) (h1 : P) (h2 : ¬ P) : False := by
  fail_if_success triv -- fails
  trivial -- succeeds