Prosedur terikat tipe

Prosedur terikat tipe (type-bound procedure) sebagai metode yang dilekatkan pada tipe turunan.

Daftar isi

Prosedur terikat tipe (type-bound procedure) adalah prosedur yang dilekatkan pada deklarasi tipe turunan sehingga dipanggil melalui objeknya, misalnya t%setor(250000.0). Topik ini membahas cara melekatkan prosedur pada tipe, peran argumen objek yang diteruskan secara otomatis, perbedaan atribut pass dan nopass, serta satu pesan error nyata yang muncul ketika aturan itu dilanggar. Sebuah modul dompet tabungan disertakan dan dijalankan, sehingga setiap perilaku yang dibahas dapat diuji ulang dengan gfortran.

Melekatkan prosedur pada tipe#

Melekatkan prosedur dilakukan di dalam deklarasi tipe, pada bagian contains yang mengikuti daftar komponennya. Setiap baris procedure menyatakan satu prosedur terikat, misalnya procedure, pass(self) :: setor, sedangkan tubuh prosedur setor itu sendiri didefinisikan pada bagian contains modul yang menampung tipe tersebut. Nama pada pemanggilan boleh dibedakan dari nama prosedurnya dengan bentuk procedure :: luas => hitung_luas, sehingga nama binding dapat dirapikan tanpa mengubah nama prosedurnya.

Setelah terikat, prosedur dipanggil dengan sintaks yang sama seperti pemilihan komponen, yaitu nama objek diikuti tanda persen dan nama bindingnya. Satu pemanggilan t%setor(500000.0) menyatakan dengan jelas bahwa saldo pada objek t yang ditambah, sehingga pembaca kode langsung mengetahui objek mana yang diubah tanpa perlu menelusuri daftar argumen prosedurnya. Pola ini menggantikan bentuk lama call setor(t, 500000.0) yang menyembunyikan objek utamanya di antara argumen lain.

Argumen objek dan aturan pass#

Prosedur terikat tipe menerima objek yang menjadi sasaran pemanggilannya sebagai argumen dummy, dan argumen ini diteruskan compiler secara otomatis tanpa ditulis pada pemanggilan. Nama dummynya bebas, tetapi konvensi yang lazim adalah self, dengan deklarasi class(tabungan), intent(inout) :: self. Atribut pass(self) pada deklarasi binding menunjuk argumen dummy mana yang menerima objek, dan bila objek berada pada argumen pertama, penunjukan itu sekadar menegaskan perilaku bawaannya.

Deklarasi dummy memakai class(tabungan) dan bukan type(tabungan), karena variabel class menerima objek bertipe tabungan maupun seluruh turunannya, sedangkan type hanya menerima tipe yang persis sama. Perbedaan ini belum terasa pada modul tanpa pewarisan, tetapi menjadi syarat ketika prosedur yang sama dipakai lintas keluarga tipe pada topik polimorfisme. Kebiasaan menulis class sejak awal membuat prosedur terikat tidak perlu ditulis ulang saat tipe dikembangkan.

Atribut nopass berkebalikan dengan pass, yaitu objek sama sekali tidak diteruskan, sehingga prosedur berperilaku seperti prosedur biasa yang terhubung ke tipe hanya melalui namanya. Pemanggilannya tetap ditulis melalui objek, tetapi seluruh argumen harus disediakan pemanggil. Atribut ini berguna untuk prosedur yang tidak memerlukan isi objek sama sekali, dan jebakannya dibahas lengkap pada bagian Jebakan nopass dan pesan errornya.

Modul dompet dan pemanggilannya#

Program berikut melekatkan tiga subrutin dan satu fungsi pada tipe tabungan yang komponen saldonya dinyatakan private. Dengan komponen yang disembunyikan, satu-satunya cara mengubah saldo adalah melalui prosedur terikatnya, sehingga aturan bisnis seperti penolakan penarikan melebihi saldo dipatuhi di satu tempat.

