Subprogram generik

Subprogram generik yang diparameterisasi atas tipe.

Daftar isi

Subprogram generik (generic subprogram) adalah subprogram yang diparameterisasi atas satu atau lebih tipe, sehingga satu deklarasi melayani banyak tipe data. Deklarasinya memuat parameter formal (formal parameter), yaitu tipe yang belum ditentukan, dan baru menjadi subprogram nyata setelah diinstansiasi (instantiation) dengan tipe aktual. Topik ini membahas bentuk deklarasi subprogram generik, cara menginstansiasinya dengan pernyataan is new, kendala pada tipe formal, serta dua kesalahan yang sering muncul. Contoh yang digunakan adalah penukaran dua nilai dan penghitungan rata-rata pembacaan sensor suhu.

Bentuk deklarasi generic#

Deklarasi subprogram generik terdiri atas dua bagian, yaitu bagian generic yang memuat parameter formal dan spesifikasi subprogram biasa setelahnya. Bagian generic menyatakan tipe apa saja yang dibutuhkan, sedangkan spesifikasinya menuliskan tipe formal itu sebagaimana tipe mana pun pada subprogram biasa. Deklarasi semacam ini tidak menghasilkan kode yang dapat dipanggil, sehingga kompilator menolak setiap upaya pemanggilan langsung. Bentuknya berpola sebagai berikut.

ada Contoh: kerangka deklarasi subprogram generik
-- tidak-dijalankan
generic
   type T is private;
procedure Nama_Prosedur (Pertama : in out T; Kedua : in out T);

--  Instansiasi menghasilkan prosedur nyata untuk tipe Integer.
procedure Nama_Prosedur_Integer is new Nama_Prosedur (Integer);

Kendala is private pada tipe formal hanya menjamin satu hal, yaitu tipe aktual dapat ditugaskan dan disalin. Di dalam badan generik, kompilator tidak mengetahui operasi lain atas tipe formal, sehingga operator aritmetika maupun perbandingan tidak tersedia secara otomatis. Bentuk parameter formal lain menjamin lebih banyak operasi, dan beberapa bentuk itu dibahas pada topik tentang parameter formal.

Letak deklarasi generik mengikuti aturan umum deklarasi Ada. Generik boleh dinyatakan pada tingkat library, di dalam package, maupun di dalam bagian deklarasi subprogram lain, dan seluruhnya memerlukan badan sebelum dapat diinstansiasi. Apabila hanya spesifikasinya yang dituliskan, badan subprogram generik harus tetap tersedia, baik pada unit yang sama maupun pada berkas badan terpisah. Aturan ini sama dengan aturan subprogram biasa, dengan tambahan syarat instansiasi sebelum pemanggilan.

Instansiasi dengan new#

Instansiasi dilakukan dengan pernyataan is new, yang menghasilkan subprogram nyata untuk satu tipe aktual. Setiap instansiasi menciptakan salinan subprogram dengan seluruh tipe formalnya diganti tipe aktual, sehingga dua instansiasi untuk tipe berbeda menghasilkan dua subprogram yang terpisah. Nama instansiasi ditentukan penulis program, dan konvensinya mengaitkan nama tersebut dengan tipe aktualnya. Program berikut menginstansiasi satu prosedur generik untuk tipe Integer dan Float.

ada tukar_generik.adb
with Ada.Text_IO;
with Ada.Integer_Text_IO;
with Ada.Float_Text_IO;

procedure Tukar_Generik is

   --  Bagian generic: T adalah tipe formal.
   generic
      type T is private;
   procedure Tukar (Kiri : in out T; Kanan : in out T);

   procedure Tukar (Kiri : in out T; Kanan : in out T) is
      Sementara : constant T := Kiri;
   begin
      Kiri  := Kanan;
      Kanan := Sementara;
   end Tukar;

   --  Instansiasi untuk tipe Integer.
   procedure Tukar_Integer is new Tukar (Integer);

   --  Instansiasi untuk tipe Float.
   procedure Tukar_Float is new Tukar (Float);

   X : Integer := 10;
   Y : Integer := 24;
   A : Float   := 22.5;
   B : Float   := 21.5;

