Record berdiskriminan

Record berdiskriminan sebagai record varian dengan kendala yang diperiksa secara statis.

Daftar isi

Record berdiskriminan adalah record yang memiliki satu atau lebih discriminant (diskriminan), yaitu parameter yang menentukan bentuk record pada saat objek dibuat. Bagian case di dalam deklarasinya memuat varian bidang, dan tepat satu varian yang aktif untuk setiap objek. Topik ini membahas penulisan record berdiskriminan, subtipe berkendala yang membuat varian diketahui pada saat kompilasi, serta pemeriksaan yang dijalankan apabila bidang milik varian lain diakses. Contoh yang digunakan adalah bacaan sensor dengan tiga ragam pengukuran.

Diskriminan dan varian record#

Diskriminan dideklarasikan pada nama record seperti parameter, misalnya type Bacaan (Ragam : Ragam_Bacaan := Suhu). Bagian varian ditulis dengan case Ragam is diikuti cabang when untuk setiap nilai yang mungkin, dan setiap cabang memuat bidangnya sendiri. Bidang yang dideklarasikan sebelum bagian case dimiliki semua varian, sehingga bidang Tebal_Garis pada contoh berikut tersedia untuk varian Lingkaran maupun Persegi. Nilai bawaan pada diskriminan membuat objek tanpa kendala tetap memiliki varian, yaitu varian Suhu pada deklarasi Bacaan di atas.

ada Contoh: record berdiskriminan bentuk geometri
-- tidak-dijalankan
type Ragam_Bentuk is (Lingkaran, Persegi);

type Bentuk (Ragam : Ragam_Bentuk := Lingkaran) is record
   Tebal_Garis : Natural := 1;   -- bidang umum pada semua varian
   case Ragam is
      when Lingkaran =>
         Radius : Float := 0.0;
      when Persegi =>
         Sisi : Float := 0.0;
   end case;
end record;

Bagian varian mengikuti aturan case pada pernyataan biasa, yaitu seluruh nilai diskriminan harus tercakup pada cabang when. Pemilihan varian dilakukan sekali pada saat objek dibuat, dan nilai diskriminan tidak berubah selama masa hidup objek, kecuali objeknya tak terkendala dan seluruh nilainya diganti sekaligus melalui penugasan record. Disiplin ini membuat bentuk varian setiap objek dapat dipastikan sejak objek dibuat.

Kegunaan diskriminan tidak berhenti pada pemilihan varian. Diskriminan juga dapat mengatur batas array di dalam record, sehingga satu tipe dapat digunakan untuk objek dengan ukuran penyimpanan yang berbeda tanpa perlu mengulang deklarasinya. Pembahasan pada topik ini difokuskan pada pemilihan varian, karena bentuk penggunaan tersebut yang paling umum dijumpai.

Subtipe berkendala#

Subtipe berkendala menyatakan nilai diskriminan secara tetap, misalnya subtype Bacaan_Suhu is Bacaan (Suhu);. Kompilator mengetahui varian yang sah untuk setiap objek subtipe ini tanpa perlu memeriksa nilai saat program berjalan, sehingga akses ke bidang variannya bebas dari biaya pemeriksaan tambahan. Program berikut membandingkan objek berkendala dengan objek yang ragamnya ditentukan pada deklarasinya.

Kendala pada subtipe tidak terbatas pada satu nilai, karena rentang nilai diskriminan juga sah, misalnya kendala Ragam_Bacaan range Suhu .. Tekanan. Untuk kendala semacam itu pemeriksaan varian tetap diperlukan saat berjalan, karena nilai diskriminan yang tepat baru diketahui pada saat objek dibuat. Pemeriksaan statis yang paling teliti diperoleh dari kendala satu nilai sebagaimana contoh program berikut.

ada bacaan_sensor.adb
with Ada.Text_IO; use Ada.Text_IO;