fortran prosedur-terikat.f90
module dompet
  implicit none

  type :: tabungan
     private
     real :: saldo = 0.0
   contains
     procedure, pass(self) :: setor
     procedure, pass(self) :: tarik
     procedure, pass(self) :: tampilkan
     procedure, pass(self) :: bunga_tahunan
  end type tabungan

contains

  subroutine setor(self, jumlah)
    class(tabungan), intent(inout) :: self
    real, intent(in) :: jumlah
    self%saldo = self%saldo + jumlah
  end subroutine setor

  subroutine tarik(self, jumlah)
    class(tabungan), intent(inout) :: self
    real, intent(in) :: jumlah
    if (jumlah > self%saldo) then
       print '(1X, A, F10.2, A)', "Penarikan", jumlah, " ditolak, saldo tidak cukup"
    else
       self%saldo = self%saldo - jumlah
    end if
  end subroutine tarik

  subroutine tampilkan(self)
    class(tabungan), intent(in) :: self
    print '(1X, A, F10.2)', "Saldo saat ini:", self%saldo
  end subroutine tampilkan

  real function bunga_tahunan(self, laju)
    class(tabungan), intent(in) :: self
    real, intent(in) :: laju
    bunga_tahunan = self%saldo * laju
  end function bunga_tahunan

end module dompet

program uji_tabungan
  use dompet
  implicit none

  type(tabungan) :: t

  call t%setor(500000.0)
  call t%setor(250000.0)
  call t%tampilkan()
  call t%tarik(300000.0)
  call t%tampilkan()
  call t%tarik(1000000.0)
  call t%tampilkan()
  print '(1X, A, F10.2)', "Bunga setahun (laju 3 persen):", t%bunga_tahunan(0.03)
end program uji_tabungan
bash
# mengompilasi program dari direktori topik
gfortran prosedur-terikat.f90 -o prosedur-terikat
# menjalankan program hasil kompilasi
./prosedur-terikat
text Keluaran
 Saldo saat ini: 750000.00
 Saldo saat ini: 450000.00
 Penarikan1000000.00 ditolak, saldo tidak cukup
 Saldo saat ini: 450000.00
 Bunga setahun (laju 3 persen):  13500.00

Keluaran mengikuti urutan pemanggilan pada unit program, yaitu dua setor menghasilkan saldo 750000.00, penarikan sah menurunkannya ke 450000.00, dan penarikan kedua ditolak karena melebihi saldo. Baris penolakan memperlihatkan efek format F10.2 yang penuh terisi, karena angka 1000000.00 tepat sepuluh karakter sehingga menempel pada kata Penarikan. Baris terakhir memanggil fungsi terikat bunga_tahunan di dalam ekspresi cetak, dan fungsi tersebut membaca saldo milik t tanpa saldo diteruskan secara manual, yaitu 450000.00 dikalikan laju 0.03 menjadi 13500.00.

Jebakan nopass dan pesan errornya#

Versi awal berkas ini menyimpan satu kesalahan yang berharga untuk dipelajari. Fungsi bunga_tahunan dideklarasikan sebagai prosedur biasa dengan dua argumen dummy saldo dan laju, lalu dilekatkan dengan procedure, nopass :: bunga_tahunan, sedangkan pemanggilannya tetap ditulis seolah saldo diteruskan otomatis, yaitu t%bunga_tahunan(0.03). Karena nopass tidak meneruskan apa pun, nilai 0.03 mengikat ke argumen pertama saldo, dan argumen laju dibiarkan kosong. Berkas berikut merekam versi keliru tersebut dan kompilasinya gagal.

fortran salah-nopass.f90
module dompet_salah
  implicit none

  type :: tabungan
     private
     real :: saldo = 0.0
   contains
     procedure, pass(self) :: setor
     procedure, nopass :: bunga_tahunan
  end type tabungan