begin
   Tukar_Integer (X, Y);
   Tukar_Float (A, B);

   Ada.Text_IO.Put ("Integer : ");
   Ada.Integer_Text_IO.Put (X, Width => 1);
   Ada.Text_IO.Put (" dan ");
   Ada.Integer_Text_IO.Put (Y, Width => 1);
   Ada.Text_IO.New_Line;

   Ada.Text_IO.Put ("Float   : ");
   Ada.Float_Text_IO.Put (A, Fore => 1, Aft => 1, Exp => 0);
   Ada.Text_IO.Put (" dan ");
   Ada.Float_Text_IO.Put (B, Fore => 1, Aft => 1, Exp => 0);
   Ada.Text_IO.New_Line;
end Tukar_Generik;
bash
# kompilasi program
gnatmake -q tukar_generik.adb
# jalankan program
./tukar_generik
text Keluaran
Integer : 24 dan 10
Float   : 21.5 dan 22.5

Keluaran memperlihatkan kedua penukaran bekerja dengan benar pada tipe yang berbeda. Prosedur Tukar_Integer dan Tukar_Float berasal dari satu deklarasi yang sama, tetapi keduanya memeriksa tipe secara terpisah, sehingga kesalahan tipe pada pemanggilan mana pun tertangkap pada waktu kompilasi. Deklarasi instansiasi pada program di atas juga memperlihatkan bahwa generik boleh dinyatakan di dalam bagian deklarasi subprogram lain, selain pada tingkat library dan di dalam package. Pemanggilannya sama seperti prosedur biasa, tanpa sintaks tambahan apa pun.

Kendala tipe formal untuk titik mengambang#

Tipe formal tidak selalu berupa is private. Untuk menghitung rata-rata, tipe formalnya dinyatakan dengan is digits <>, yaitu kendala yang menerima tipe apa pun berbasis bilangan titik mengambang (floating point). Tipe array penampungnya dinyatakan sebagai parameter formal tersendiri dengan indeks Positive. Kendala is digits <> memberi akses ke operator penjumlahan dan pembagian di dalam badan generik, operasi yang tidak tersedia pada is private. Hasil instansiasi untuk tipe array suhu berperilaku seperti fungsi biasa.

ada rata_rata_generik.adb
with Ada.Text_IO;
with Ada.Float_Text_IO;

