Ketikkan inferensi dengan jenis produk

15

Saya sedang mengerjakan kompiler untuk bahasa concatenative dan ingin menambahkan dukungan jenis inferensi. Saya mengerti Hindley-Milner, tetapi saya telah mempelajari teori jenis ketika saya pergi, jadi saya tidak yakin bagaimana mengadaptasinya. Apakah sistem berikut ini masuk akal dan dapat disimpulkan?

Istilah adalah literal, komposisi istilah, kutipan istilah, atau primitif.

e::=x|ee|[e]|

Semua istilah menunjukkan fungsi. Untuk dua fungsi e1 dan e2 , e1e2=e2e1 , yaitu, penjajaran menunjukkan komposisi terbalik. Literal menunjukkan fungsi niladik.

Istilah selain komposisi memiliki aturan tipe dasar:

x:ι[Lit]Γe:σΓ[e]:α.ασ×α[Quot],α not free in Γ

Yang terutama absen adalah aturan untuk aplikasi, karena bahasa concatenative tidak memilikinya.

Tipe adalah literal, variabel tipe, atau fungsi dari tumpukan ke tumpukan, di mana tumpukan didefinisikan sebagai tupel bersarang kanan. Semua fungsi secara implisit polimorfik sehubungan dengan "sisa tumpukan".

τ::=ι|α|ρρρ::=()|τ×ρσ::=τ|α.σ

Ini adalah hal pertama yang tampaknya mencurigakan, tetapi saya tidak tahu persis apa yang salah dengannya.

Untuk membantu keterbacaan dan mengurangi tanda kurung, saya akan menganggap bahwa dalam skema jenis. Saya juga akan menggunakan huruf kapital untuk variabel yang menunjukkan tumpukan, daripada nilai tunggal.ab=b×(a)

Ada enam primitif. Lima yang pertama cukup berbahaya. dupmengambil nilai teratas dan menghasilkan dua salinannya. swapmengubah urutan dua nilai teratas. popmembuang nilai teratas. quotemengambil nilai dan menghasilkan kutipan (fungsi) yang mengembalikannya. applyberlaku kutipan untuk stack.

dup::Ab.AbAbbswap::Abc.AbcAcbpop::Ab.AbAquote::Ab.AbA(C.CCb)apply::AB.A(AB)B

Combinator terakhir compose,, harus mengambil dua kutipan dan mengembalikan tipe gabungannya, yaitu, . Dalam bahasa concatenative yang diketik secara statisCat, jenisini sangat mudah.[e1][e2]compose=[e1e2]compose

compose::ABCD.A(BC)(CD)A(BD)

Namun, tipe ini terlalu membatasi: mengharuskan produksi fungsi pertama sama persis dengan konsumsi fungsi kedua. Pada kenyataannya, Anda harus mengasumsikan tipe yang berbeda, lalu menyatukannya. Tetapi bagaimana Anda menulis tipe itu?

compose::ABCDE.A(BC)(DE)A

Jika Anda membiarkan menunjukkan perbedaan dua jenis, maka saya pikir Anda dapat menulis jenis dengan benar.compose

compose::ABCDE.A(BC)(DE)A((DC)B((CD)E))

Ini masih relatif mudah: composemengambil fungsi dan satu f 2 : D E . Hasilnya mengkonsumsi B di atas konsumsi f 2 yang tidak diproduksi oleh f 1 , dan menghasilkan D di atas produksi f 1 yang tidak dikonsumsi oleh f 2 . Ini memberikan aturan untuk komposisi biasa.f1:BCf2:DEBf2f1Df1f2

Γe1:AB.ABΓe2:CD.CDΓe1e2:((CB)A((BC)D))[Comp]

Namun, saya tidak tahu bahwa hipotesis ini sebenarnya sesuai dengan apa pun, dan saya telah mengejarnya cukup lama sehingga saya pikir saya salah belok. Mungkinkah itu perbedaan tuple yang sederhana?

A.()A=()A.A()=AABCD.ABCD=BD iff A=Cotherwise=undefined

Apakah ada sesuatu yang sangat buruk tentang hal ini yang tidak saya lihat, atau apakah saya berada di jalur yang benar? (Saya mungkin salah mengukur beberapa hal ini dan akan menghargai perbaikan di daerah itu juga.)

