8. Hierarki

Dalam Bab 7, kita telah melihat cara mendefinisikan kelas grup dan membangun instans kelas tersebut, lalu cara membangun instans kelas gelanggang komutatif. Namun, tentu saja terdapat hierarki di sini: gelanggang komutatif khususnya merupakan grup aditif. Dalam bab ini, kita akan mempelajari cara membangun hierarki semacam itu. Hierarki muncul di semua cabang matematika, tetapi dalam bab ini penekanannya terletak pada contoh-contoh aljabar.

Membahas cara membangun hierarki sebelum membahas lebih jauh penggunaan hierarki yang sudah ada mungkin terasa terlalu dini. Namun, pemahaman tertentu tentang teknologi yang mendasari hierarki diperlukan untuk menggunakannya. Karena itu, Anda sebaiknya tetap membaca bab ini, tetapi pada pembacaan pertama tidak perlu terlalu berusaha mengingat semuanya. Setelah itu, bacalah bab-bab berikutnya dan kembali ke sini untuk pembacaan kedua.

Dalam bab ini, kita akan mendefinisikan ulang versi yang lebih sederhana dari banyak hal yang terdapat di Mathlib, sehingga kita akan menggunakan indeks untuk membedakan versi kita. Misalnya, Ring₁ akan menjadi versi kita dari Ring. Karena kita akan secara bertahap menjelaskan cara-cara yang lebih canggih untuk memformalkan struktur, indeks tersebut kadang-kadang akan bertambah melampaui satu.

8.1. Dasar-Dasar

Pada lapisan paling bawah dari semua hierarki di Lean, terdapat kelas-kelas yang membawa data. Kelas berikut mencatat bahwa tipe α yang diberikan dilengkapi sebuah elemen khusus bernama one. Pada tahap ini, elemen tersebut sama sekali belum memiliki sifat apa pun.

class One₁ (α : Type) where
  /-- Elemen satu -/
  one : α

Karena dalam bab ini kita akan jauh lebih banyak menggunakan kelas, kita perlu memahami secara lebih terperinci apa yang dilakukan perintah class. Pertama, perintah class di atas mendefinisikan struktur One₁ dengan parameter α : Type dan satu field one. Perintah tersebut juga menandai struktur ini sebagai kelas, sehingga argumen bertipe One₁ α untuk suatu tipe α dapat disimpulkan melalui prosedur resolusi instans, asalkan ditandai sebagai argumen implisit-instans, yakni ditulis di antara tanda kurung siku. Kedua efek ini juga dapat diperoleh dengan menggunakan perintah structure beserta atribut class, yakni dengan menulis @[class] structure alih-alih class. Namun, perintah untuk mendefinisikan kelas juga memastikan bahwa One₁ α muncul sebagai argumen implisit-instans pada field-fieldnya sendiri. Bandingkan:

#check One₁.one -- One₁.one {α : Type} [self : One₁ α] : α

@[class] structure One₂ (α : Type) where
  /-- Elemen satu -/
  one : α

#check One₂.one

Pada pemeriksaan kedua, terlihat bahwa self : One₂ α merupakan argumen eksplisit. Mari kita pastikan bahwa versi pertama memang dapat digunakan tanpa argumen eksplisit apa pun.

example (α : Type) [One₁ α] : α := One₁.one

Catatan: dalam contoh di atas, argumen One₁ α ditandai sebagai implisit-instans. Hal ini sedikit ganjil karena penandaan tersebut hanya memengaruhi penggunaan deklarasi, sedangkan deklarasi yang dibuat oleh perintah example tidak dapat digunakan. Namun, cara ini memungkinkan kita tidak memberi nama pada argumen tersebut dan, yang lebih penting, mulai membiasakan diri menandai argumen One₁ α sebagai implisit-instans.

Catatan lainnya adalah bahwa semua ini hanya akan bekerja apabila Lean mengetahui tipe α. Dalam contoh di atas, jika penetapan tipe : α dihilangkan, Lean akan menghasilkan pesan galat seperti: typeclass instance problem is stuck, it is often due to metavariables One₁ (?m.263 α). Di sini, ?m.263 α berarti "suatu tipe yang bergantung pada α" (dan 263 hanyalah indeks yang dibuat secara otomatis untuk membedakan beberapa hal yang belum diketahui). Cara lain untuk menghindari masalah ini adalah menggunakan anotasi tipe, seperti berikut:

example (α : Type) [One₁ α] := (One₁.one : α)

Anda mungkin telah menjumpai masalah ini ketika bereksperimen dengan limit barisan di Bab 3.6. Misalnya, hal itu terjadi jika Anda mencoba menyatakan 0 < 1 tanpa memberi tahu Lean apakah pertidaksamaan tersebut berkaitan dengan bilangan asli atau bilangan real.

Tugas kita berikutnya adalah memberikan notasi kepada One₁.one. Karena kita tidak ingin berbenturan dengan notasi bawaan untuk 1, kita akan menggunakan 𝟙. Hal ini dilakukan oleh perintah berikut; baris pertamanya meminta Lean menggunakan dokumentasi One₁.one sebagai dokumentasi untuk simbol 𝟙.

@[inherit_doc]
notation "𝟙" => One₁.one

example {α : Type} [One₁ α] : α := 𝟙

example {α : Type} [One₁ α] : (𝟙 : α) = 𝟙 := rfl

Sekarang kita menginginkan kelas pembawa data yang mencatat suatu operasi biner. Untuk saat ini, kita tidak ingin memilih antara penjumlahan dan perkalian, sehingga kita akan menggunakan simbol wajik.

class Dia₁ (α : Type) where
  dia : α  α  α

infixl:70 " ⋄ "   => Dia₁.dia

Seperti dalam contoh One₁, pada tahap ini operasinya sama sekali belum memiliki sifat apa pun. Sekarang mari kita definisikan kelas struktur semigrup yang operasinya dinyatakan dengan . Untuk sementara, kita mendefinisikannya secara manual sebagai struktur dengan dua field: sebuah instans Dia₁ dan field bernilai Prop bernama dia_assoc yang menyatakan sifat asosiatif dari .

class Semigroup₀ (α : Type) where
  toDia₁ : Dia₁ α
  /-- Operasi wajik bersifat asosiatif -/
  dia_assoc :  a b c : α, a  b  c = a  (b  c)

Perhatikan bahwa ketika dia_assoc dinyatakan, field toDia₁ yang telah didefinisikan berada dalam konteks lokal. Karena itu, field tersebut dapat digunakan ketika Lean mencari instans Dia₁ α untuk memaknai a b. Namun, field toDia₁ ini tidak menjadi bagian dari basis data instans kelas tipe. Akibatnya, penulisan example : Type} [Semigroup₀ α] (a b : α) : α := a b akan gagal dengan pesan galat failed to synthesize instance Dia₁ α.

Kita dapat memperbaikinya dengan menambahkan atribut instance sesudahnya.

attribute [instance] Semigroup₀.toDia₁

example {α : Type} [Semigroup₀ α] (a b : α) : α := a  b

Sebelum melanjutkan pembangunan hierarki, kita perlu memakai sintaks yang berbeda untuk menambahkan field toDia₁ ini. Sintaks tersebut memberi tahu Lean agar memperlakukan Dia₁ α seolah-olah field-fieldnya merupakan field milik Semigroup₁ sendiri. Sintaks ini juga secara otomatis menambahkan instans toDia₁. Perintah class mendukung hal tersebut melalui sintaks extends, seperti berikut:

class Semigroup₁ (α : Type) extends toDia₁ : Dia₁ α where
  /-- Operasi wajik bersifat asosiatif -/
  dia_assoc :  a b c : α, a  b  c = a  (b  c)

example {α : Type} [Semigroup₁ α] (a b : α) : α := a  b

Perhatikan bahwa sintaks ini juga tersedia pada perintah structure. Namun, dalam kasus tersebut sintaks ini hanya mengatasi kerepotan menulis field seperti toDia₁, karena tidak ada instans yang perlu didefinisikan.

Nama field toDia₁ bersifat opsional dalam sintaks extends. Secara baku, nama field diambil dari nama kelas yang diperluas lalu diberi prefiks "to".

class Semigroup₂ (α : Type) extends Dia₁ α where
  /-- Operasi wajik bersifat asosiatif -/
  dia_assoc :  a b c : α, a  b  c = a  (b  c)

Sekarang mari kita coba menggabungkan operasi wajik dengan sebuah elemen satu khusus, beserta aksioma yang menyatakan bahwa elemen tersebut netral di kedua sisi.

class DiaOneClass₁ (α : Type) extends One₁ α, Dia₁ α where
  /-- Satu adalah elemen netral kiri untuk wajik. -/
  one_dia :  a : α, 𝟙  a = a
  /-- Satu adalah elemen netral kanan untuk wajik. -/
  dia_one :  a : α, a  𝟙 = a

Dalam contoh berikut, kita memberi tahu Lean bahwa α memiliki struktur DiaOneClass₁, lalu menyatakan sebuah sifat yang menggunakan instans Dia₁ dan instans One₁ sekaligus. Untuk melihat cara Lean menemukan instans-instans tersebut, kita mengaktifkan opsi pelacakan yang hasilnya dapat dilihat di Infoview. Secara baku, hasil ini cukup ringkas, tetapi dapat diperluas dengan mengeklik baris yang berakhir dengan panah hitam. Hasil tersebut mencakup percobaan gagal ketika Lean berusaha menemukan instans sebelum memiliki informasi tipe yang cukup. Percobaan yang berhasil memang melibatkan instans yang dihasilkan oleh sintaks extends.

set_option trace.Meta.synthInstance true in
example {α : Type} [DiaOneClass₁ α] (a b : α) : Prop := a  b = 𝟙

Perhatikan bahwa kita tidak perlu menyertakan field tambahan ketika menggabungkan kelas-kelas yang sudah ada. Karena itu, monoid dapat didefinisikan sebagai berikut:

class Monoid₁ (α : Type) extends Semigroup₁ α, DiaOneClass₁ α

Walaupun definisi di atas tampak langsung, ada satu seluk-beluk penting yang tersembunyi. Semigroup₁ α dan DiaOneClass₁ α sama-sama memperluas Dia₁ α. Karena itu, kita mungkin khawatir bahwa sebuah instans Monoid₁ α akan memberi dua operasi wajik yang tidak berkaitan pada α: satu berasal dari field Monoid₁.toSemigroup₁ dan satu lagi dari field Monoid₁.toDiaOneClass₁.

Memang, jika kita mencoba membangun kelas monoid secara manual dengan:

class Monoid₂ (α : Type) where
  toSemigroup₁ : Semigroup₁ α
  toDiaOneClass₁ : DiaOneClass₁ α

kita akan memperoleh dua operasi wajik yang sama sekali tidak berkaitan, Monoid₂.toSemigroup₁.toDia₁.dia dan Monoid₂.toDiaOneClass₁.toDia₁.dia.

Versi yang dihasilkan dengan sintaks extends tidak memiliki cacat ini.

example {α : Type} [Monoid₁ α] :
  (Monoid₁.toSemigroup₁.toDia₁.dia : α  α  α) = Monoid₁.toDiaOneClass₁.toDia₁.dia := rfl

Jadi, perintah class telah melakukan semacam keajaiban bagi kita (dan perintah structure juga akan melakukan hal yang sama). Cara mudah untuk melihat field-field kelas kita adalah dengan memeriksa konstruktornya. Bandingkan:

/- Monoid₂.mk {α : Type} (toSemigroup₁ : Semigroup₁ α) (toDiaOneClass₁ : DiaOneClass₁ α) : Monoid₂ α -/
#check Monoid₂.mk

/- Monoid₁.mk {α : Type} [toSemigroup₁ : Semigroup₁ α] [toOne₁ : One₁ α] (one_dia : ∀ (a : α), 𝟙 ⋄ a = a) (dia_one : ∀ (a : α), a ⋄ 𝟙 = a) : Monoid₁ α -/
#check Monoid₁.mk

Jadi, terlihat bahwa Monoid₁ menerima argumen Semigroup₁ α seperti yang diharapkan. Namun, ia tidak menerima argumen DiaOneClass₁ α yang akan tumpang-tindih; sebaliknya, argumen itu diurai dan hanya bagian yang tidak tumpang-tindih yang disertakan. Lean juga secara otomatis menghasilkan instans Monoid₁.toDiaOneClass₁. Instans ini bukan sebuah field, tetapi memiliki tipe yang diharapkan dan, dari sudut pandang pengguna akhir, memulihkan simetri antara dua kelas yang diperluas, Semigroup₁ dan DiaOneClass₁.

#check Monoid₁.toSemigroup₁
#check Monoid₁.toDiaOneClass₁

Sekarang kita hampir dapat mendefinisikan grup. Kita dapat menambahkan field pada struktur monoid yang menyatakan keberadaan invers untuk setiap elemen. Akan tetapi, kita kemudian harus bekerja lebih jauh untuk mengakses invers tersebut. Dalam praktik, invers lebih mudah ditambahkan sebagai data. Agar dapat digunakan kembali secara optimal, kita mendefinisikan kelas pembawa data baru lalu memberinya notasi.

class Inv₁ (α : Type) where
  /-- Fungsi inversi -/
  inv : α  α

@[inherit_doc]
postfix:max "⁻¹" => Inv₁.inv

class Group₁ (G : Type) extends Monoid₁ G, Inv₁ G where
  inv_dia :  a : G, a⁻¹  a = 𝟙

Definisi di atas mungkin tampak terlalu lemah: kita hanya meminta agar a⁻¹ menjadi invers kiri dari a. Namun, sifat pada sisi lainnya berlaku secara otomatis. Untuk membuktikannya, kita memerlukan sebuah lemma pendahuluan.

lemma left_inv_eq_right_inv₁ {M : Type} [Monoid₁ M] {a b c : M} (hba : b  a = 𝟙) (hac : a  c = 𝟙) : b = c := by
  rw [ DiaOneClass₁.one_dia c,  hba, Semigroup₁.dia_assoc, hac, DiaOneClass₁.dia_one b]

Dalam lemma ini, penggunaan nama lengkap cukup merepotkan, terlebih karena kita harus mengetahui bagian hierarki mana yang menyediakan setiap fakta. Salah satu cara mengatasinya adalah memakai perintah export untuk menyalin fakta-fakta tersebut sebagai lemma ke namespace akar.