procedure Rata_Rata_Generik is

   --  T formal titik mengambang dan Larik formal array bertipe T.
   generic
      type T is digits <>;
      type Larik is array (Positive range <>) of T;
   function Rata_Rata (Sekumpulan : Larik) return T;

   function Rata_Rata (Sekumpulan : Larik) return T is
      Total : T := 0.0;
   begin
      for Nilai of Sekumpulan loop
         Total := Total + Nilai;
      end loop;
      return Total / T (Sekumpulan'Length);
   end Rata_Rata;

   type Larik_Suhu is array (Positive range <>) of Float;

   function Rata_Rata_Suhu is new Rata_Rata
     (T => Float, Larik => Larik_Suhu);

   Suhu_Sensor : constant Larik_Suhu := (22.5, 23.0, 21.5, 23.5, 22.0);

begin
   Ada.Text_IO.Put ("Rata-rata suhu : ");
   Ada.Float_Text_IO.Put
     (Rata_Rata_Suhu (Suhu_Sensor), Fore => 1, Aft => 2, Exp => 0);
   Ada.Text_IO.New_Line;
end Rata_Rata_Generik;
bash
# kompilasi program
gnatmake -q rata_rata_generik.adb
# jalankan program
./rata_rata_generik
text Keluaran
Rata-rata suhu : 22.50

Keluaran menampilkan rata-rata lima pembacaan sensor, yaitu 22.50 derajat. Bagian generic memuat dua parameter formal sekaligus, yaitu tipe elemen dan tipe array, dan keduanya diisi saat instansiasi. Pendekatan ini membuat satu badan fungsi melayani array suhu, array tekanan, atau array tipe titik mengambang lain tanpa satu baris pun yang disalin. Generik yang sama juga boleh diinstansiasi beberapa kali pada satu unit, sehingga dukungan atas tipe baru cukup ditambahkan sebagai instansiasi baru.

Fungsi generik mengikuti aturan yang sama dengan prosedur generik, yaitu keduanya tidak dapat dipanggil sebelum diinstansiasi. Perbedaannya terletak pada bentuk spesifikasinya, yaitu fungsi mengembalikan nilai sehingga cocok untuk perhitungan seperti rata-rata. Kombinasi dua parameter formal pada contoh ini juga memperlihatkan bahwa bagian generic tidak dibatasi satu parameter, dan urutan pengisiannya bebas karena asosiasi bernama T => dan Larik => digunakan.

Kesalahan umum#

Kesalahan pada subprogram generik umumnya tertangkap pada waktu kompilasi, karena kendala parameter formal membatasi apa saja yang boleh ditulis di dalam badan generik. Dua kesalahan berikut sering ditemui.

Kesalahan pertama adalah memanggil subprogram generik secara langsung tanpa menginstansiasinya terlebih dahulu.

ada panggil_langsung_error.adb
with Ada.Text_IO;

--  Berkas ini sengaja salah: memanggil subprogram generik tanpa instansiasi.
procedure Panggil_Langsung_Error is

   generic
      type T is private;
   procedure Tukar (Kiri : in out T; Kanan : in out T);

   procedure Tukar (Kiri : in out T; Kanan : in out T) is
      Sementara : constant T := Kiri;
   begin
      Kiri  := Kanan;
      Kanan := Sementara;
   end Tukar;

   A : Integer := 1;
   B : Integer := 2;

begin
   Tukar (A, B);
   Ada.Text_IO.Put_Line ("selesai");
end Panggil_Langsung_Error;
bash
# kompilasi berkas yang memanggil generik tanpa instansiasi
gnatmake -q panggil_langsung_error.adb
text Keluaran
panggil_langsung_error.adb:21:04: error: must instantiate generic procedure "Tukar" before call
gnatmake: "panggil_langsung_error.adb" compilation error

Pesan error menyatakan bahwa prosedur generik Tukar wajib diinstansiasi sebelum dipanggil. Deklarasi generik belum menghasilkan subprogram yang dapat dipanggil, sedangkan pemanggilan memerlukan instansiasi dengan tipe yang sudah pasti. Perbaikannya adalah menuliskan instansiasi terlebih dahulu, misalnya procedure Tukar_Integer is new Tukar (Integer), lalu memanggil nama instansiasinya.

Kesalahan kedua adalah menggunakan operator pada tipe formal is private, padahal kendala itu tidak menjamin operator apa pun.

ada operasi_hilang_error.adb
--  Berkas ini sengaja salah: memakai operator < pada tipe formal private.
procedure Operasi_Hilang_Error is

   generic
      type T is private;
   function Terbesar (Kiri, Kanan : T) return T;

   function Terbesar (Kiri, Kanan : T) return T is
   begin
      if Kiri < Kanan then
         return Kanan;
      else
         return Kiri;
      end if;
   end Terbesar;

begin
   null;
end Operasi_Hilang_Error;
bash
# kompilasi berkas yang memakai operator pada tipe formal private
gnatmake -q operasi_hilang_error.adb
text Keluaran
operasi_hilang_error.adb:10:15: error: there is no applicable operator "<" for private type "T" defined at line 5
gnatmake: "operasi_hilang_error.adb" compilation error

Pesan error menyatakan tidak ada operator < yang berlaku untuk tipe formal T, karena kendala is private tidak memuat operasi perbandingan. Perbaikannya adalah menambahkan parameter formal subprogram yang mewakili operator perbandingan tersebut, lalu mengisinya saat instansiasi. Pola with function semacam ini dibahas pada topik tentang parameter formal.

Ringkasan#

  • Subprogram generik dideklarasikan dengan bagian generic yang memuat parameter formal, dan deklarasinya tidak menghasilkan kode yang dapat dipanggil hingga diinstansiasi.
  • Instansiasi dengan is new menghasilkan subprogram nyata per tipe aktual, dan setiap salinan memeriksa tipe secara terpisah.
  • Kendala is private hanya menjamin penugasan dan salinan, sehingga operator lain atas tipe formal memicu error waktu kompilasi.

Referensi#