Jon Purdy
sumber
Bagaimana Anda menggunakan variabel dalam tata bahasa Anda? Pertanyaan ini seharusnya membantu Anda dalam menangani "subtyping" yang tampaknya Anda butuhkan.
jmad
1
@jmad: Saya tidak yakin saya mengerti pertanyaannya. Tipe variabel hanya ada demi skema tipe yang secara formal mendefinisikan, dan bahasa itu sendiri tidak memiliki variabel sama sekali, hanya definisi, yang dapat [saling] rekursif.
Jon Purdy
Cukup adil. Bisakah Anda mengatakan mengapa (mungkin dengan contoh) aturan composeterlalu ketat? Saya mendapat kesan bahwa ini baik-baik saja seperti ini. (misalnya pembatasanC=Ddapat ditangani dengan penyatuan seperti untuk aplikasi dalam seperti di λ-calculus)
jmad
@jmad: Tentu. Pertimbangkan twicedidefinisikan sebagai dup compose apply, yang mengambil kutipan dan menerapkannya dua kali. [1 +] twicebaik-baik saja: Anda membuat dua fungsi tipeιι. Tetapi [pop] twicetidak: jikaSEBUAHb.f1,f2:SEBUAHbSEBUAH, masalahnya adalah SEBUAHSEBUAHb, jadi ekspresi tidak diizinkan meskipun harus valid dan bertipe SEBUAHb.SEBUAHbbSEBUAH. Solusinya tentu saja untuk menempatkan kualifikasi di tempat yang tepat, tetapi saya terutama bertanya-tanya bagaimana sebenarnya menulis jenis composetanpa beberapa definisi melingkar.
Jon Purdy

Jawaban:

9

Berikut peringkat tipe-2

menyusun:SEBUAHBCδ.δ (α.α SEBUAHαB) (β.β BβC)δ (γ.γ SEBUAHγC)
tampaknya cukup umum. Ini jauh lebih polimorfik daripada tipe yang diusulkan dalam pertanyaan. Di sini variabel mengkuantifikasi potongan-potongan stack yang berdekatan, yang menangkap fungsi multi-argumen.

Huruf Yunani digunakan untuk variabel sisa tumpukan untuk kejelasan saja.

Ini mengungkapkan kendala bahwa tumpukan output dari elemen pertama pada tumpukan harus sama dengan tumpukan input dari elemen kedua. Instantiating variabel dengan tepatB karena dua argumen yang sebenarnya adalah cara mendapatkan kendala untuk bekerja dengan baik, daripada mendefinisikan operasi baru, seperti yang Anda usulkan dalam pertanyaan.

Tipe memeriksa tipe-2 tipe tidak dapat diputuskan secara umum, saya percaya, meskipun beberapa pekerjaan telah dilakukan yang memberikan hasil yang baik dalam praktek (untuk Haskell):

  • Simon L. Peyton Jones, Dimitrios Vytiniotis, Stephanie Weirich, Mark Shields: Inferensi tipe praktis untuk tipe peringkat sewenang-wenang. J. Funct. Program. 17 (1): 1-82 (2007)

Aturan jenis untuk komposisi hanyalah:

Γe1:α.α Aα BΓe1:α.α Bα CΓe1 e2:α.α Aα C

Agar sistem tipe berfungsi secara umum, Anda memerlukan aturan spesialisasi berikut:

Γe:α.α SEBUAHα BΓe:α.C SEBUAHα C B
Dave Clarke
sumber
Terima kasih, ini sangat membantu. Tipe ini benar untuk fungsi argumen tunggal, tetapi tidak mendukung banyak argumen. Misalnya, dup +harus memiliki tipeιι because + has type ιιι. But type inference in the absence of annotations is an absolute requirement, so clearly I need to go back to the drawing board. I have an idea for another approach to pursue, though, and will blog about it if it works out.
Jon Purdy
1
The stack types quantify over stack fragments, so there is no problem dealing with two argument functions. I'm not sure how this applies to dup +, as that does not use compose, as you defined it above.
Dave Clarke
Er, right, I meant [dup] [+] compose. But I read αB as B×α; say B=ι×ι; then you have (ι×ι)×α and not ι×(ι×α). The nesting isn’t right, unless you flip the stack around so that the top is the last (deepest nested) element.
Jon Purdy
I may be building my stack in the wrong direction. I don't think the nesting matters, so long as the pairs building up the stack do not appear in the programming language. (I'm planning to update my answer, but need to do a little research first.)
Dave Clarke
Yeah, nesting is pretty much an implementation detail.
Jon Purdy