export DiaOneClass₁ (one_dia dia_one)
export Semigroup₁ (dia_assoc)
export Group₁ (inv_dia)

Selanjutnya, bukti di atas dapat ditulis ulang sebagai berikut:

example {M : Type} [Monoid₁ M] {a b c : M} (hba : b  a = 𝟙) (hac : a  c = 𝟙) : b = c := by
  rw [ one_dia c,  hba, dia_assoc, hac, dia_one b]

Sekarang giliran Anda membuktikan beberapa hal tentang struktur aljabar kita.

lemma inv_eq_of_dia [Group₁ G] {a b : G} (h : a  b = 𝟙) : a⁻¹ = b :=
  sorry

lemma dia_inv [Group₁ G] (a : G) : a  a⁻¹ = 𝟙 :=
  sorry

Pada tahap ini, kita ingin beralih ke definisi gelanggang, tetapi ada masalah serius. Struktur gelanggang pada suatu tipe memuat struktur grup aditif, struktur monoid multiplikatif, dan beberapa sifat mengenai interaksi keduanya. Namun, sejauh ini kita telah menetapkan notasi secara langsung untuk semua operasi. Pada tingkat yang lebih mendasar, sistem kelas tipe mengasumsikan bahwa setiap tipe hanya memiliki satu instans dari masing-masing kelas tipe.

Ada berbagai cara untuk menyelesaikan masalah ini. Agak mengejutkan, Mathlib menggunakan gagasan sederhana untuk menggandakan segala sesuatu bagi teori aditif dan multiplikatif dengan bantuan atribut pembangkit kode. Struktur dan kelas didefinisikan dalam notasi aditif maupun multiplikatif, lalu dihubungkan dengan atribut to_additive. Dalam kasus pewarisan berganda seperti pada semigrup, instans "pemulih simetri" yang dibuat secara otomatis juga perlu ditandai. Bagian ini agak teknis; Anda tidak perlu memahami perinciannya. Pokok pentingnya adalah bahwa lemma cukup dinyatakan dalam notasi multiplikatif dan diberi atribut to_additive. Sebagai contoh, lemma multiplikatif left_inv_eq_right_inv' akan menghasilkan versi aditif left_neg_eq_right_neg' secara otomatis. Untuk memeriksa nama versi aditif tersebut, kita menggunakan perintah whatsnew in pada left_inv_eq_right_inv'.

class AddSemigroup₃ (α : Type) extends Add α where
  /-- Penjumlahan bersifat asosiatif -/
  add_assoc₃ :  a b c : α, a + b + c = a + (b + c)

@[to_additive AddSemigroup₃]
class Semigroup₃ (α : Type) extends Mul α where
  /-- Perkalian bersifat asosiatif -/
  mul_assoc₃ :  a b c : α, a * b * c = a * (b * c)

class AddMonoid₃ (α : Type) extends AddSemigroup₃ α, AddZeroClass α

@[to_additive AddMonoid₃]
class Monoid₃ (α : Type) extends Semigroup₃ α, MulOneClass α

export Semigroup₃ (mul_assoc₃)
export AddSemigroup₃ (add_assoc₃)

whatsnew in
@[to_additive]
lemma left_inv_eq_right_inv' {M : Type} [Monoid₃ M] {a b c : M} (hba : b * a = 1) (hac : a * c = 1) : b = c := by
  rw [ one_mul c,  hba, mul_assoc₃, hac, mul_one b]

#check left_neg_eq_right_neg'

Dilengkapi teknologi ini, kita juga dapat dengan mudah mendefinisikan semigrup komutatif, monoid komutatif, dan grup komutatif, lalu mendefinisikan gelanggang.

class AddCommSemigroup₃ (α : Type) extends AddSemigroup₃ α where
  add_comm :  a b : α, a + b = b + a

@[to_additive AddCommSemigroup₃]
class CommSemigroup₃ (α : Type) extends Semigroup₃ α where
  mul_comm :  a b : α, a * b = b * a

class AddCommMonoid₃ (α : Type) extends AddMonoid₃ α, AddCommSemigroup₃ α

@[to_additive AddCommMonoid₃]
class CommMonoid₃ (α : Type) extends Monoid₃ α, CommSemigroup₃ α

class AddGroup₃ (G : Type) extends AddMonoid₃ G, Neg G where
  neg_add :  a : G, -a + a = 0

@[to_additive AddGroup₃]
class Group₃ (G : Type) extends Monoid₃ G, Inv G where
  inv_mul :  a : G, a⁻¹ * a = 1

Kita perlu ingat untuk menandai lemma dengan simp apabila sesuai.

attribute [simp] Group₃.inv_mul AddGroup₃.neg_add

Selanjutnya, kita perlu sedikit mengulang karena beralih ke notasi baku. Namun, setidaknya to_additive menangani penerjemahan dari notasi multiplikatif ke notasi aditif.

@[to_additive]
lemma inv_eq_of_mul [Group₃ G] {a b : G} (h : a * b = 1) : a⁻¹ = b :=
  sorry

Perhatikan bahwa kita dapat meminta to_additive menandai sebuah lemma dengan simp dan meneruskan atribut tersebut ke versi aditifnya sebagai berikut.

@[to_additive (attr := simp)]
lemma Group₃.mul_inv {G : Type} [Group₃ G] (a : G) : a * a⁻¹ = 1 := by
  sorry

@[to_additive]
lemma mul_left_cancel₃ {G : Type} [Group₃ G] {a b c : G} (h : a * b = a * c) : b = c := by
  sorry

@[to_additive]
lemma mul_right_cancel₃ {G : Type} [Group₃ G] {a b c : G} (h : b*a = c*a) : b = c := by
  sorry

class AddCommGroup₃ (G : Type) extends AddGroup₃ G, AddCommMonoid₃ G

@[to_additive AddCommGroup₃]
class CommGroup₃ (G : Type) extends Group₃ G, CommMonoid₃ G

Sekarang kita siap mendefinisikan gelanggang. Untuk tujuan demonstrasi, kita tidak akan mengasumsikan bahwa penjumlahan bersifat komutatif, lalu langsung menyediakan instans AddCommGroup₃. Mathlib tidak memakai cara ini. Pertama, dalam praktiknya cara tersebut tidak mempermudah pembuatan instans gelanggang. Kedua, hierarki aljabar Mathlib melewati semigelanggang, yang menyerupai gelanggang tetapi tidak memiliki lawan, sehingga bukti di bawah tidak berlaku untuknya.

Selain menjadi latihan yang bagus jika Anda belum pernah melihatnya, pendekatan ini memberi kita contoh pembangunan instans dengan sintaks yang memungkinkan struktur induk diberikan sebagai parameter instans, lalu field tambahan dipasok secara terpisah. Di sini, argumen Ring₃ R menyediakan semua yang diperlukan AddCommGroup₃ R kecuali add_comm.

class Ring₃ (R : Type) extends AddGroup₃ R, Monoid₃ R, MulZeroClass R where
  /-- Perkalian distributif kiri terhadap penjumlahan -/
  left_distrib :  a b c : R, a * (b + c) = a * b + a * c
  /-- Perkalian distributif kanan terhadap penjumlahan -/
  right_distrib :  a b c : R, (a + b) * c = a * c + b * c