contains

  subroutine setor(self, jumlah)
    class(tabungan), intent(inout) :: self
    real, intent(in) :: jumlah
    self%saldo = self%saldo + jumlah
  end subroutine setor

  real function bunga_tahunan(saldo, laju)
    real, intent(in) :: saldo, laju
    bunga_tahunan = saldo * laju
  end function bunga_tahunan

end module dompet_salah

program salah_nopass
  use dompet_salah
  implicit none

  type(tabungan) :: t

  call t%setor(450000.0)
  print '(1X, A, F10.2)', "Bunga setahun (laju 3 persen):", t%bunga_tahunan(0.03)
end program salah_nopass
bash
# mencoba mengompilasi berkas dengan pemanggilan nopass yang keliru
gfortran salah-nopass.f90 -o salah-nopass
text Keluaran
salah-nopass.f90:34:59:

   34 |   print '(1X, A, F10.2)', "Bunga setahun (laju 3 persen):", t%bunga_tahunan(0.03)
      |                                                           1
Error: Missing actual argument for argument ‘laju’ at (1)

Pesan error menunjuk kolom pemanggilan dan menyatakan argumen aktual untuk laju hilang, padahal pemanggilannya memuat nilai 0.03 yang dimaksudkan sebagai laju. Inilah jebakannya, yaitu argumen pertama yang tampak sebagai laju ternyata mengisi saldo, sehingga pertukaran argumen terjadi tanpa terlihat. Ada dua jalan keluar, dan keduanya mengajarkan kontrak pass dengan jelas. Jalan pertama adalah meneruskan semua argumen secara eksplisit seperti t%bunga_tahunan(t%saldo, 0.03), tetapi pada tipe ini jalan itu tertutup karena komponen saldo bersifat private dan tidak dapat dinamai dari luar modul. Jalan kedua, yang dipakai pada berkas utama, adalah mengubah deklarasinya menjadi procedure, pass(self) :: bunga_tahunan dengan argumen dummy self, sehingga objek diteruskan compiler dan pemanggilan cukup t%bunga_tahunan(0.03).

Kesalahan umum#

Kesalahan pertama adalah mendeklarasikan argumen objek dengan type(tabungan) alih-alih class(tabungan). Kode itu sah untuk tipe yang persis sama, tetapi pemanggilan lewat variabel polimorfik ditolak compiler, dan perbaikannya adalah mengganti deklarasinya menjadi class. Kesalahan kedua adalah mencoba mengubah saldo langsung dari unit program, misalnya t%saldo = 0.0, yang ditolak compiler dengan pesan bahwa komponen tersebut private. Penyembunyian itu memang disengaja, dan jalur yang benar adalah melalui prosedur terikat yang sudah disediakan.

Kesalahan ketiga adalah memakai nopass untuk prosedur yang membutuhkan objek, sebagaimana terjadi pada fungsi bunga di atas, sehingga argumen bergeser satu posisi tanpa terlihat. Pemindahan argumen semacam ini tidak selalu menghasilkan error, karena bila tipe kedua argumennya sama, kode bisa saja lolos kompilasi dan menghitung sesuatu yang salah. Memeriksa deklarasi dummy terhadap pemanggilan setiap kali atribut pass atau nopass diubah adalah kebiasaan yang mencegah kesalahan jenis ini.

Ringkasan#

  • Prosedur terikat tipe dideklarasikan pada bagian contains di dalam tipe dan dipanggil melalui objeknya dengan tanda persen, sehingga objek utama pada setiap pemanggilan terlihat jelas.
  • Atribut pass meneruskan objek secara otomatis ke argumen dummy yang ditunjuk, sedangkan nopass tidak meneruskan objek apa pun sehingga seluruh argumen disediakan pemanggil.
  • Komponen private menjadikan prosedur terikat satu-satunya pintu perubahan data, dan penggunaan nopass pada prosedur yang membutuhkan objek memindahkan argumen satu posisi tanpa terlihat.

Referensi#