Template

Abstraksi sintaksis berbiaya nol yang diperluas secara inline pada waktu kompilasi.

Daftar isi

Template adalah pola kode yang diperluas saat kompilasi, sehingga pemanggilannya tidak menjadi pemanggilan fungsi pada saat program berjalan, melainkan disisipkan langsung di tempatnya. Sifat ini menempatkannya di antara proc dan makro, yaitu lebih lentur daripada proc karena parameternya berupa potongan kode, tetapi jauh lebih ringkas daripada makro karena ditulis dengan sintaks Nim biasa. Topik ini membangun template sederhana, memakai parameter bertipe, lalu menyibak satu jebakan klasiknya yang terkenal pada template bahasa lain, dan membuktikan bahwa perilakunya berbeda di Nim.

Ekspansi inline saat kompilasi#

Deklarasi template menyerupai proc, tetapi parameternya untyped menerima potongan kode apa pun, dan tubuhnya disisipkan pada setiap pemanggilan. Template pertama menghitung kuadrat, sedangkan yang kedua menerima blok pernyataan dan mengulanginya dua kali, sesuatu yang tidak dapat dilakukan proc karena blok kode bukan nilai yang dapat diteruskan.

nim kuadrat.nim
# Template: pola kode yang diperluas secara inline saat kompilasi
template kuadrat(x: untyped): untyped =
  (x) * (x)

template duaKali(body: untyped) =
  body
  body

var penghitung = 0
duaKali:
  penghitung += 3

echo kuadrat(4)
echo kuadrat(2 + 3)
echo penghitung
bash
# kompilasi dan jalankan contoh template untyped
nim r -f --hints:off kuadrat.nim
text Keluaran
16
25
6

Ekspansi yang terjadi nyata tetapi tak terlihat pada keluaran, yaitu kuadrat(2 + 3) menjadi (2 + 3) * (2 + 3) sebelum program disusun, dan blok duaKali menuliskan penghitung += 3 dua kali sehingga pencacahnya bernilai 6. Tanda kurung pada tubuh kuadrat menjaga argumen majemuk tetap satu kesatuan, dan nim menyediakan expandMacros untuk melihat hasil perluasannya sendiri ketika perlu memastikan. Pernyataan duaKali: diikuti blok terindentasi adalah bentuk pemanggilan khusus template dan makro, yaitu argumen terakhir ditulis sebagai blok, dan bentuk inilah yang dipakai pustaka standar untuk pernyataan serupa defer maupun gaya pembangun DSL lainnya.

Pengenal yang dideklarasikan di dalam tubuh template bersifat higienis, artinya kompilator mengganti namanya secara otomatis agar tidak mungkin menabrak pengenal milik pemanggil, sehingga variabel lokal seperti sumber pada contoh perbaikan di bawah aman dipakai di mana pun template dipanggil. Kebalikannya tidak otomatis, yaitu pengenal milik pemanggil yang dirujuk dari tubuh template harus disebut dengan pernyataan bind apabila ada kemungkinan namanya tertutup oleh pengenal lain di titik pemanggilan.

Parameter bertipe dan pengenal yang disisipkan#

Parameter template tidak wajib untyped. Parameter bertipe seperti int diperiksa pada setiap pemanggilan, sehingga kesalahan argumen tertangkap lebih dini, dan tubuhnya tetap disisipkan secara inline sebagaimana template lainnya. Template kedua berikut membangun sebuah string dengan perulangan, sesuatu yang lazim untuk format keluaran.

nim pengali.nim
# Template dengan parameter bertipe
template pengaliDua(n: int): int =
  n * 2

template garis(jumlah: int): string =
  var s = ""
  for i in 1 .. jumlah:
    s.add('-')
  s

echo pengaliDua(21)
echo pengaliDua(7 + 1)
echo garis(10)
bash
# kompilasi dan jalankan contoh template bertipe
nim r -f --hints:off pengali.nim
text Keluaran
42
16
----------

Baris kedua keluaran memperlihatkan bahwa 7 + 1 dievaluasi menjadi satu nilai sebelum ditempel ke tubuh template bertipe, sehingga hasilnya 16 dan bukan ekspansi tekstual yang aneh. Pembedaan untyped dan bertipe inilah kunci memilih perilaku yang diinginkan, yaitu untyped untuk parameter yang bentuknya belum tentu ekspresi lengkap, dan bertipe untuk parameter yang memang bernilai.