procedure Bacaan_Sensor is

   type Ragam_Bacaan is (Suhu, Tekanan, Kelembapan);

   -- Record berdiskriminan: setiap varian memiliki bidangnya sendiri.
   type Bacaan (Ragam : Ragam_Bacaan := Suhu) is record
      Stasiun : Natural := 1;   -- bidang umum pada semua varian
      case Ragam is
         when Suhu =>
            Celsius : Float := 0.0;
         when Tekanan =>
            Kilo_Pascal : Float := 0.0;
         when Kelembapan =>
            Persen : Float := 0.0;
      end case;
   end record;

   -- Subtipe berkendala: varian yang sah diketahui saat kompilasi.
   subtype Bacaan_Suhu is Bacaan (Suhu);

   package Real_IO is new Ada.Text_IO.Float_IO (Float);
   use Real_IO;

   procedure Tampilkan (B : Bacaan) is
   begin
      Put ("Stasiun" & Natural'Image (B.Stasiun) & ": ");
      case B.Ragam is
         when Suhu =>
            Put ("suhu ");
            Put (B.Celsius, Fore => 0, Aft => 1, Exp => 0);
            Put_Line (" derajat Celsius");
         when Tekanan =>
            Put ("tekanan ");
            Put (B.Kilo_Pascal, Fore => 0, Aft => 1, Exp => 0);
            Put_Line (" kPa");
         when Kelembapan =>
            Put ("kelembapan ");
            Put (B.Persen, Fore => 0, Aft => 1, Exp => 0);
            Put_Line (" persen");
      end case;
   end Tampilkan;

   Ruang : Bacaan_Suhu := (Ragam => Suhu, Stasiun => 3, Celsius => 26.5);
   Umum  : Bacaan      := (Ragam => Tekanan, Stasiun => 5,
                           Kilo_Pascal => 101.3);

begin
   Tampilkan (Ruang);
   Tampilkan (Umum);
   Ruang.Celsius := 24.8;      -- bidang variannya sah untuk Bacaan_Suhu
   Tampilkan (Ruang);
end Bacaan_Sensor;
bash
# kompilasi program
gnatmake -q bacaan_sensor.adb
# jalankan program
./bacaan_sensor
text Keluaran
Stasiun 3: suhu 26.5 derajat Celsius
Stasiun 5: tekanan 101.3 kPa
Stasiun 3: suhu 24.8 derajat Celsius

Keluaran memperlihatkan dua bacaan dari dua varian yang berbeda, kemudian satu pembaruan pada varian suhu. Ruang bertipe Bacaan_Suhu, sehingga bidang varian yang tersedia hanyalah Celsius dan penugasan Ruang.Celsius := 24.8 dilakukan tanpa pemeriksaan ragam saat berjalan. Umum dideklarasikan sebagai Bacaan dengan agregat yang menyebut Ragam => Tekanan, sehingga variannya ditetapkan pada saat deklarasi dan bidang Kilo_Pascal tersedia untuk dibaca. Prosedur Tampilkan menerima kedua objek melalui parameter bertipe Bacaan yang tak terkendala, dan pemilihan bidang di dalamnya mengikuti nilai B.Ragam.

Pemeriksaan varian yang salah#

Membaca bidang milik varian yang tidak aktif merupakan pelanggaran, dan cara pelanggaran itu terdeteksi bergantung pada bentuk deklarasinya. Pada objek yang ragamnya tidak diketahui secara statis, pemeriksaan dilakukan saat program berjalan, sedangkan pada subtipe berkendala GNAT memberi peringatan sejak kompilasi. Berkas berikut sengaja membaca bidang Persen padahal diskriminan bawaannya Suhu.

ada uji_varian.adb
with Ada.Text_IO; use Ada.Text_IO;

procedure Uji_Varian is

   type Ragam_Bacaan is (Suhu, Tekanan, Kelembapan);

   type Bacaan (Ragam : Ragam_Bacaan := Suhu) is record
      Stasiun : Natural := 1;
      case Ragam is
         when Suhu =>
            Celsius : Float := 0.0;
         when Tekanan =>
            Kilo_Pascal : Float := 0.0;
         when Kelembapan =>
            Persen : Float := 0.0;
      end case;
   end record;

   Umum : Bacaan;   -- diskriminan bawaan Suhu

begin
   Put_Line ("Membaca bidang Persen pada varian Suhu");
   Put_Line ("Nilai:" & Float'Image (Umum.Persen));
end Uji_Varian;
bash
# kompilasi program
gnatmake -q uji_varian.adb
# jalankan program
./uji_varian
text Keluaran
Membaca bidang Persen pada varian Suhu

raised CONSTRAINT_ERROR : uji_varian.adb:23 discriminant check failed

Baris raised CONSTRAINT_ERROR pada keluaran berasal dari pemeriksa diskriminan yang dijalankan sebelum bidang Persen dibaca. Pemeriksaan ini aktif secara bawaan tanpa flag tambahan, sehingga pelanggaran varian selalu terdeteksi. Program berhenti karena exception tersebut tidak ditangkap, dan cara penangkapannya sama dengan penanganan Constraint_Error pada topik exception.

Kesalahan yang sama pada subtipe berkendala terdeteksi lebih awal. GNAT memberi dua peringatan pada waktu kompilasi, lalu pemeriksa run time tetap menegakkan kendalanya apabila program dijalankan.

ada uji_kendala_salah.adb
--  Berkas ini sengaja salah: bidang varian lain pada subtipe berkendala.
procedure Uji_Kendala_Salah is

   type Ragam_Bacaan is (Suhu, Tekanan, Kelembapan);

   type Bacaan (Ragam : Ragam_Bacaan := Suhu) is record
      Stasiun : Natural := 1;
      case Ragam is
         when Suhu =>
            Celsius : Float := 0.0;
         when Tekanan =>
            Kilo_Pascal : Float := 0.0;
         when Kelembapan =>
            Persen : Float := 0.0;
      end case;
   end record;

   subtype Bacaan_Suhu is Bacaan (Suhu);

   Ruang : Bacaan_Suhu;

begin
   Ruang.Persen := 40.0;   -- Persen milik varian Kelembapan
end Uji_Kendala_Salah;
bash
# kompilasi berkas yang sengaja salah
gnatmake -q uji_kendala_salah.adb
# jalankan program
./uji_kendala_salah
text Keluaran
uji_kendala_salah.adb:23:09: warning: component not present in type "Bacaan_Suhu" defined at line 18 [enabled by default]
uji_kendala_salah.adb:23:09: warning: Constraint_Error will be raised at run time [enabled by default]

raised CONSTRAINT_ERROR : uji_kendala_salah.adb:23 discriminant check failed

Dua baris peringatan menyebut komponen yang tidak tersedia pada tipe Bacaan_Suhu dan memperingatkan bahwa Constraint_Error akan dibangkitkan saat berjalan. Peringatan tersebut merupakan bukti pemeriksaan statis, karena kompilator membaca kendala diskriminan dari deklarasi subtipe dan menilai akses tersebut pasti gagal. Pada program yang dipelihara secara teratur, peringatan semacam ini diperlakukan sama dengan error dan dibereskan sebelum kode digabungkan.

Kesalahan umum#

  • Mengisi agregat tanpa nilai diskriminan pada tipe yang memiliki nilai bawaan. GNAT menolak deklarasi tersebut meskipun subtipe objeknya sudah berkendala. Perbaikannya, sebutkan diskriminan pada agregat, misalnya (Ragam => Suhu, Stasiun => 3, Celsius => 26.5).
  • Mengira diskriminan objek berkendala dapat diganti melalui penugasan record. Penugasan pada objek bertipe Bacaan_Suhu wajib mempertahankan nilai diskriminan yang sudah terkendala. Perbaikannya, deklarasikan objek sebagai tak terkendala apabila ragamnya memang perlu berganti selama program berjalan. Kendala yang berlaku adalah nilai diskriminan pada deklarasi subtipe, bukan nilai bidang lain yang kebetulan sama.
  • Mengakses bidang varian lain tanpa memeriksa diskriminan terlebih dahulu. Pemeriksa diskriminan membangkitkan Constraint_Error untuk akses semacam itu. Perbaikannya, pilih bidang melalui case B.Ragam sebagaimana prosedur Tampilkan pada contoh di atas.
  • Menganggap pemeriksaan statis membuat pemeriksaan run time tidak pernah dijalankan. GNAT tetap menegakkan kendala diskriminan saat berjalan sebagaimana keluaran pada bagian pemeriksaan varian. Perbaikannya, perlakukan peringatan kompilasi sebagai error yang wajib dibereskan, bukan sebagai pengganti pemeriksaan run time.

Ringkasan#

  • Record berdiskriminan menyimpan satu varian bidang sesuai nilai diskriminan, dan bidang sebelum bagian case dimiliki semua varian.
  • Subtipe berkendala seperti Bacaan (Suhu) memperjelas varian pada waktu kompilasi, sehingga GNAT dapat memperingatkan akses yang pasti gagal.
  • Akses bidang varian yang tidak aktif diperiksa pemeriksa diskriminan saat berjalan dan membangkitkan Constraint_Error.

Referensi#