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.
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# mengompilasi program dari direktori topik
gfortran prosedur-terikat.f90 -o prosedur-terikat
# menjalankan program hasil kompilasi
./prosedur-terikat 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.00Keluaran 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.
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# mencoba mengompilasi berkas dengan pemanggilan nopass yang keliru
gfortran salah-nopass.f90 -o salah-nopasssalah-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
containsdi 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
privatemenjadikan prosedur terikat satu-satunya pintu perubahan data, dan penggunaan nopass pada prosedur yang membutuhkan objek memindahkan argumen satu posisi tanpa terlihat.
Referensi#
- Learn: tutorial resmi Fortran — fortran-lang — Kumpulan tutorial resmi komunitas Fortran yang menjadi rujukan fitur bahasa pada jalur ini.
- Fortran Object Oriented Programming — fortran-lang — Artikel resmi komunitas tentang penerapan fitur berorientasi objek pada Fortran modern.
- The GNU Fortran Compiler — GNU Project — Manual gfortran, compiler yang digunakan untuk menjalankan seluruh contoh kode pada jalur ini.