/-- **In sets, the epimorphisms are the surjections.** A function `f : X → Y` is an
epimorphism if `g ∘ f = h ∘ f` implies `g = h` for all `g, h : Y → Z`.
If `f` is an epimorphism, compare two maps into the two-element "set of truth values":
`g y = (y is in the image of f)` and `h y = true`. They agree after `f`, so they agree, so every
`y` is in the image. Conversely, if `f` is surjective, every `y` is some `f x`, where `g` and `h`
agree. (`ULift` only moves the type of propositions into the universe of `Y`.) -/
theorem epi_iff_surjective {X Y : Type u} (f : X → Y) :
(∀ (Z : Type u) (g h : Y → Z), g ∘ f = h ∘ f → g = h) ↔ Function.Surjective f := by
constructor
· intro hf y
let g : Y → ULift.{u} Prop := fun y => ULift.up (∃ x, f x = y)
let h : Y → ULift.{u} Prop := fun _ => ULift.up True
have hgh : g ∘ f = h ∘ f := by
funext x
simp only [Function.comp, g, h, ULift.up.injEq, eq_iff_iff, iff_true]
exact ⟨x, rfl⟩
have hy : g y = h y := congrFun (hf _ g h hgh) y
simp only [g, h, ULift.up.injEq, eq_iff_iff, iff_true] at hy
exact hy
· intro hf Z g h hgh
funext y
obtain ⟨x, rfl⟩ := hf y
exact congrFun hgh x