Jika sesuatu itu sederhana, maka itu harus sepenuhnya dijelaskan dengan beberapa kata. Ini dapat dilakukan untuk kalkulus λ:
Kalkulus λ adalah tata bahasa sintaksis (pada dasarnya, struktur) dengan aturan reduksi (yang berarti prosedur pencarian / penggantian berulang kali diterapkan pada setiap kemunculan pola tertentu hingga tidak ada pola seperti itu).
Tatabahasa:
Term = (Term Term) | (λ Var . Term) | Var
Aturan pengurangan:
((λ var body) term) -> SUBS(body,var,term) where `SUBS` replaces all occurrences of `var` by `term` in `body`, avoiding name capture.
Contoh:
(λ a . a) -> (λ a a) ((λ a . (λ b . (b a))) (λ x . x)) -> (λ b . (b (λ x x))) ((λ a . (a a)) (λ x . x)) -> (λ x . x) ((λ a . (λ b . ((b a) a))) (λ x . x)) -> (λ b . ((b (λ x . x)) (λ x . x))) ((λ x . (x x)) (λ x . (x x))) -> never halts
Meskipun agak informal, orang bisa berpendapat ini cukup informatif bagi manusia normal untuk memahami kalkulus λ secara keseluruhan - dan dibutuhkan 22 garis penurunan harga. Saya mencoba memahami sistem tipe murni / dependen seperti yang digunakan oleh Idris / Agda dan proyek-proyek serupa, tetapi penjelasan singkat yang bisa saya temukan adalah Simply Easy - sebuah makalah yang hebat, tetapi itu sepertinya mengasumsikan banyak pengetahuan sebelumnya (Haskell, inductive definisi) yang tidak saya miliki. Saya pikir sesuatu yang lebih singkat, kurang kaya bisa menghilangkan beberapa hambatan itu. Demikian,
Apakah mungkin untuk memberikan penjelasan singkat dan lengkap tentang sistem tipe murni / dependen, dalam format yang sama dengan yang saya sajikan pada kalkulus λ di atas?
sumber
Jawaban:
Penolakan
Ini sangat informal, seperti yang Anda minta.
Tata bahasa
Dalam bahasa yang diketik secara dependen, kami memiliki pengikat di tingkat tipe serta di tingkat nilai:
Istilah yang diketik dengan baik adalah istilah dengan tipe terlampir, kami akan menulis
t ∈ σ
atauuntuk menunjukkan bahwa istilah tersebut
t
memiliki tipeσ
.Aturan mengetik
Demi kesederhanaan, kami mengharuskan
λ v. t ∈ ∀ (v : σ). τ
keduanyaλ
dan∀
mengikat variabel yang sama (v
dalam hal ini).Aturan:
Jadi,
*
adalah "tipe semua tipe" (1),∀
bentuk tipe dari tipe (2), abstraksi lambda memiliki tipe pi (3) dan jikav
diperkenalkan oleh∀ (v : σ). τ
, makav
memiliki tipeσ
(5)."dalam bentuk normal" berarti kami melakukan pengurangan sebanyak mungkin menggunakan aturan pengurangan:
Aturan pengurangan "The"
Atau dalam sintaks dua dimensi di mana
berarti
t ∈ σ
:Ini hanya mungkin untuk menerapkan abstraksi lambda ke suatu istilah ketika istilah tersebut memiliki tipe yang sama dengan variabel dalam semua kuantifier terkait. Kemudian kita mengurangi abstraksi lambda dan kuantifikasi forall dengan cara yang sama seperti pada kalkulus lambda murni sebelumnya. Setelah mengurangi bagian level nilai, kami mendapatkan (4) aturan mengetik.
Sebuah contoh
Inilah operator aplikasi fungsi:
(kita menyingkat
∀ (x : σ). τ
keσ -> τ
jikaτ
tidak menyebutkanx
)f
pengembalianB y
untuk semuay
jenis yang disediakanA
. Kami menerapkanf
untukx
, yang merupakan dari jenis yang tepatA
, dan penggantiy
untukx
di∀
setelah.
, sehinggaf x ∈ SUBS(B y, y, x)
~>f x ∈ B x
.Sekarang mari kita menyingkat operator aplikasi fungsi
app
dan menerapkannya sendiri:Saya menempatkan
?
persyaratan yang harus kami sediakan. Pertama-tama kami secara eksplisit memperkenalkan dan membuat instantiateA
danB
:Sekarang kita perlu menyatukan apa yang kita miliki
yang sama dengan
dan apa yang
app ? ?
diterimaIni menghasilkan
(lihat juga Apa itu predicativity? )
Ekspresi kami (setelah diganti nama) menjadi
Karena untuk apa pun
A
,B
danf
(di manaf ∈ ∀ (y : A). B y
)kita bisa instantiate
A
danB
mendapatkan (untuk apa punf
dengan tipe yang sesuai)dan tipe tanda tangan setara dengan
(∀ (x : A). B x) -> ∀ (x : A). B x
.Seluruh ekspresi itu
Yaitu
yang setelah semua pengurangan pada level nilai memberikan
app
kembali yang sama .Jadi sementara ini hanya memerlukan beberapa langkah dalam kalkulus lambda murni untuk mendapatkan
app
dariapp app
, dalam pengaturan diketik (dan terutama ketergantungan diketik) kami juga perlu peduli tentang unifikasi dan hal-hal menjadi lebih kompleks bahkan dengan beberapa kenyamanan yang tidak konsisten (* ∈ *
).Ketik memeriksa
t
ini*
kemudiant ∈ *
oleh (1)t
adalah∀ (x : σ) τ
,σ ∈? *
,τ ∈? *
(lihat catatan tentang∈?
bawah) kemudiant ∈ *
oleh (2)t
adalahf x
,f ∈ ∀ (v : σ) τ
untuk beberapaσ
danτ
,x ∈? σ
kemudiant ∈ SUBS(τ, v, x)
oleh (4)t
variabelv
,v
diperkenalkan∀ (v : σ). τ
saat itut ∈ σ
oleh (5)Ini semua adalah aturan inferensi, tetapi kami tidak dapat melakukan hal yang sama untuk lambdas (inferensi tipe tidak dapat ditentukan untuk tipe dependen). Jadi untuk lambda kita periksa (
t ∈? σ
) daripada menyimpulkan:t
iniλ v. b
dan diperiksa terhadap∀ (v : σ) τ
,b ∈? τ
makat ∈ ∀ (v : σ) τ
t
ada sesuatu yang lain dan diperiksa,σ
maka simpulkan jenist
penggunaan fungsi di atas dan periksa apakah ituσ
Pemeriksaan kesetaraan untuk tipe mengharuskan mereka dalam bentuk normal, jadi untuk memutuskan apakah
t
memiliki tipe,σ
pertama kita periksa yangσ
memiliki tipe*
. Jika demikian, makaσ
dapat dinormalisasi (modulo Girard's paradox) dan menjadi dinormalisasi (makaσ
menjadi terbentuk dengan baik oleh (0)).SUBS
juga menormalkan ekspresi untuk dipertahankan (0).Ini disebut pengecekan jenis dua arah. Dengan itu kita tidak perlu membubuhi keterangan setiap lambda dengan jenis: jika dalam
f x
jenisf
diketahui, makax
diperiksa terhadap jenis argumen yangf
diterima alih-alih disimpulkan dan dibandingkan untuk kesetaraan (yang juga kurang efisien). Tetapi jikaf
lambda, itu membutuhkan penjelasan jenis eksplisit (penjelasan dihilangkan dalam tata bahasa dan di mana-mana, Anda dapat menambahkanAnn Term Term
atauλ' (σ : Term) (v : Var)
ke konstruktor).Juga, lihat Simpler, Lebih Mudah! blogpost.
sumber
(λ v. b ∈ ∀ (v : σ). τ) (t ∈ σ)
~>SUBS(b, v, t) ∈ SUBS(τ, v, t)
.(∀ (v : σ). τ) t ~> ...
dan yang lain untuk yang bermakna(λ v. b) t ~> ...
. Saya akan menghapus yang pertama, dan mengubahnya menjadi komentar di bawah ini.Mari kita mulai. Saya tidak akan peduli dengan paradoks Girard, karena mengalihkan perhatian dari ide-ide sentral. Saya perlu memperkenalkan beberapa mesin presentasi tentang penilaian dan derivasi dan semacamnya.
Tatabahasa
Tata bahasanya memiliki dua bentuk yang saling didefinisikan, "istilah" yang merupakan gagasan umum tentang benda (jenis adalah benda, nilai adalah benda), termasuk * (jenis jenis), jenis fungsi dependen, dan abstraksi lambda, tetapi juga menanamkan " eliminasi "(yaitu" penggunaan "daripada" konstruksi "), yang merupakan aplikasi bersarang di mana hal yang pada akhirnya di posisi fungsi adalah variabel atau istilah yang dijelaskan dengan tipenya.
Aturan Pengurangan
Operasi substitusi t [e / x] menggantikan setiap kemunculan variabel x dengan eliminasi e, menghindari penangkapan nama. Untuk membentuk aplikasi yang dapat mengurangi, istilah lambda harus dianotasi berdasarkan jenisnya untuk melakukan eliminasi . Anotasi jenis ini memberikan abstraksi lambda semacam "reaktivitas", yang memungkinkan aplikasi untuk melanjutkan. Setelah kami mencapai titik di mana tidak ada lagi aplikasi yang terjadi dan t: T sedang disematkan kembali ke dalam sintaksis istilah, kita dapat menghapus anotasi jenis.
Mari kita memperluas hubungan ↝ reduksi dengan penutupan struktural: aturan berlaku di mana saja di dalam istilah dan eliminasi bahwa Anda dapat menemukan sesuatu yang cocok dengan sisi kiri. Tulis ↝ * untuk penutupan refleksif-transitif (0-atau-lebih-langkah) dari ↝. Sistem reduksi yang dihasilkan bersatu dalam pengertian ini:
Konteks
Konteks adalah urutan yang menetapkan jenis ke variabel, tumbuh di sebelah kanan, yang kita anggap sebagai "lokal", memberi tahu kita tentang variabel terikat terbaru. Properti penting dari konteks adalah bahwa selalu mungkin untuk memilih variabel yang belum digunakan dalam konteks. Kami mempertahankan invarian bahwa variabel yang dianggap sebagai tipe dalam konteks berbeda.
Penghakiman
Itu tata bahasa penilaian, tetapi bagaimana cara membacanya? Sebagai permulaan, ⊢ adalah simbol "turnstile" tradisional, memisahkan asumsi dari kesimpulan: Anda dapat membacanya secara informal sebagai "berkata".
berarti konteks yang diberikan G, tipe T mengakui istilah t;
berarti bahwa diberikan konteks G, eliminasi e diberikan tipe S.
Penilaian memiliki struktur yang menarik: nol atau lebih input , satu subjek atau lebih , nol atau lebih output .
Artinya, kita harus mengusulkan jenis-jenis istilah terlebih dahulu dan cukup memeriksanya , tetapi kita mensintesiskan jenis-jenis eliminasi.
Aturan Mengetik
Saya menyajikan ini dalam gaya Prolog yang samar-samar, menulis J -: P1; ...; Pn untuk menunjukkan bahwa penilaian J berlaku jika premis P1 hingga Pn juga berlaku. Premis akan menjadi penilaian lain, atau klaim tentang pengurangan.
Dan itu dia!
Hanya dua aturan yang tidak diarahkan sintaks: aturan yang mengatakan "Anda bisa mengurangi tipe sebelum Anda menggunakannya untuk memeriksa istilah" dan aturan yang mengatakan "Anda bisa mengurangi tipe setelah Anda mensintesisnya dari eliminasi". Salah satu strategi yang dapat dilakukan adalah mengurangi tipe hingga Anda mengekspos konstruktor paling atas.
Sistem ini tidak sangat normal (karena Paradox Girard, yang merupakan paradoks referensi diri pembohong), tetapi dapat dibuat sangat normal dengan memecah * menjadi "level alam semesta" di mana setiap nilai yang melibatkan tipe di level bawah sendiri memiliki tipe pada level yang lebih tinggi, mencegah referensi diri.
Sistem ini, bagaimanapun, memiliki sifat pelestarian tipe, dalam pengertian ini.
Konteks dapat menghitung dengan membiarkan istilah yang dikandungnya menghitung. Artinya, jika penilaian sekarang valid, Anda dapat menghitung inputnya sebanyak yang Anda suka dan subjeknya satu langkah, dan kemudian dimungkinkan untuk menghitung hasilnya entah bagaimana untuk memastikan penilaian yang dihasilkan tetap valid. Buktinya adalah induksi sederhana pada derivasi mengetik, mengingat pertemuan -> *.
Tentu saja, saya hanya menyajikan inti fungsional di sini, tetapi ekstensi bisa sangat modular. Berikut ini adalah pasangan.
sumber
G, x:S, G' ⊢ x is S -: G' ⊬ x
?Korespondensi Curry-Howard mengatakan bahwa ada korespondensi sistematis antara sistem tipe dan sistem bukti dalam logika. Mengambil pandangan programmer-centric ini, Anda bisa menyusunnya kembali dengan cara ini:
Terlihat dari sudut ini:
Berikut adalah aturan deduksi alami untuk logika tingkat pertama, menggunakan diagram dari entri Wikipedia tentang deduksi alami . Ini pada dasarnya adalah aturan dari kalkulus lambda mengetik minimal juga.
Perhatikan bahwa aturan memiliki ketentuan lambda di dalamnya. Ini dapat dibaca sebagai program yang membangun bukti dari kalimat yang diwakili oleh tipenya (atau lebih tepatnya, kami hanya mengatakan program adalah bukti ). Aturan pengurangan yang serupa yang Anda berikan dapat diterapkan pada persyaratan lambda ini.
Kenapa kita peduli dengan ini? Yah, pertama-tama, karena bukti mungkin berubah menjadi alat yang berguna dalam pemrograman, dan memiliki bahasa yang dapat bekerja dengan bukti sebagai objek kelas satu membuka banyak jalan. Misalnya, jika fungsi Anda memiliki prasyarat, alih-alih menuliskannya sebagai komentar, Anda sebenarnya dapat meminta bukti sebagai argumen.
Kedua, karena jenis mesin sistem yang diperlukan untuk menangani bilangan bulat mungkin memiliki kegunaan lain dalam konteks pemrograman. Secara khusus, bahasa yang diketik secara dependen menangani universal quantifiers ("for all x, ...") dengan menggunakan konsep yang disebut tipe fungsi dependen — fungsi di mana tipe statis hasil dapat bergantung pada nilai runtime argumen.
Untuk memberikan aplikasi yang sangat pejalan kaki ini, saya menulis kode sepanjang waktu yang harus membaca file Avro yang terdiri dari catatan dengan struktur yang seragam — semua berbagi set nama dan tipe bidang yang sama. Ini mengharuskan saya untuk:
Seperti yang dapat Anda lihat di halaman tutorial Avro Java , mereka menunjukkan kepada Anda bagaimana menggunakan perpustakaan sesuai dengan kedua pendekatan ini.
Dengan tipe fungsi dependen Anda dapat memiliki kue dan memakannya, dengan biaya sistem tipe yang lebih kompleks. Anda bisa menulis fungsi yang membaca file Avro, mengekstrak skema, dan mengembalikan konten file sebagai aliran catatan yang jenis statis tergantung pada skema yang disimpan dalam file . Kompiler akan dapat menangkap kesalahan di mana saya, misalnya, mencoba mengakses bidang bernama yang mungkin tidak ada dalam catatan file yang akan diproses saat runtime. Manis ya
sumber