instance {R : Type} [Ring₃ R] : AddCommGroup₃ R :=
{ add_comm := by
    sorry }

Tentu saja kita juga dapat membangun instans konkret, misalnya struktur gelanggang pada bilangan bulat. Instans di bawah memanfaatkan semua pekerjaan yang telah diselesaikan di Mathlib.

instance : Ring₃  where
  add := (· + ·)
  add_assoc₃ := add_assoc
  zero := 0
  zero_add := by simp
  add_zero := by simp
  neg := (- ·)
  neg_add := by simp
  mul := (· * ·)
  mul_assoc₃ := mul_assoc
  one := 1
  one_mul := by simp
  mul_one := by simp
  zero_mul := by simp
  mul_zero := by simp
  left_distrib := Int.mul_add
  right_distrib := Int.add_mul

Sebagai latihan, sekarang Anda dapat menyusun hierarki sederhana bagi relasi urutan. Hierarki ini mencakup kelas monoid komutatif terurut, yang memiliki struktur urutan parsial sekaligus struktur monoid komutatif sedemikian sehingga a b : α, a b c : α, c * a c * b. Tentu saja, Anda perlu menambahkan field dan mungkin klausa extends pada kelas-kelas berikut.

class LE₁ (α : Type) where
  /-- Relasi kurang dari atau sama dengan. -/
  le : α  α  Prop

@[inherit_doc] infix:50 " ≤₁ " => LE₁.le

class Preorder₁ (α : Type)

class PartialOrder₁ (α : Type)

class OrderedCommMonoid₁ (α : Type)

instance : OrderedCommMonoid₁  where

Sekarang kita akan membahas struktur aljabar yang melibatkan beberapa tipe. Contoh utamanya adalah modul atas gelanggang. Jika Anda belum mengetahui apa itu modul, anggap saja modul berarti ruang vektor dan semua gelanggang kita adalah medan. Struktur tersebut merupakan grup aditif komutatif yang dilengkapi perkalian skalar oleh elemen suatu gelanggang.

Pertama, kita mendefinisikan kelas tipe pembawa data untuk perkalian skalar oleh suatu tipe α pada tipe β, lalu memberinya notasi asosiatif kanan.

class SMul₃ (α : Type) (β : Type) where
  /-- Perkalian skalar -/
  smul : α  β  β

infixr:73 " • " => SMul₃.smul

Selanjutnya kita dapat mendefinisikan modul (sekali lagi, bayangkan ruang vektor jika Anda belum mengenal modul).

class Module₁ (R : Type) [Ring₃ R] (M : Type) [AddCommGroup₃ M] extends SMul₃ R M where
  zero_smul :  m : M, (0 : R)  m = 0
  one_smul :  m : M, (1 : R)  m = m
  mul_smul :  (a b : R) (m : M), (a * b)  m = a  b  m
  add_smul :  (a b : R) (m : M), (a + b)  m = a  m + b  m
  smul_add :  (a : R) (m n : M), a  (m + n) = a  m + a  n

Ada sesuatu yang menarik di sini. Tidak terlalu mengejutkan bahwa struktur gelanggang pada R menjadi parameter dalam definisi ini, tetapi Anda mungkin menduga AddCommGroup₃ M akan menjadi bagian dari klausa extends, sama seperti SMul₃ R M. Jika kita mencobanya, akan terbentuk field Module₁.toAddCommGroup₃ yang ditandai sebagai instans. Instans tersebut akan memiliki tipe: (R : Type) [inst : Ring₃ R] {M : Type} [self : Module₁ R M] AddCommGroup₃ M. Dengan instans seperti itu di basis data kelas tipe, setiap kali Lean mencari instans AddCommGroup₃ M bagi suatu M, ia harus terlebih dahulu memburu tipe R yang sama sekali belum ditentukan beserta instans Ring₃ R, sebelum memulai pencarian utama atas instans Module₁ R M. Kedua pencarian sampingan ini direpresentasikan oleh metavariabel dalam pesan galat, yang masing-masing dilambangkan dengan ?R dan ?inst✝. Instans Module₁.toAddCommGroup₃ semacam itu akan menjadi jebakan besar bagi prosedur resolusi instans.

Lalu bagaimana dengan extends SMul₃ R M? Klausa ini membuat field Module₁.toSMul₃ : {R : Type}   [inst : Ring₃ R] {M : Type} [inst_1 : AddCommGroup₃ M] [self : Module₁ R M] SMul₃ R M. Hasil akhirnya, SMul₃ R M, menyebut R maupun M, sehingga field ini aman digunakan sebagai instans. Aturannya mudah diingat: setiap kelas yang muncul dalam klausa extends harus menyebut setiap tipe yang muncul dalam parameter.

Mari kita buat instans modul pertama: suatu gelanggang merupakan modul atas dirinya sendiri, dengan perkaliannya sebagai perkalian skalar.

instance selfModule (R : Type) [Ring₃ R] : Module₁ R R where
  smul := fun r s  r*s
  zero_smul := zero_mul
  one_smul := one_mul
  mul_smul := mul_assoc₃
  add_smul := Ring₃.right_distrib
  smul_add := Ring₃.left_distrib

Sebagai contoh kedua, setiap grup abelian merupakan modul atas (inilah salah satu alasan untuk memperumum teori ruang vektor dengan mengizinkan skalar yang tidak dapat dibalik). Pertama, kita dapat mendefinisikan perkalian skalar dengan bilangan asli pada setiap tipe yang dilengkapi nol dan penjumlahan: n a didefinisikan sebagai a + + a, dengan a muncul sebanyak n kali. Definisi ini kemudian diperluas menjadi perkalian skalar dengan bilangan bulat dengan memastikan bahwa (-1) a = -a.

def nsmul₁ {M : Type*} [Zero M] [Add M] :   M  M
  | 0, _ => 0
  | n + 1, a => a + nsmul₁ n a

def zsmul₁ {M : Type*} [Zero M] [Add M] [Neg M] :   M  M
  | Int.ofNat n, a => nsmul₁ n a
  | Int.negSucc n, a => -nsmul₁ n.succ a

Membuktikan bahwa konstruksi ini menghasilkan struktur modul sedikit melelahkan dan tidak menarik bagi pembahasan sekarang, sehingga kita akan membiarkan semua aksiomanya sebagai sorry. Anda tidak diminta mengganti sorry tersebut dengan bukti. Jika tetap ingin melakukannya, Anda mungkin perlu menyatakan dan membuktikan beberapa lemma perantara mengenai nsmul₁ dan zsmul₁.

instance abGrpModule (A : Type) [AddCommGroup₃ A] : Module₁  A where
  smul := zsmul₁
  zero_smul := sorry
  one_smul := sorry
  mul_smul := sorry
  add_smul := sorry
  smul_add := sorry

Masalah yang jauh lebih penting adalah bahwa sekarang kita memiliki dua struktur modul atas gelanggang bagi itu sendiri: abGrpModule karena merupakan grup abelian, dan selfModule karena merupakan gelanggang. Kedua struktur modul tersebut berkaitan dengan struktur grup abelian yang sama, tetapi belum jelas bahwa keduanya memiliki perkalian skalar yang sama. Sebenarnya memang sama, tetapi fakta ini tidak berlaku berdasarkan definisi dan memerlukan bukti. Ini merupakan kabar yang sangat buruk bagi prosedur resolusi instans kelas tipe dan akan menimbulkan kegagalan yang sangat menjengkelkan bagi pengguna hierarki ini. Ketika diminta secara langsung mencari sebuah instans, Lean akan memilih salah satunya. Kita dapat melihat pilihannya dengan:

#synth Module₁   -- abGrpModule ℤ

Namun, dalam konteks yang lebih tidak langsung, Lean dapat menyimpulkan instans yang lain lalu menjadi bingung. Keadaan ini dikenal sebagai wajik buruk. Istilah ini tidak berkaitan dengan operasi wajik yang kita gunakan di atas, melainkan dengan cara menggambar lintasan dari menuju Module₁ melalui AddCommGroup₃ atau Ring₃ .

Penting untuk dipahami bahwa tidak semua wajik bersifat buruk. Faktanya, wajik terdapat di mana-mana dalam Mathlib, juga dalam bab ini. Sejak awal, kita telah melihat bahwa lintasan dari Monoid₁ α ke Dia₁ α dapat melewati Semigroup₁ α atau DiaOneClass₁ α. Berkat pekerjaan yang dilakukan perintah class, kedua instans Dia₁ α yang dihasilkan sama secara definisional. Secara khusus, wajik dengan kelas bernilai Prop pada bagian bawahnya tidak mungkin buruk, sebab setiap dua bukti atas pernyataan yang sama bersifat sama secara definisional.

Namun, wajik yang kita buat dengan modul benar-benar buruk. Bagian penyebabnya adalah field smul, yang berupa data, bukan bukti, dan kita memiliki dua konstruksi yang tidak sama secara definisional. Cara tangguh untuk memperbaiki masalah ini adalah memastikan bahwa perpindahan dari struktur yang kaya ke struktur yang lebih miskin selalu dilakukan dengan melupakan data, bukan dengan mendefinisikan data. Pola terkenal ini disebut "pewarisan dengan pelupaan data" dan dibahas secara luas dalam https://inria.hal.science/hal-02463336v2.

Dalam kasus konkret kita, definisi AddMonoid₃ dapat diubah agar menyertakan field data nsmul dan beberapa field bernilai Prop yang memastikan bahwa operasi ini terbukti sama dengan operasi yang kita konstruksi di atas. Field-field tersebut diberi nilai baku menggunakan := setelah tipenya dalam definisi di bawah. Berkat nilai baku ini, kebanyakan instans dapat dikonstruksi tepat seperti pada definisi sebelumnya. Namun, dalam kasus khusus , kita dapat memberikan nilai tertentu.

class AddMonoid₄ (M : Type) extends AddSemigroup₃ M, AddZeroClass M where
  /-- Perkalian dengan bilangan asli. -/
  nsmul :   M  M := nsmul₁
  /-- Perkalian dengan `(0 : ℕ)` menghasilkan `0`. -/
  nsmul_zero :  x, nsmul 0 x = 0 := by intros; rfl
  /-- Perkalian dengan `(n + 1 : ℕ)` berperilaku seperti yang diharapkan. -/
  nsmul_succ :  (n : ) (x), nsmul (n + 1) x = x + nsmul n x := by intros; rfl

instance mySMul {M : Type} [AddMonoid₄ M] : SMul  M := AddMonoid₄.nsmul

Mari kita periksa bahwa kita tetap dapat mengonstruksi instans monoid produk tanpa menyediakan field-field yang berkaitan dengan nsmul.

instance (M N : Type) [AddMonoid₄ M] [AddMonoid₄ N] : AddMonoid₄ (M × N) where
  add := fun p q  (p.1 + q.1, p.2 + q.2)
  add_assoc₃ := fun a b c  by ext <;> apply add_assoc₃
  zero := (0, 0)
  zero_add := fun a  by ext <;> apply zero_add
  add_zero := fun a  by ext <;> apply add_zero

Sekarang mari kita tangani kasus khusus . Di sini, kita ingin membangun nsmul menggunakan koersi dari ke dan perkalian pada . Perhatikan khususnya bahwa field-field bukti memerlukan lebih banyak pekerjaan daripada nilai baku di atas.

instance : AddMonoid₄  where
  add := (· + ·)
  add_assoc₃ := Int.add_assoc
  zero := 0
  zero_add := Int.zero_add
  add_zero := Int.add_zero
  nsmul := fun n m  (n : ) * m
  nsmul_zero := Int.zero_mul
  nsmul_succ := fun n m  show (n + 1 : ) * m = m + n * m
    by rw [Int.add_mul, Int.add_comm, Int.one_mul]

Mari kita periksa bahwa masalah kita telah terselesaikan. Karena Lean sudah memiliki definisi perkalian skalar antara bilangan asli dan bilangan bulat, dan kita ingin memastikan instans kita yang digunakan, kita tidak akan memakai notasi . Sebagai gantinya, kita memanggil SMul.smul dan secara eksplisit memberikan instans yang didefinisikan di atas.

example (n : ) (m : ) : SMul.smul (self := mySMul) n m = n * m := rfl

Pembahasan ini kemudian berlanjut dengan memasukkan field zsmul ke dalam definisi grup serta menerapkan kiat-kiat serupa. Sekarang Anda siap membaca definisi monoid, grup, gelanggang, dan modul dalam Mathlib. Definisi tersebut lebih rumit daripada yang telah kita lihat di sini karena menjadi bagian dari hierarki yang sangat besar, tetapi semua prinsip dasarnya telah dijelaskan di atas.

Sebagai latihan, Anda dapat kembali ke hierarki relasi urutan yang dibangun di atas dan mencoba memasukkan kelas tipe LT₁ yang membawa notasi kurang dari <₁. Pastikan bahwa setiap praurutan dilengkapi <₁ dengan nilai baku yang dibangun dari ≤₁ serta sebuah field bernilai Prop yang menyatakan hubungan alami antara kedua operator perbandingan tersebut.

8.2. Morfisme

Sejauh ini dalam bab ini, kita telah membahas cara membuat hierarki struktur matematika. Namun, definisi struktur belum benar-benar lengkap sebelum kita memiliki morfisme. Ada dua pendekatan utama. Pendekatan yang paling jelas adalah mendefinisikan predikat pada fungsi.

def isMonoidHom₁ [Monoid G] [Monoid H] (f : G  H) : Prop :=
  f 1 = 1   g g', f (g * g') = f g * f g'

Dalam definisi ini, penggunaan konjungsi sedikit merepotkan. Secara khusus, pengguna harus mengingat urutan yang kita pilih ketika ingin mengakses kedua syarat tersebut. Karena itu, kita dapat menggunakan struktur sebagai gantinya.

structure isMonoidHom₂ [Monoid G] [Monoid H] (f : G  H) : Prop where
  map_one : f 1 = 1
  map_mul :  g g', f (g * g') = f g * f g'