Template juga menjadi bahan bangun modul-modul pustaka standar yang permukaannya terasa seperti bahasa baru, karena sejumlah pernyataan populer pada modul sugar dan strformat sebenarnya adalah template yang memperluas dirinya menjadi kode biasa. Pengalaman membaca dan menulis template kecil seperti pada topik ini dengan demikian langsung terpakai ketika membaca sumber pustaka tersebut, sebab bentuk deklarasinya persis sama, lengkap dengan parameter bertipe maupun untyped. Template dengan tipe kembalian dinyatakan seperti proc, dan tubuhnya boleh memuat pernyataan apa pun sebagaimana proc biasa, termasuk deklarasi variabel lokal seperti s pada garis.

Jebakan evaluasi ganda#

Template untyped menyisipkan argumen apa adanya, sehingga parameter yang dipakai lebih dari sekali membuat argumennya dievaluasi sebanyak itu pula. Jebakan ini terkenal pada makro C, dan Nim mewarisinya pada template untyped, meskipun kekhawatiran klasik lainnya justru tidak berlaku, yaitu urutan operasi tetap benar karena substitusi terjadi pada tingkat pohon sintaks, sehingga kuadratSalah(2 + 3) bernilai 25 seperti yang diharapkan. Bahaya sesungguhnya muncul ketika argumennya memuat efek samping.

nim kuadrat_salah.nim
# Template untyped yang memakai parameternya dua kali mengevaluasi dua kali
template kuadratSalah(x: untyped): untyped =
  x * x

var panggilan = 0

proc sumberData(): int =
  inc panggilan
  5

echo kuadratSalah(sumberData())
echo panggilan
bash
# kompilasi dan jalankan contoh evaluasi ganda
nim r -f --hints:off kuadrat_salah.nim
text Keluaran
25
2

Nilai kuadratnya benar, tetapi panggilan mencatat 2, karena sumberData() dijalankan dua kali oleh ekspansi x * x. Perbaikannya sederhana, yaitu mengikat parameter ke variabel lokal di dalam tubuh template, sehingga sumber hanya dievaluasi sekali dan sisanya memakai ikatan itu.

nim kuadrat_benar.nim
# Perbaikan: ikat parameter ke variabel lokal agar evaluasi sekali
template kuadratBenar(x: untyped): untyped =
  let sumber = x
  sumber * sumber

var panggilan = 0

proc sumberData(): int =
  inc panggilan
  5

echo kuadratBenar(sumberData())
echo panggilan
bash
# kompilasi dan jalankan versi perbaikan evaluasi tunggal
nim r -f --hints:off kuadrat_benar.nim
text Keluaran
25
1

Kesalahan umum#

  • Memberi argumen yang tidak cocok dengan tipe parameter template. Template bertipe menolaknya saat kompilasi, misalnya pengaliDua("a") menghasilkan Error: type mismatch yang menyebut posisi pemanggilan, dan pemecahannya menyesuaikan argumen atau mengubah parameter menjadi untyped bila kelenturan memang dimaksudkan.
  • Mengasumsikan perilaku makro teks ala C pada template Nim. Substitusi terjadi pada tingkat AST, sehingga urutan operasi argumen majemuk tetap terjaga, sedangkan yang benar-benar terjadi adalah evaluasi ganda pada parameter untyped yang dipakai berulang.
  • Mengharapkan template dapat menerima blok kode melalui parameter proc. Blok pernyataan hanya dapat diteruskan ke template atau makro, karena proc menerima nilai, sedangkan duaKali pada contoh pertama menerima pernyataan itu sendiri.

Ringkasan#

  • Template diperluas secara inline pada waktu kompilasi, sehingga pemanggilannya tanpa biaya saat program berjalan dan parameternya dapat berupa potongan kode maupun blok pernyataan.
  • Parameter bertipe memeriksa argumen pada setiap pemanggilan, sedangkan untyped menerima apa pun, dan keduanya tetap disisipkan apa adanya pada titik pemanggilan.
  • Parameter untyped yang dipakai lebih dari sekali mengevaluasi argumennya sebanyak itu, dan perbaikan baku berupa ikatan variabel lokal di awal tubuh template. Kebiasaan mengikat argumen sejak baris pertama tubuh template tidak menambah biaya apa pun dan menutup seluruh kelas jebakan evaluasi ganda sejak template pertama ditulis.

Referensi#