Inferensi tipe global

Cara compiler menyimpulkan tipe seluruh program tanpa anotasi dan mengapa inferensinya global.

Daftar isi

Crystal mengetahui tipe setiap ekspresi tanpa menuntut satu pun anotasi dari penulisnya. Proses menyimpulkan tipe ini disebut inferensi tipe (type inference), dan cakupannya adalah seluruh program, bukan baris demi baris. Topik ini membahas cara compiler menyimpulkan tipe, peralatan untuk melihat hasil inferensinya, serta batas yang membuat beberapa tempat tetap memerlukan anotasi. Setelah membaca topik ini, pesan error dari compiler dapat dibaca dengan memahami keputusan tipe yang melatarbelakanginya.

Program tanpa satu pun anotasi tipe#

Tipe variabel disimpulkan dari ekspresi yang menugaskan nilainya, dan tipe nilai balik metode disimpulkan dari badannya. Tidak ada satu pun nama tipe pada program berikut, tetapi setiap nilai tetap bertipe pasti bagi compiler.

crystal tanpa-anotasi.cr
# Program tanpa satu pun anotasi tipe

def dobel(n)
  n * 2
end

def gabung(kiri, kanan)
  kiri + kanan
end

jumlah = dobel(21)
p typeof(jumlah)
p jumlah

kalimat = gabung("kuda", "nil")
p typeof(kalimat)
p kalimat

Jalankan berkasnya dengan perintah berikut.

bash
# jalankan program tanpa-anotasi.cr
crystal run tanpa-anotasi.cr
text Keluaran
Int32
42
String
"kudanil"

Metode dobel mengalikan argumennya dengan dua, sehingga pemanggilan dobel(21) menghasilkan Int32 dan variabel jumlah mengikutinya. Metode gabung menyambungkan dua argumennya, sehingga kalimat bertipe String tanpa anotasi apa pun. Kesimpulan ini dilakukan compiler sebelum program dijalankan, sehingga kesalahan tipe terdeteksi pada waktu compile dan bukan saat program berjalan.

Menyimpulkan tipe dari seluruh program#

Compiler Crystal memproses program melalui tiga tahap, yaitu parsing, analisis semantik, dan pembangkitan kode. Inferensi tipe berlangsung pada tahap analisis semantik, ketika seluruh definisi sudah terdaftar dan setiap pemanggilan diperiksa terhadap definisi yang ada. Karena itu definisi metode boleh berada setelah pemanggilannya, sebagaimana program berikut.

crystal Contoh: definisi metode setelah pemanggilannya
p halo

def halo
  "hai"
end
text Keluaran
"hai"

Pemanggilan p halo ditulis sebelum definisinya, tetapi program tetap berjalan dan menghasilkan teks yang benar. Setelah tahap parsing selesai, compiler telah membaca seluruh berkas sehingga definisi halo sudah terdaftar ketika baris pemanggilannya diperiksa. Cara kerja ini berbeda dari bahasa dinamis seperti Ruby. Tipe pada Ruby melekat pada nilai saat program berjalan, sedangkan Crystal menetapkan tipe setiap ekspresi pada saat compile dengan membaca gambaran utuh program. Sifat inilah yang disebut inferensi global, dan akibat praktisnya, perubahan satu definisi dapat mengubah hasil pemeriksaan tipe di tempat lain.

typeof sebagai jendela keputusan compiler#

Metode typeof mengembalikan tipe sebuah ekspresi sebagaimana dilihat compiler, sehingga keputusan inferensinya dapat diamati langsung.

crystal Contoh: tipe dari beberapa ekspresi
p typeof(3.5)
p typeof([1, 2])
p typeof(1 < 2)
text Keluaran
Float64
Array(Int32)
Bool

Ekspresi di dalam typeof tidak dievaluasi, sehingga pemanggilannya tidak berdampak apa pun pada keadaan program. Bukti dapat dilihat pada program berikut, yang nilai x-nya tetap 1 meskipun ekspresi di dalam typeof mencoba menugaskan 999.

crystal Contoh: argumen typeof tidak dievaluasi
x = 1
p typeof(x = 999)
p x
text Keluaran
Int32
1

Variabel lokal mengikuti penugasan terakhir yang terlihat pada alurnya. Penugasan ulang dengan tipe berbeda mengganti tipe variabel itu, sedangkan penugasan yang terjadi di dalam percabangan membentuk union dari semua kemungkinannya.

crystal Contoh: penugasan ulang mengganti tipe
x = 42
p typeof(x)
x = "empat puluh dua"
p typeof(x)
text Keluaran
Int32
String

Setelah penugasan kedua, compiler tidak lagi menganggap x dapat berisi Int32 pada baris-baris berikutnya. Tipe yang berlaku selalu tipe dari nilai yang terlihat pada titik itu, bukan gabungan seluruh riwayat penugasannya. Peralatan sederhana ini berguna saat membaca program dan saat menguji dugaan tipe, karena hasil typeof persis merupakan tipe yang digunakan compiler untuk memeriksa baris berikutnya.

Batas inferensi pada variabel instans#

Variabel instans disimpulkan dari seluruh pengisian yang mungkin terjadi di dalam class, bukan dari urutan pemanggilan metode. Compiler tidak menalar bahwa satu metode dipanggil lebih dulu daripada metode lain, sehingga variabel instans yang diisi di satu metode dan dibaca di metode lain tetap memasukkan kemungkinan belum terisi.

crystal Contoh: tipe variabel instans lintas metode
class Contoh
  def isi
    @x = 42
  end

  def baca
    @x
  end
end

c = Contoh.new
p typeof(c.baca)
text Keluaran
(Int32 | Nil)

Tipe @x menjadi Int32 | Nil karena pengisiannya di metode isi terkumpul, sedangkan pembacaannya di baca menambah kemungkinan variabel itu belum terisi. Apabila variabel instans sama sekali tidak diisi di mana pun, compiler menolak program dengan pesan can't infer the type of instance variable dan menyarankan anotasi tipe. Batas inilah alasan anotasi tetap diperlukan pada class, dan tempat inilah yang lazim memunculkan anotasi dalam kode Crystal sehari-hari.

Kesalahan umum#

Mengira setiap variabel wajib diberi anotasi tipe. Anotasi hanya diperlukan ketika compiler tidak punya dasar untuk menyimpulkan, dan kasus yang lazim adalah variabel instans yang tidak pernah diisi. Variabel lokal dan metode tanpa anotasi tetap diperiksa penuh, sebagaimana program pada bagian pertama topik ini. Anotasi pada tempat yang sudah jelas hanya mengulang keputusan compiler.

Mengira penugasan ulang dengan tipe berbeda selalu ditolak compiler. Program pada bagian typeof menunjukkan sebaliknya, yaitu tipe variabel lokal mengikuti penugasan terakhir yang terlihat. Yang ditolak adalah operasi yang tidak cocok dengan tipe nilai pada titik pemakaiannya, misalnya operasi aritmetika terhadap nilai yang mungkin nil.

Ringkasan#

  • Tipe disimpulkan dari seluruh program pada tahap analisis semantik, setelah semua definisi terdaftar.
  • Definisi metode boleh berada setelah pemanggilannya karena pemeriksaan tipe berlangsung terhadap gambaran utuh program.
  • typeof menunjukkan tipe yang dilihat compiler tanpa mengevaluasi ekspresinya.
  • Variabel lokal mengikuti penugasan terakhirnya, sedangkan variabel instans disimpulkan dari seluruh pengisian di class dan memerlukan anotasi ketika tidak ada pengisian yang terlihat.

Referensi#