Setelah sampai di sini, kita bahkan mungkin tergoda menjadikannya kelas dan memakai prosedur resolusi instans kelas tipe untuk menyimpulkan isMonoidHom₂ secara otomatis bagi fungsi rumit dari instans-instans fungsi yang lebih sederhana. Sebagai contoh, komposisi morfisme monoid merupakan morfisme monoid, sehingga tampaknya berguna sebagai instans. Namun, instans semacam itu akan sangat sulit bagi prosedur resolusi karena harus menemukan g f di mana-mana. Kegagalannya pada g (f x) akan sangat menjengkelkan. Secara lebih umum, kita harus selalu mengingat bahwa mengenali fungsi mana yang diterapkan dalam suatu ekspresi merupakan masalah yang sangat sulit, yang disebut "masalah unifikasi orde tinggi". Karena itu, Mathlib tidak menggunakan pendekatan berbasis kelas ini.

Pertanyaan yang lebih mendasar adalah apakah kita menggunakan predikat seperti di atas (melalui def atau structure), atau memakai struktur yang mengemas fungsi beserta predikatnya. Hal ini sebagian merupakan persoalan psikologis. Sangat jarang kita mempertimbangkan fungsi di antara monoid yang bukan morfisme. "Morfisme monoid" benar-benar terasa sebagai nomina, bukan adjektiva yang dapat dilekatkan pada fungsi biasa. Di sisi lain, orang dapat berpendapat bahwa fungsi kontinu di antara ruang topologi memang merupakan fungsi yang kebetulan kontinu. Inilah salah satu alasan Mathlib memiliki predikat Continuous. Sebagai contoh, Anda dapat menulis:

example : Continuous (id :   ) := continuous_id

Kita tetap memiliki bundel fungsi kontinu, yang antara lain memudahkan pemberian topologi pada suatu ruang fungsi kontinu, tetapi bundel tersebut bukan alat utama untuk bekerja dengan kekontinuan.

Sebaliknya, morfisme di antara monoid (atau struktur aljabar lainnya) dibundel sebagai berikut:

@[ext]
structure MonoidHom₁ (G H : Type) [Monoid G] [Monoid H]  where
  toFun : G  H
  map_one : toFun 1 = 1
  map_mul :  g g', toFun (g * g') = toFun g * toFun g'

Tentu saja kita tidak ingin mengetik toFun di mana-mana. Karena itu, kita mendaftarkan koersi dengan menggunakan kelas tipe CoeFun. Argumen pertamanya adalah tipe yang ingin kita koersikan menjadi fungsi. Argumen kedua menjelaskan tipe fungsi sasaran. Dalam kasus kita, tipenya selalu G H untuk setiap f : MonoidHom₁ G H. Kita juga menandai MonoidHom₁.toFun dengan atribut coe agar tampil nyaris tak terlihat dalam keadaan taktik, hanya dengan prefiks .

instance [Monoid G] [Monoid H] : CoeFun (MonoidHom₁ G H) (fun _  G  H) where
  coe := MonoidHom₁.toFun

attribute [coe] MonoidHom₁.toFun

Mari kita periksa bahwa morfisme monoid terbundel memang dapat diterapkan pada sebuah elemen.

example [Monoid G] [Monoid H] (f : MonoidHom₁ G H) : f 1 = 1 :=  f.map_one

Kita dapat melakukan hal yang sama dengan jenis morfisme lain hingga mencapai morfisme gelanggang.

@[ext]
structure AddMonoidHom₁ (G H : Type) [AddMonoid G] [AddMonoid H]  where
  toFun : G  H
  map_zero : toFun 0 = 0
  map_add :  g g', toFun (g + g') = toFun g + toFun g'

instance [AddMonoid G] [AddMonoid H] : CoeFun (AddMonoidHom₁ G H) (fun _  G  H) where
  coe := AddMonoidHom₁.toFun

attribute [coe] AddMonoidHom₁.toFun

@[ext]
structure RingHom₁ (R S : Type) [Ring R] [Ring S] extends MonoidHom₁ R S, AddMonoidHom₁ R S

Pendekatan ini memiliki beberapa masalah. Masalah kecilnya adalah kita tidak tahu persis di mana harus menempatkan atribut coe, sebab RingHom₁.toFun tidak ada. Fungsi yang relevan ialah MonoidHom₁.toFun RingHom₁.toMonoidHom₁, yang bukan deklarasi yang dapat ditandai dengan atribut (meskipun kita tetap dapat mendefinisikan instans CoeFun  (RingHom₁ R S) (fun _ R S)).

Masalah yang jauh lebih penting adalah bahwa lemma tentang morfisme monoid tidak langsung berlaku pada morfisme gelanggang. Pilihannya adalah mengutak-atik RingHom₁.toMonoidHom₁ setiap kali ingin menerapkan lemma morfisme monoid, atau menyatakan ulang setiap lemma semacam itu bagi morfisme gelanggang. Kedua pilihan ini tidak menarik, sehingga Mathlib menggunakan kiat hierarki baru. Gagasannya adalah mendefinisikan kelas tipe bagi objek yang setidaknya merupakan morfisme monoid, membuat instans kelas tersebut untuk morfisme monoid maupun morfisme gelanggang, lalu menggunakannya untuk menyatakan setiap lemma. Dalam definisi di bawah, F dapat berupa MonoidHom₁ M N, atau RingHom₁ M N apabila M dan N memiliki struktur gelanggang.

class MonoidHomClass₁ (F : Type) (M N : Type) [Monoid M] [Monoid N] where
  toFun : F  M  N
  map_one :  f : F, toFun f 1 = 1
  map_mul :  f g g', toFun f (g * g') = toFun f g * toFun f g'

Namun, implementasi di atas memiliki masalah. Kita belum mendaftarkan instans koersi ke fungsi. Mari kita coba melakukannya sekarang.

def badInst [Monoid M] [Monoid N] [MonoidHomClass₁ F M N] : CoeFun F (fun _  M  N) where
  coe := MonoidHomClass₁.toFun

Menjadikannya instans merupakan gagasan buruk. Ketika menghadapi bentuk seperti f x dan tipe f bukan tipe fungsi, Lean akan mencoba menemukan instans CoeFun untuk mengoersikan f menjadi fungsi. Fungsi di atas memiliki tipe: {M N F : Type} [Monoid M] [Monoid N] [MonoidHomClass₁ F M N] CoeFun F (fun x M N). Jadi, ketika mencoba menerapkannya, Lean sejak awal tidak mengetahui urutan untuk menyimpulkan tipe-tipe tak diketahui M, N, dan F. Ini merupakan jenis instans buruk yang sedikit berbeda dari yang telah kita lihat, tetapi sumber masalahnya sama: tanpa mengetahui M, Lean harus mencari instans monoid pada tipe yang belum diketahui dan dengan sia-sia mencoba setiap instans monoid di basis data. Jika ingin melihat dampak instans semacam itu, Anda dapat menulis set_option synthInstance.checkSynthOrder false in di atas deklarasi tersebut, mengganti def badInst dengan instance, lalu mencari kegagalan acak dalam berkas ini.

Di sini solusinya mudah: kita perlu memberi tahu Lean agar terlebih dahulu mencari tipe F, lalu menyimpulkan M dan N. Hal ini dilakukan dengan fungsi outParam. Fungsi tersebut didefinisikan sebagai fungsi identitas, tetapi tetap dikenali oleh mekanisme kelas tipe dan memicu perilaku yang diinginkan. Karena itu, kita dapat mencoba lagi mendefinisikan kelas kita dengan memperhatikan fungsi outParam:

class MonoidHomClass₂ (F : Type) (M N : outParam Type) [Monoid M] [Monoid N] where
  toFun : F  M  N
  map_one :  f : F, toFun f 1 = 1
  map_mul :  f g g', toFun f (g * g') = toFun f g * toFun f g'

instance [Monoid M] [Monoid N] [MonoidHomClass₂ F M N] : CoeFun F (fun _  M  N) where
  coe := MonoidHomClass₂.toFun

attribute [coe] MonoidHomClass₂.toFun

Sekarang kita dapat melanjutkan rencana untuk membuat instans kelas ini.

instance (M N : Type) [Monoid M] [Monoid N] : MonoidHomClass₂ (MonoidHom₁ M N) M N where
  toFun := MonoidHom₁.toFun
  map_one := fun f  f.map_one
  map_mul := fun f  f.map_mul

instance (R S : Type) [Ring R] [Ring S] : MonoidHomClass₂ (RingHom₁ R S) R S where
  toFun := fun f  f.toMonoidHom₁.toFun
  map_one := fun f  f.toMonoidHom₁.map_one
  map_mul := fun f  f.toMonoidHom₁.map_mul

Seperti dijanjikan, setiap lemma yang kita buktikan mengenai f : F dengan asumsi instans MonoidHomClass₂ F M N akan berlaku bagi morfisme monoid maupun morfisme gelanggang. Mari kita lihat sebuah contoh lemma dan periksa bahwa lemma tersebut berlaku dalam kedua keadaan.

lemma map_inv_of_inv [Monoid M] [Monoid N] [MonoidHomClass₂ F M N] (f : F) {m m' : M} (h : m*m' = 1) :
    f m * f m' = 1 := by
  rw [ MonoidHomClass₂.map_mul, h, MonoidHomClass₂.map_one]

example [Monoid M] [Monoid N] (f : MonoidHom₁ M N) {m m' : M} (h : m*m' = 1) : f m * f m' = 1 :=
map_inv_of_inv f h

example [Ring R] [Ring S] (f : RingHom₁ R S) {r r' : R} (h : r*r' = 1) : f r * f r' = 1 :=
map_inv_of_inv f h

Sekilas, mungkin tampak bahwa kita kembali pada gagasan buruk lama untuk menjadikan MonoidHom₁ sebuah kelas. Namun, sebenarnya tidak demikian. Semuanya digeser naik satu tingkat abstraksi. Prosedur resolusi kelas tipe tidak akan mencari fungsi; ia akan mencari MonoidHom₁ atau RingHom₁.

Satu masalah yang tersisa dari pendekatan kita adalah adanya kode berulang di sekitar field toFun, instans CoeFun yang bersesuaian, dan atribut coe. Sebaiknya kita juga mencatat bahwa pola ini hanya digunakan bagi fungsi dengan sifat tambahan, yang berarti koersi ke fungsi harus injektif. Karena itu, Mathlib menambahkan satu lapisan abstraksi lagi melalui kelas dasar DFunLike ("DFun" merupakan singkatan dari istilah bahasa Inggris yang berarti fungsi dependen). Mari kita definisikan ulang MonoidHomClass di atas lapisan dasar ini.

class MonoidHomClass₃ (F : Type) (M N : outParam Type) [Monoid M] [Monoid N] extends
    DFunLike F M (fun _  N) where
  map_one :  f : F, f 1 = 1
  map_mul :  (f : F) g g', f (g * g') = f g * f g'

instance (M N : Type) [Monoid M] [Monoid N] : MonoidHomClass₃ (MonoidHom₁ M N) M N where
  coe := MonoidHom₁.toFun
  coe_injective' _ _ := MonoidHom₁.ext
  map_one := MonoidHom₁.map_one
  map_mul := MonoidHom₁.map_mul

Tentu saja hierarki morfisme tidak berhenti di sini. Kita dapat melanjutkan dengan mendefinisikan kelas RingHomClass₃ yang memperluas MonoidHomClass₃, lalu membuat instansnya pada RingHom dan kemudian AlgebraHom (aljabar adalah gelanggang dengan struktur tambahan). Namun, kita telah membahas gagasan utama formalisasi morfisme yang digunakan dalam Mathlib. Anda kini seharusnya siap memahami cara morfisme didefinisikan di Mathlib.

Sebagai latihan, cobalah mendefinisikan kelas bagi fungsi pelestari urutan terbundel di antara tipe-tipe terurut, lalu bagi morfisme monoid pelestari urutan. Latihan ini hanya ditujukan untuk berlatih. Seperti fungsi kontinu, fungsi pelestari urutan di Mathlib terutama digunakan tanpa bundel dan didefinisikan melalui predikat Monotone. Tentu saja, Anda perlu melengkapi definisi kelas di bawah.

@[ext]
structure OrderPresHom (α β : Type) [LE α] [LE β] where
  toFun : α  β
  le_of_le :  a a', a  a'  toFun a  toFun a'

@[ext]
structure OrderPresMonoidHom (M N : Type) [Monoid M] [LE M] [Monoid N] [LE N] extends
MonoidHom₁ M N, OrderPresHom M N

class OrderPresHomClass (F : Type) (α β : outParam Type) [LE α] [LE β]

instance (α β : Type) [LE α] [LE β] : OrderPresHomClass (OrderPresHom α β) α β where

instance (α β : Type) [LE α] [Monoid α] [LE β] [Monoid β] :
    OrderPresHomClass (OrderPresMonoidHom α β) α β where

instance (α β : Type) [LE α] [Monoid α] [LE β] [Monoid β] :
    MonoidHomClass₃ (OrderPresMonoidHom α β) α β
  := sorry

8.3. Subobjek

Setelah mendefinisikan suatu struktur aljabar beserta morfismenya, langkah berikutnya adalah mempertimbangkan himpunan yang mewarisi struktur aljabar tersebut, misalnya subgrup atau subgelanggang. Hal ini banyak bertumpang tindih dengan topik sebelumnya. Memang, himpunan pada X diimplementasikan sebagai fungsi dari X ke Prop, sehingga subobjek merupakan fungsi yang memenuhi predikat tertentu. Karena itu, kita dapat menggunakan kembali banyak gagasan yang menghasilkan kelas DFunLike beserta turunannya.

Kita tidak akan menggunakan kembali DFunLike itu sendiri karena hal tersebut akan melanggar penghalang abstraksi dari Set X ke X Prop. Sebagai gantinya, terdapat kelas SetLike. Alih-alih membungkus injeksi ke suatu tipe fungsi, kelas ini membungkus injeksi ke tipe Set dan mendefinisikan koersi serta instans Membership yang bersesuaian.

@[ext]
structure Submonoid₁ (M : Type) [Monoid M] where
  /-- Himpunan pembawa sebuah submonoid. -/
  carrier : Set M
  /-- Hasil kali dua elemen submonoid termasuk dalam submonoid tersebut. -/
  mul_mem {a b} : a  carrier  b  carrier  a * b  carrier
  /-- Elemen satu termasuk dalam submonoid. -/
  one_mem : 1  carrier

/-- Submonoid di `M` dapat dipandang sebagai himpunan di `M`. -/
instance [Monoid M] : SetLike (Submonoid₁ M) M where
  coe := Submonoid₁.carrier
  coe_injective' _ _ := Submonoid₁.ext

Dilengkapi instans SetLike di atas, kita sudah dapat menyatakan secara alami bahwa submonoid N memuat 1 tanpa menggunakan N.carrier. Kita juga dapat secara diam-diam memperlakukan N sebagai himpunan di M dan mengambil citra langsungnya di bawah suatu pemetaan.

example [Monoid M] (N : Submonoid₁ M) : 1  N := N.one_mem

example [Monoid M] (N : Submonoid₁ M) (α : Type) (f : M  α) := f '' N

Kita juga memiliki koersi ke Type yang menggunakan Subtype. Dengan demikian, jika diberikan submonoid N, kita dapat menulis parameter (x : N) yang dapat dikoersikan menjadi elemen M yang termasuk dalam N.

example [Monoid M] (N : Submonoid₁ M) (x : N) : (x : M)  N := x.property

Dengan menggunakan koersi ke Type ini, kita juga dapat menangani tugas melengkapi submonoid dengan struktur monoid. Kita akan menggunakan koersi dari tipe yang terkait dengan N seperti di atas, serta lemma SetCoe.ext yang menyatakan bahwa koersi tersebut injektif. Keduanya disediakan oleh instans SetLike.

instance SubMonoid₁Monoid [Monoid M] (N : Submonoid₁ M) : Monoid N where
  mul := fun x y  x*y, N.mul_mem x.property y.property
  mul_assoc := fun x y z  SetCoe.ext (mul_assoc (x : M) y z)
  one := 1, N.one_mem
  one_mul := fun x  SetCoe.ext (one_mul (x : M))
  mul_one := fun x  SetCoe.ext (mul_one (x : M))

Perhatikan bahwa pada instans di atas, alih-alih menggunakan koersi ke M dan memanggil field property, kita dapat menggunakan pengikat destrukturisasi sebagai berikut.

example [Monoid M] (N : Submonoid₁ M) : Monoid N where
  mul := fun x, hx y, hy  x*y, N.mul_mem hx hy
  mul_assoc := fun x, _ y, _ z, _  SetCoe.ext (mul_assoc x y z)
  one := 1, N.one_mem
  one_mul := fun x, _  SetCoe.ext (one_mul x)
  mul_one := fun x, _  SetCoe.ext (mul_one x)

Agar dapat menerapkan lemma tentang submonoid pada subgrup atau subgelanggang, kita memerlukan kelas, sama seperti pada morfisme. Perhatikan bahwa kelas ini menerima instans SetLike sebagai parameter, sehingga tidak memerlukan field pembawa dan dapat menggunakan notasi keanggotaan dalam field-fieldnya.

class SubmonoidClass₁ (S : Type) (M : Type) [Monoid M] [SetLike S M] : Prop where
  mul_mem :  (s : S) {a b : M}, a  s  b  s  a * b  s
  one_mem :  s : S, 1  s

instance [Monoid M] : SubmonoidClass₁ (Submonoid₁ M) M where
  mul_mem := Submonoid₁.mul_mem
  one_mem := Submonoid₁.one_mem

Sebagai latihan, Anda perlu mendefinisikan struktur Subgroup₁, melengkapinya dengan instans SetLike dan instans SubmonoidClass₁, menempatkan instans Group pada subtipe yang terkait dengan Subgroup₁, serta mendefinisikan kelas SubgroupClass₁.

Hal sangat penting lainnya yang perlu diketahui adalah bahwa subobjek dari suatu objek aljabar di Mathlib selalu membentuk kekisi lengkap, dan struktur ini sering digunakan. Misalnya, Anda mungkin mencari lemma yang menyatakan bahwa irisan submonoid merupakan submonoid. Namun, hal tersebut bukan sebuah lemma, melainkan konstruksi infimum. Mari kita kerjakan kasus dua submonoid.

instance [Monoid M] : Min (Submonoid₁ M) :=
  fun S₁ S₂ 
    { carrier := S₁  S₂
      one_mem := S₁.one_mem, S₂.one_mem
      mul_mem := fun hx, hx' hy, hy'  S₁.mul_mem hx hy, S₂.mul_mem hx' hy' }⟩

Hal ini memungkinkan kita memperoleh irisan dua submonoid sebagai submonoid.

example [Monoid M] (N P : Submonoid₁ M) : Submonoid₁ M := N  P

Anda mungkin menyayangkan bahwa dalam contoh di atas kita harus menggunakan simbol infimum alih-alih simbol irisan . Namun, pertimbangkan supremumnya. Gabungan dua submonoid bukanlah submonoid. Meskipun demikian, submonoid tetap membentuk kekisi, bahkan kekisi lengkap. Sebenarnya, N P adalah submonoid yang dibangkitkan oleh gabungan N dan P, dan tentu akan sangat membingungkan jika dinyatakan dengan N P. Karena itu, penggunaan N P dapat dipandang jauh lebih konsisten. Notasi tersebut juga jauh lebih konsisten di antara berbagai jenis struktur aljabar.

Pada mulanya, mungkin tampak agak aneh melihat jumlah dua subruang vektor E dan F dinyatakan dengan E F alih-alih E + F. Namun, Anda akan terbiasa. Tak lama kemudian, Anda akan menganggap notasi E + F sebagai pengalih perhatian yang menekankan fakta sampingan bahwa elemen E F dapat ditulis sebagai jumlah elemen E dan elemen F, alih-alih menekankan fakta mendasar bahwa E F merupakan subruang vektor terkecil yang memuat E dan F.

Topik terakhir kita dalam bab ini adalah hasil bagi. Sekali lagi, kita ingin menjelaskan cara Mathlib membangun notasi yang nyaman sambil menghindari duplikasi kode. Perangkat utamanya di sini adalah kelas HasQuotient, yang memungkinkan notasi seperti M N. Perhatikan bahwa simbol hasil bagi merupakan karakter Unicode khusus, bukan simbol pembagian ASCII biasa.

Sebagai contoh, kita akan membangun hasil bagi monoid komutatif oleh sebuah submonoid dan menyerahkan buktinya kepada Anda. Dalam contoh terakhir, Anda dapat menggunakan Setoid.refl, tetapi Lean tidak akan secara otomatis mengambil struktur Setoid yang relevan. Masalah ini dapat diperbaiki dengan memberikan semua argumen melalui sintaks @, seperti dalam @Setoid.refl M N.Setoid.

def Submonoid.Setoid [CommMonoid M] (N : Submonoid M) : Setoid M  where
  r := fun x y   w  N,  z  N, x*w = y*z
  iseqv := {
    refl := fun x  1, N.one_mem, 1, N.one_mem, rfl
    symm := fun w, hw, z, hz, h  z, hz, w, hw, h.symm
    trans := by
      sorry
  }

instance [CommMonoid M] : HasQuotient M (Submonoid M) where
  Quotient := fun N  Quotient N.Setoid

def QuotientMonoid.mk [CommMonoid M] (N : Submonoid M) : M  M  N := Quotient.mk N.Setoid

instance [CommMonoid M] (N : Submonoid M) : Monoid (M  N) where
  mul := Quotient.map₂ (· * ·) (by
      sorry
        )
  mul_assoc := by
      sorry
  one := QuotientMonoid.mk N 1
  one_mul := by
      sorry
  mul_one := by
      sorry