Pemrograman generik Ada ── menulis kontrak dengan tipe, reuse tanpa biaya runtime

· Diperbarui pada: · · Ada, Programming Language, Generics, Type System, Static Typing, Contract Model, Zero-Cost Abstraction, GNAT, Alire, Keandalan tinggi, Reuse kode

Riwayat revisi (1 pembaruan, terakhir pada 31 Aug 2026)

Catatan perubahan yang dilakukan pada artikel ini. Jika versi sebelumnya telah diarsipkan, versi itu tetap dapat dibaca melalui tautan permanen dengan DOI.

Diterjemahkan ulang sebagai terjemahan lengkap dari naskah Jepang. Versi bahasa Indonesia sebelumnya adalah ringkasan yang hanya memindahkan sebagian naskah, sehingga bagian, tabel, gambar Mermaid, keterangan gambar, dan FAQ tidak ada. Semuanya dipulihkan sesuai naskah Jepang, dan klaim teknisnya sama dengan versi Jepang.
Publikasi pertama
Mengutip artikel ini(DOI (arsip terdaftar): 10.5281/zenodo.22174049)

DOI di bawah mengarah ke versi yang telah diarsipkan sebelumnya dan mungkin berbeda dari teks saat ini. Gunakan URL halaman ini untuk merujuk teks saat ini.

Go Komura (2026). Pemrograman generik Ada ── menulis kontrak dengan tipe, reuse tanpa biaya runtime. KomuraSoft LLC. https://comcomponent.com/id/blog/ada-generic-programming/

DOI (arsip terdaftar)
10.5281/zenodo.22174049
DOI (versi terakhir yang didaftarkan)
10.5281/zenodo.22174050

1. Pendahuluan ── bukan “menerima apa pun”, melainkan “apa yang dijanjikan”

Ketika menulis kode yang dapat di-reuse dalam bahasa bertipe statis, dilema yang sama segera muncul. Stack yang ditulis untuk integer ingin dipakai juga untuk string. Pemrosesan statistik yang sama ingin dipakai juga pada array floating-point. Logika pengurutan menaik ingin dipakai juga untuk pengurutan menurun. Namun jika kode yang sama disalin per tipe, perbaikan mudah terlewat. Sebaliknya, desain yang menerima apa pun lewat void* atau cast meruntuhkan type-safety.

Jawaban Ada adalah generic (generic units).

Generic Ada bukan sekadar substitusi teks. Ia menerima tipe, nilai, subprogram, bahkan paket itu sendiri sebagai parameter formal, dan tipenya diperiksa secara statis pada saat instansiasi. Dengan kata lain, alih-alih meneliti di runtime apakah tipe ini benar-benar aman, ini adalah mekanisme yang pada waktu kompilasi menetapkan apakah komponen ini memenuhi kontrak tersebut.

Pemrosesan yang ingin di-reuseBagaimana cara reuse-nya?Salin dan tempelvoid* / Object / castGeneric AdaPerbaikan mudah terlewatError runtime atau rusaknya tipe mudah terjadiType-safePemeriksaan waktu kompilasiTanpa dispatch tambahan di runtime

Artikel ini menata pemrograman generik Ada menurut alur berikut.

  • Subprogram generik
  • Paket generik
  • Parameter tipe, parameter nilai, dan parameter subprogram
  • Kategori tipe seperti private, range <>, digits <>
  • Contoh implementasi pengurutan, stack, pemrosesan statistik, Count_If, dan penyimpanan kunci-nilai
  • Generic tingkat tinggi lewat parameter paket formal
  • Contract model Ada dan gagasan desain praktis

1.1 Pembaca sasaran dan apa yang didapat dari artikel ini

Artikel ini ditujukan kepada pembaca seperti berikut.

  • Yang punya pengalaman memakai template C++, generics C# atau Java, atau generics Rust
  • Yang belum akrab dengan sintaksis Ada, tetapi tertarik pada gagasan desain “menulis kontrak dengan tipe”
  • Yang sedang menimbang pedoman desain komponen reuse di bidang pemeliharaan jangka panjang, embedded, atau keandalan tinggi

Dari sisi sintaksis Ada, cukup mengenal paket (pemisahan spesifikasi .ads dan body .adb) serta mode parameter in / out / in out untuk mengikuti bacaan. Jika dua hal itu masih terasa goyah, membaca dulu “Daya tarik bahasa Ada” akan mempercepat pemahaman.

Yang didapat bukan sintaksis Ada itu sendiri, melainkan disiplin desain: menuliskan sebagai tipe, sebelum implementasi, apa yang dituntut sebuah komponen yang dapat di-reuse. Gagasan ini dapat langsung diterapkan juga saat memutuskan seberapa jauh menulis batasan interface di C# atau concepts di C++20.

1.2 Panduan membaca ── tidak perlu membaca semuanya

Artikel ini terdiri dari 20 bab. Tanpa harus dibaca berurutan, bab dapat dipetik sesuai tujuan sebagai berikut.

Tujuan Bab yang dibaca
Ingin menangkap gagasannya saja dalam waktu singkat Bab 4 (model dasar) → bab 6 (subprogram generik minimum) → bab 13 (contract model)
Ingin menulis komponen sendiri Bab 4 → bab 6 → bab 7 (paket generik) → bab 8 (injeksi perilaku) → bab 9 (kategori tipe)
Hanya butuh pedoman keputusan desain Bab 13 → bab 14 (apa yang dijadikan generic) → bab 15 (titik tersandung) → bab 17 (daftar periksa)
Ingin langsung praktik Siapkan lingkungan di bab 3, lalu mulai dari sampel jadi di bab 6 dan 7

Rute terpendek adalah tiga bab: 4, 6, dan 13. Tiga bab itu merangkum inti: parameter formal, instansiasi, dan contract model. Bab 5 dan 9 adalah daftar parameter formal, jadi cukup dibaca sebagai kamus ketika dibutuhkan.

Tema ini merupakan kelanjutan serial blog ini: “Daya tarik bahasa Ada”, “Pengantar verifikasi formal dengan SPARK”, “Konkurensi yang aman”, dan “Sistem real-time”. Gagasan Ada yang “berbicara desain lewat tipe” didalami dari sudut generic.

Pada diagram, garis utuh menunjukkan relasi yang selalu berlaku dan garis putus-putus menunjukkan relasi bersyarat (syaratnya ada pada penjelasan masing-masing relasi di halaman rincian). Daftar lengkap relasi (total 21, beserta bukti dan tingkat kepastian) serta definisi konsep utama dikumpulkan di halaman rincian peta pengetahuan (dalam bahasa Jepang). Data: JSON-LD / Turtle

2. Peta artikel ini

Pertama, tangkap gambaran keseluruhan lewat diagram. Memahami generic Ada hanya sebagai “fitur yang menerima tipe sebagai argumen” adalah pandangan yang terlalu sempit. Pada praktiknya, sesuai unit yang ingin di-reuse, subprogram, paket, parameter subprogram, parameter nilai, dan parameter paket formal dikombinasikan.

Ada GenericsGeneric SubprogramSwapCount_IfSortGeneric PackageStackStatisticsKV StoreFormal ParametersTypeprivatelimited privaterange boxmod boxdigits boxdelta boxdiscrete boxObjectMax_SizeThresholdSubprogramLess functionEquals functionPredicatePackagewith package P is newGenericDesign IdeasContract ModelStatic CheckingZero-Cost AbstractionSeparate Specification andBody

box pada diagram merujuk pada <> (box compound delimiter) di Ada. Mermaid tidak dapat menggambar <> secara langsung, jadi hanya di dalam diagram ia ditulis box.

Cara membaca artikel ini sederhana. Bab-bab awal mengikuti sintaksis; bab-bab kemudian membahas keputusan desain. Bagi yang baru membaca Ada, jangan dulu berusaha menghafal sintaksis halus. Perhatikan “apa yang dijadikan parameter formal” dan “operasi apa yang diizinkan pada parameter formal itu”.

2.1 Kamus mini istilah

Kata-kata yang akan muncul berulang dijelaskan dulu dalam pasangan Indonesia–Inggris. Literatur Ada berbahasa Jepang sering memakai istilah sōshō untuk generic. Artikel ini memakai subprogram generik dan paket generik, dengan makna yang sama.

Notasi di artikel ini English Arti
Unit generik generic unit Deklarasi yang dimulai dengan generic. Sebutan gabungan untuk subprogram generik dan paket generik
Parameter formal generic formal parameter Argumen di sisi penerima, ditulis antara generic dan body deklarasi. Berupa tipe, nilai, subprogram, atau paket
formal part generic formal part Bagian tempat parameter formal disusun. Boleh dibaca sebagai “tempat menulis kontrak”
Parameter aktual generic actual parameter Tipe, nilai, subprogram, atau paket yang benar-benar diteruskan pada saat instansiasi
Instansiasi instantiation Membuat subprogram atau paket biasa dari unit generik dengan new
box <> Nama tanda <> di Ada. Seperti pada range <>, menandakan bahwa tipe konkret ditentukan saat instansiasi
contract model contract model Cara Ada menulis body hanya dalam lingkup janji pada parameter formal, lalu memeriksa tipe body itu secara mandiri (bab 13)

Di tubuh artikel, notasi Ada ditulis range <>, digits <>, tetapi hanya di dalam diagram ditulis range box, digits box. Mermaid tidak dapat menggambar label yang berisi <>; maknanya sama dengan di tubuh artikel. Seperti pada tabel di atas, Ada sendiri menyebut <> sebagai box, jadi notasi diagram tidak menyimpang dari istilah Ada.

3. Lingkungan eksekusi dan cara kompilasi

Kode di artikel ini mengasumsikan GNAT 15.x atau lebih baru. GNAT adalah kompilator Ada yang representatif dan dapat dipasang dari Alire. Alire adalah manajer paket Ada / SPARK, dan juga dipakai untuk mengelola toolchain serta build.

gnat --version
# GNAT 15.2.1

Pasang GNAT dari Alire (manajer paket Ada) dengan alr install gnat_native gprbuild, lalu masukkan ke PATH.

Sampel yang dibahas di artikel ini diasumsikan diletakkan di repositori sebagai berikut.

ada-generic-programming/src/snippets/01_swap.ada02_stack.ada03_sort.ada04_statistics.ada05_filter.ada06_kv_store.adaREADME.md

Sampel yang merangkum beberapa unit kompilasi dalam satu berkas dipotong dulu dengan gnatchop, lalu di-build dengan gnatmake.

mkdir work
cd work
gnatchop ../src/snippets/01_swap.ada
gnatmake -gnata swap_demo
./swap_demo

-gnata adalah opsi untuk mengaktifkan assertion. Tidak wajib hanya untuk memakai generic, tetapi pada sampel pembelajaran memudahkan pemeriksaan kontrak dan kondisi batas.

Berkas eksekusignatmakegnatchopPengembangBerkas eksekusignatmakegnatchopPengembangmenyerahkan satu berkas .adamembagi menjadi .ads / .adb / maingnatmake -gnata mainbind dan link sampai selesai./mainhasil eksekusi

4. Model dasar generic Ada

Generic Ada paling mudah dipahami sebagai tiga langkah berikut.

  1. Menulis unit generik
  2. Menulis parameter formal di bagian generic
  3. Di sisi pemakai, menginstansiasi dengan new
deklarasi genericparameter formalbody generikinstansiasi dengan newdipakai sebagai subprogram atau paket biasatipenilaisubprogrampaket

Misalnya, jika pemrosesan menukar dua nilai dijadikan generic, hanya tipenya yang perlu dijadikan parameter formal.

generic
   type Element is private;
procedure Generic_Swap (A, B : in out Element);

Pada titik ini Generic_Swap belum dapat dipanggil. Ini adalah “templat pemrosesan tukar yang dapat dipakai untuk tipe Element mana pun”. Baru setelah diberi tipe konkret ia menjadi prosedur biasa.

procedure Swap_Integer is new Generic_Swap (Integer);

Hubungannya dalam diagram adalah sebagai berikut.

meneruskan Integermeneruskan Charactermeneruskan My_RecordGeneric_Swaptype Element is privateSwap_IntegerSwap_CharacterSwap_My_Recordmenukar variabel Integermenukar variabel Charactermenukar variabel My_Record

Yang penting: body templat hanya ditulis dengan operasi yang boleh dipakai pada Element. Jika dideklarasikan type Element is private;, operasi dasar seperti assignment dan perbandingan kesetaraan boleh dipakai, tetapi perbandingan urutan dan aritmetika tidak. Dengan kata lain, deklarasi generic itu sendiri menyatakan “apa yang boleh diasumsikan komponen ini”.

5. Jenis parameter formal ── kosakata generic Ada

Yang dapat diterima generic Ada bukan hanya tipe. Inilah perbedaan besar dibanding generics C# atau Java pada umumnya.

generic formal parametersparameter tipeparameter objek / nilaiparameter subprogramparameter pakettype Element is privatetype Index is boxtype Real is digits boxMax_Size : PositiveDefault_Value : Elementwith function Less...with procedure Put ...with package P is new ...

box pada diagram merujuk pada <> (box compound delimiter) di Ada. Mermaid tidak dapat menggambar <> secara langsung, jadi hanya di dalam diagram ia ditulis box.

Parameter formal yang representatif dapat diringkas dalam tabel berikut.

Jenis Contoh Arti
Parameter tipe type Element is private; Bentuk dasar yang menerima tipe non-limited yang definite
Parameter tipe limited type Element is limited private; Menerima juga tipe yang tidak dapat disalin
Tipe diskret type Index is (<>); Tipe yang dapat dipakai sebagai indeks array, misalnya integer atau enumerasi
Integer bertanda type Count is range <>; Dapat mengasumsikan aritmetika integer seperti +, -, dan perbandingan urutan
Integer modular type Word is mod <>; Menangani operasi bit atau integer modular
Tipe floating-point type Real is digits <>; Float, Long_Float, tipe floating-point buatan pengguna, dan sebagainya
Tipe fixed-point type Money is delta <>; Menangani aritmetika fixed-point
Parameter nilai Max_Size : Positive; Mengunci ukuran, ambang, dan sejenisnya per instance
Subprogram with function Predicate (...) return Boolean; Menginjeksikan perilaku seperti fungsi perbandingan atau predikat
Paket with package P is new Some_Generic (<>); Menerima paket generik yang sudah diinstansiasi sebagai komponen

Karena kosakata ini ada, di Ada menjadi natural untuk menulis “hanya menerima tipe yang mampu operasi ini”, bukan “menerima apa pun lalu melakukan hal berbahaya di dalamnya”.

6. Subprogram generik ── memahami konfigurasi minimum lewat Generic_Swap

Sebagai contoh pertama, lihat Generic_Swap yang menukar dua variabel bertipe apa pun.

6.1 Spesifikasi

generic
   type Element is private;
procedure Generic_Swap (A, B : in out Element);

Bagian setelah generic adalah parameter formal. Di sini diterima sebuah tipe bernama Element. is private berarti, dari sudut pandang body generik, representasi internal tipe itu tidak diketahui.

Dari deklarasi ini dua hal terlihat.

  • Generic_Swap dapat dipakai untuk tipe Element mana pun
  • Body tidak bergantung pada struktur internal Element maupun pada perbandingan urutan

6.2 Body

procedure Generic_Swap (A, B : in out Element) is
   Temp : constant Element := A;
begin
   A := B;
   B := Temp;
end Generic_Swap;

Body ini hanya memakai assignment pada Element. Tidak memakai A < B maupun A + B. Karena itu, selama tipenya dapat di-assign — Integer, Character, tipe record, tipe enumerasi, dan sebagainya — ia dapat dipakai secara natural.

Setelah pemanggilanSebelum pemanggilanA = 20B = 10A = 10B = 20Temp = A

6.3 Instansiasi

Di sisi pemakai, new dipakai.

procedure Swap_Int  is new Generic_Swap (Integer);
procedure Swap_Char is new Generic_Swap (Character);

Dengan itu Swap_Int dan Swap_Char dapat dipanggil sebagai prosedur biasa.

with Ada.Text_IO; use Ada.Text_IO;

procedure Swap_Demo is
   generic
      type Element is private;
   procedure Generic_Swap (A, B : in out Element);

   procedure Generic_Swap (A, B : in out Element) is
      Temp : constant Element := A;
   begin
      A := B;
      B := Temp;
   end Generic_Swap;

   procedure Swap_Int is new Generic_Swap (Integer);

   X : Integer := 10;
   Y : Integer := 20;
begin
   Put_Line ("Before: X=" & Integer'Image (X) & ", Y=" & Integer'Image (Y));
   Swap_Int (X, Y);
   Put_Line ("After : X=" & Integer'Image (X) & ", Y=" & Integer'Image (Y));
end Swap_Demo;

Gambaran hasil eksekusi adalah sebagai berikut.

Before: X= 10, Y= 20
After : X= 20, Y= 10

Variabel Float tidak dapat diteruskan ke Swap_Int (X, Y);. Swap_Int adalah prosedur biasa yang sudah diinstansiasi untuk Integer. Generic lebih mudah dipahami bukan sebagai “lubang yang menelan apa pun”, melainkan sebagai “mekanisme yang membuat benda konkret yang aman per tipe”.

7. Paket generik ── menjadikan tipe dan nilai sebagai parameter

Jika yang ingin di-reuse bukan satu subprogram, melainkan kumpulan operasi plus status internal, pakailah paket generik. Contoh representatifnya adalah stack.

Pada stack, logika dasarnya sama; yang berubah hanya tipe elemen dan ukuran maksimum.

Generic_StackElement_TypeMax_SizePush / Pop / Size / Is_Empty / Is_FullInt_StackElement=IntegerMax_Size=5Float_StackElement=FloatMax_Size=3String_StackElement=Unbounded_StringMax_Size=20

7.1 Spesifikasi

generic
   type Element_Type is private;
   Max_Size : Positive;
package Generic_Stack is
   procedure Push (Item : Element_Type);
   function Pop return Element_Type;
   function Is_Empty return Boolean;
   function Is_Full  return Boolean;
   function Size return Natural;

   Stack_Overflow  : exception;
   Stack_Underflow : exception;
end Generic_Stack;

Di sini dipakai dua jenis parameter formal.

  • Element_Type adalah parameter tipe
  • Max_Size adalah parameter nilai

Max_Size bertipe Positive, jadi instansiasi dengan ukuran 0 atau kurang tidak dapat dilakukan. Seperti ini, parameter nilai pun dapat membawa batasan tipe.

7.2 Body

package body Generic_Stack is
   subtype Index_Type is Positive range 1 .. Max_Size;
   type Storage_Type is array (Index_Type) of Element_Type;

   Data : Storage_Type;
   Top  : Natural := 0;

   procedure Push (Item : Element_Type) is
   begin
      if Top = Max_Size then
         raise Stack_Overflow;
      end if;

      Top := Top + 1;
      Data (Top) := Item;
   end Push;

   function Pop return Element_Type is
      Result : Element_Type;
   begin
      if Top = 0 then
         raise Stack_Underflow;
      end if;

      Result := Data (Top);
      Top := Top - 1;
      return Result;
   end Pop;

   function Is_Empty return Boolean is
   begin
      return Top = 0;
   end Is_Empty;

   function Is_Full return Boolean is
   begin
      return Top = Max_Size;
   end Is_Full;

   function Size return Natural is
   begin
      return Top;
   end Size;
end Generic_Stack;

Yang penting pada body paket ini: Data dan Top dibuat terpisah per instance.

package Int_Stack   is new Generic_Stack (Integer, 5);
package Float_Stack is new Generic_Stack (Float,   3);

Keduanya dibuat dari templat yang sama, tetapi tidak berbagi status internal.

Status Float_StackTopData : array FloatStatus Int_StackTopData : array IntegerGeneric_StackInt_StackFloat_Stack

7.3 Transisi status stack

Stack lebih mudah dipahami sebagai mesin status.

PushPush / PopPop mengambil elemen terakhirPush mencapai Max_SizePopPushPopEmptyNonEmptyFullOverflowUnderflow

7.4 Contoh pemakaian

with Ada.Text_IO; use Ada.Text_IO;

procedure Stack_Demo is
   generic
      type Element_Type is private;
      Max_Size : Positive;
   package Generic_Stack is
      procedure Push (Item : Element_Type);
      function Pop return Element_Type;
      function Is_Empty return Boolean;
      function Is_Full  return Boolean;
      function Size return Natural;
      Stack_Overflow  : exception;
      Stack_Underflow : exception;
   end Generic_Stack;

   package body Generic_Stack is
      subtype Index_Type is Positive range 1 .. Max_Size;
      type Storage_Type is array (Index_Type) of Element_Type;

      Data : Storage_Type;
      Top  : Natural := 0;

      procedure Push (Item : Element_Type) is
      begin
         if Top = Max_Size then
            raise Stack_Overflow;
         end if;

         Top := Top + 1;
         Data (Top) := Item;
      end Push;

      function Pop return Element_Type is
         Result : Element_Type;
      begin
         if Top = 0 then
            raise Stack_Underflow;
         end if;

         Result := Data (Top);
         Top := Top - 1;
         return Result;
      end Pop;

      function Is_Empty return Boolean is (Top = 0);
      function Is_Full  return Boolean is (Top = Max_Size);
      function Size     return Natural is (Top);
   end Generic_Stack;

   package Int_Stack is new Generic_Stack (Integer, 5);
begin
   Int_Stack.Push (10);
   Int_Stack.Push (20);
   Int_Stack.Push (30);

   Put_Line ("Size=" & Natural'Image (Int_Stack.Size));
   Put_Line ("Pop =" & Integer'Image (Int_Stack.Pop));
   Put_Line ("Pop =" & Integer'Image (Int_Stack.Pop));
   Put_Line ("Size=" & Natural'Image (Int_Stack.Size));
end Stack_Demo;

Build dan jalankan dengan langkah yang sama seperti bab 3.

gnatchop ../src/snippets/02_stack.ada
gnatmake -gnata stack_demo
./stack_demo
Size= 3
Pop = 30
Pop = 20
Size= 1

Ada satu spasi di kanan = karena 'Image pada tipe integer menempatkan satu karakter spasi di depan nilai non-negatif. Push dipanggil 3 kali dan Pop 2 kali, jadi Size terakhir menjadi 1. Jika Push dipanggil lagi saat stack penuh (dalam contoh ini 5 buah, yaitu Max_Size), Int_Stack.Stack_Overflow dilempar; jika Pop dipanggil saat kosong, Int_Stack.Stack_Underflow dilempar.

Paket generik dalam praktik sering bermanfaat untuk “kontainer kecil”, “buffer panjang tetap”, “ring buffer”, “antrean log”, dan “lapisan abstraksi perangkat keras”. Khususnya di Ada, desain yang mengunci ukuran secara statis sebagai parameter tipe atau nilai, alih-alih menggesernya di runtime, bekerja serasi dengan sistem berkeandalan tinggi.

8. Parameter subprogram formal ── menginjeksikan perilaku

Menerima tipe saja masih belum cukup untuk beberapa hal. Pada pengurutan, misalnya, selain tipe elemen dibutuhkan juga logika perbandingan: “mana yang diletakkan lebih dulu”.

Di Ada, fungsi perbandingan itu dapat dijadikan parameter formal generic.

Generic_Insertion_SortItem_TypeIndexItem_Arrayfungsi perbandinganmemakai perbandingan standarmeneruskan Greater untuk urutan menurunmeneruskan urutan sendiri

8.1 Spesifikasi

generic
   type Item_Type is private;
   type Index is (<>);
   type Item_Array is array (Index range <>) of Item_Type;
   with function "<" (Left, Right : Item_Type) return Boolean is <>;
procedure Generic_Insertion_Sort (Items : in out Item_Array);

Ada empat parameter formal.

  1. Item_Type: tipe elemen array
  2. Index: tipe indeks array
  3. Item_Array: tipe array yang sebenarnya
  4. "<": fungsi perbandingan

type Index is (<>); menerima tipe diskret. Bukan hanya integer, enumerasi pun dapat diterima. Yang khas Ada: indeks array tidak hanya Positive, tetapi juga enumerasi seperti Day.

is <> pada with function "<" ... is <>; berarti, jika parameter aktual dihilangkan, operator standar yang terlihat atau fungsi yang cocok dipakai. Jadi, pada tipe yang sudah punya < seperti Integer, fungsi perbandingan tidak perlu ditulis eksplisit.

8.2 Body

procedure Generic_Insertion_Sort (Items : in out Item_Array) is
   J   : Index;
   Key : Item_Type;
begin
   if Items'Length <= 1 then
      return;
   end if;

   for I in Index'Succ (Items'First) .. Items'Last loop
      Key := Items (I);
      J := I;

      while J > Items'First and then Key < Items (Index'Pred (J)) loop
         Items (J) := Items (Index'Pred (J));
         J := Index'Pred (J);
      end loop;

      Items (J) := Key;
   end loop;
end Generic_Insertion_Sort;

Insertion sort tidak cocok untuk array besar, tetapi cocok untuk menjelaskan generic. Cukup mengganti fungsi perbandingan, struktur loop yang sama dapat dipakai untuk urutan menaik maupun menurun.

YesNoNoYesArray belum terurutAmbil Key dari kiri ke kananKey lebih depan dari elemen sebelumnya?Geser elemen sebelumnya ke kananSisipkan KeySudah sampai akhir?Array terurut

8.3 Membuat urutan menaik dan menurun dari body yang sama

type Int_Array is array (Positive range <>) of Integer;

procedure Sort_Asc is new Generic_Insertion_Sort
  (Item_Type  => Integer,
   Index      => Positive,
   Item_Array => Int_Array);

function Greater (Left, Right : Integer) return Boolean is
  (Left > Right);

procedure Sort_Desc is new Generic_Insertion_Sort
  (Item_Type  => Integer,
   Index      => Positive,
   Item_Array => Int_Array,
   "<"        => Greater);

Sort_Asc memakai < standar. Sort_Desc mengganti fungsi perbandingan dengan "<" => Greater.

99, 3, 47, 12Sort_Ascperbandingan standarSort_DescGreater sebagai fungsi perbandingan3, 12, 47, 9999, 47, 12, 3

Mekanisme ini dekat dengan desain C++ yang meneruskan function object perbandingan sebagai argumen templat, atau desain Rust yang menuntut urutan lewat trait bound. Namun di Ada, yang dituliskan secara eksplisit sebagai parameter subprogram formal adalah “teruskan fungsi berbentuk ini”.

8.4 Contoh eksekusi

Mengikuti struktur bab 18, sisi pemanggil jika Generic_Insertion_Sort diletakkan di berkas terpisah (generic_insertion_sort.ads / .adb) adalah sebagai berikut.

with Ada.Text_IO;            use Ada.Text_IO;
with Generic_Insertion_Sort;

procedure Sort_Demo is
   type Int_Array is array (Positive range <>) of Integer;

   function Greater (Left, Right : Integer) return Boolean is
     (Left > Right);

   procedure Sort_Asc is new Generic_Insertion_Sort
     (Item_Type  => Integer,
      Index      => Positive,
      Item_Array => Int_Array);

   procedure Sort_Desc is new Generic_Insertion_Sort
     (Item_Type  => Integer,
      Index      => Positive,
      Item_Array => Int_Array,
      "<"        => Greater);

   procedure Show (Label : String; Items : Int_Array) is
   begin
      Put (Label);
      for V of Items loop
         Put (Integer'Image (V));
      end loop;
      New_Line;
   end Show;

   Asc  : Int_Array := (99, 3, 47, 12);
   Desc : Int_Array := (99, 3, 47, 12);
begin
   Sort_Asc (Asc);
   Sort_Desc (Desc);
   Show ("Asc :", Asc);
   Show ("Desc:", Desc);
end Sort_Demo;
gnatchop ../src/snippets/03_sort.ada
gnatmake -gnata sort_demo
./sort_demo
Asc : 3 12 47 99
Desc: 99 47 12 3

Dapat dipastikan bahwa dua prosedur yang dibuat dari body Generic_Insertion_Sort yang sama menjadi urutan terbalik hanya dengan mengganti fungsi perbandingan. Spasi di depan elemen berasal dari 'Image tipe integer yang menempatkan satu karakter spasi di depan nilai non-negatif.

9. Kategori tipe ── menulis kontrak yang lebih konkret daripada private

type T is private; nyaman, tetapi tidak berarti segala operasi boleh. Pada tipe private, aritmetika empat operasi atau perbandingan urutan tidak dapat dianggap tersedia. Karena itu Ada memungkinkan kategori ditentukan pada parameter tipe formal.

Formal Typeprivatelimited privatediscrete box: tipe diskretrange box: integer bertandamod box: integer modulardigits box: floating-pointdelta box: fixed-pointtipe accesstipe enumerasitipe integerFloatLong_Floattipe floating-point buatan pengguna

box pada diagram merujuk pada <> (box compound delimiter) di Ada. Mermaid tidak dapat menggambar <> secara langsung, jadi hanya di dalam diagram ia ditulis box.

9.1 Apa gunanya menentukan kategori

Misalnya, untuk menghitung rata-rata atau varians dibutuhkan penjumlahan, pengurangan, perkalian, dan pembagian. Tipe private tidak dapat mengasumsikan operasi-operasi itu. Karena itu, batasi ke tipe floating-point.

generic
   type Real is digits <>;
   type Real_Array is array (Positive range <>) of Real;
package Generic_Statistics is
   function Mean (Values : Real_Array) return Real;
   function Variance (Values : Real_Array) return Real;
end Generic_Statistics;

Dengan type Real is digits <>; terlihat bahwa Real adalah tipe floating-point. Karena itu, di body generik +, -, *, /, dan sejenisnya dapat dipakai.

9.2 Body

package body Generic_Statistics is
   function Mean (Values : Real_Array) return Real is
      Sum : Real := 0.0;
   begin
      if Values'Length = 0 then
         return 0.0;
      end if;

      for V of Values loop
         Sum := Sum + V;
      end loop;

      return Sum / Real (Values'Length);
   end Mean;

   function Variance (Values : Real_Array) return Real is
      M   : constant Real := Mean (Values);
      Sum : Real := 0.0;
   begin
      if Values'Length = 0 then
         return 0.0;
      end if;

      for V of Values loop
         declare
            D : constant Real := V - M;
         begin
            Sum := Sum + D * D;
         end;
      end loop;

      return Sum / Real (Values'Length);
   end Variance;
end Generic_Statistics;

9.3 Dipakai dengan Float dan Long_Float

type Float_Array is array (Positive range <>) of Float;
type Long_Array  is array (Positive range <>) of Long_Float;

package Float_Stats is new Generic_Statistics (Float, Float_Array);
package Long_Stats  is new Generic_Statistics (Long_Float, Long_Array);

Pemrosesan statistik yang sama dapat di-reuse untuk tipe floating-point dengan ketelitian berbeda.

Generic_StatisticsReal is digits boxFloat_StatsLong_StatsMy_Real_StatsMean / Variance dengan FloatMean / Variance dengan Long_FloatMean / Variance dengan Real buatan pengguna

9.4 Contoh eksekusi

with Ada.Text_IO;        use Ada.Text_IO;
with Ada.Float_Text_IO;  use Ada.Float_Text_IO;
with Generic_Statistics;

procedure Statistics_Demo is
   type Float_Array is array (Positive range <>) of Float;

   package Float_Stats is new Generic_Statistics (Float, Float_Array);

   Samples : constant Float_Array := (1.0, 2.0, 3.0, 4.0);
begin
   Put ("Mean     = ");
   Put (Float_Stats.Mean (Samples), Fore => 1, Aft => 3, Exp => 0);
   New_Line;

   Put ("Variance = ");
   Put (Float_Stats.Variance (Samples), Fore => 1, Aft => 3, Exp => 0);
   New_Line;
end Statistics_Demo;
gnatchop ../src/snippets/04_statistics.ada
gnatmake -gnata statistics_demo
./statistics_demo
Mean     = 2.500
Variance = 1.250

Ada.Float_Text_IO adalah pustaka standar yang menginstansiasi Ada.Text_IO.Float_IO untuk Float. Jika Exp => 0 ditentukan, notasi menjadi desimal biasa, bukan bentuk eksponen. Aft => 3 adalah jumlah digit di bagian pecahan. Perhatikan bahwa Variance ini adalah varians populasi (dibagi Values'Length), bukan varians sampel.

9.5 Penentuan kategori adalah “spesifikasi tingkat tipe”

Penentuan kategori bukan sekadar sintaksis untuk menenangkan kompilator. Bagi pembaca, ia menjadi spesifikasi yang menyampaikan “apa yang dituntut komponen ini”.

Pemrosesan yang ingin ditulis Tipe formal yang cocok Alasan
Tukar, simpan, ambil private Cukup dapat di-assign
Pengelolaan sumber daya yang tidak dapat disalin limited private Tidak mengasumsikan assignment
Indeks array, penelusuran status enumerasi (<>) First, Last, Succ, Pred dapat dipakai
Jumlah integer, pencacah range <> Dapat mengasumsikan aritmetika integer
Bitmask, pencacah sirkular mod <> Dapat mengasumsikan operasi modular
Rata-rata, varians, komputasi numerik digits <> Dapat mengasumsikan aritmetika floating-point
Jumlah uang, besaran kendali, dan ketelitian tetap lain delta <> Dapat mengasumsikan aritmetika fixed-point

10. Injeksi predikat ── menulis Count_If dengan cara Ada

Parameter subprogram formal tidak hanya untuk fungsi perbandingan; predikat pun dapat. Predikat adalah fungsi yang menerima nilai dan mengembalikan Boolean.

Peran yang dekat dengan Func<T, bool> di C#, Predicate<T> di Java, serta lambda atau function object di C++, di Ada dapat dinyatakan sebagai subprogram formal generic.

10.1 Spesifikasi

generic
   type Element is private;
   type Index is (<>);
   type Array_Type is array (Index range <>) of Element;
   with function Predicate (Item : Element) return Boolean;
function Generic_Count_If (Arr : Array_Type) return Natural;

Di sini Predicate tidak diberi is <>. Tidak ada fungsi predikat standar yang terlihat, jadi desainnya mewajibkan sisi pemakai meneruskannya.

10.2 Body

function Generic_Count_If (Arr : Array_Type) return Natural is
   Count : Natural := 0;
begin
   for Item of Arr loop
      if Predicate (Item) then
         Count := Count + 1;
      end if;
   end loop;

   return Count;
end Generic_Count_If;

Alur pemrosesannya sederhana.

TrueFalseArrayTelusuri tiap elemenPredicate(Item)?Naikkan CountTidak melakukan apa punKe elemen berikutnyaKembalikan Count

10.3 Menghitung genap dan menghitung di atas ambang

type Int_Array is array (Positive range <>) of Integer;

function Is_Even (N : Integer) return Boolean is
  (N mod 2 = 0);

function Is_Large (N : Integer) return Boolean is
  (N > 50);

function Count_Even is new Generic_Count_If
  (Element    => Integer,
   Index      => Positive,
   Array_Type => Int_Array,
   Predicate  => Is_Even);

function Count_Large is new Generic_Count_If
  (Element    => Integer,
   Index      => Positive,
   Array_Type => Int_Array,
   Predicate  => Is_Large);

Dari logika penelusuran yang sama, dua fungsi yang hanya berbeda syaratnya dapat dibuat.

Generic_Count_IfCount_EvenPredicate = Is_EvenCount_LargePredicate = Is_Large12, 7, 88, 3, 56, 91, 44, 19, 62jumlah bilangan genapjumlah yang lebih besar dari 50

Dalam contoh ini, penelusuran array, pengelolaan pencacah, dan pengembalian hasil semuanya sama. Yang diinjeksikan sebagai fungsi hanyalah “apa yang dihitung”. Inilah bentuk dasar desain tingkat tinggi di Ada.

10.4 Contoh eksekusi

with Ada.Text_IO;       use Ada.Text_IO;
with Generic_Count_If;

procedure Count_If_Demo is
   type Int_Array is array (Positive range <>) of Integer;

   function Is_Even (N : Integer) return Boolean is
     (N mod 2 = 0);

   function Is_Large (N : Integer) return Boolean is
     (N > 50);

   function Count_Even is new Generic_Count_If
     (Element    => Integer,
      Index      => Positive,
      Array_Type => Int_Array,
      Predicate  => Is_Even);

   function Count_Large is new Generic_Count_If
     (Element    => Integer,
      Index      => Positive,
      Array_Type => Int_Array,
      Predicate  => Is_Large);

   Data : constant Int_Array := (12, 7, 88, 3, 56, 91, 44, 19, 62);
begin
   Put_Line ("Even  =" & Natural'Image (Count_Even (Data)));
   Put_Line ("Large =" & Natural'Image (Count_Large (Data)));
end Count_If_Demo;
gnatchop ../src/snippets/05_filter.ada
gnatmake -gnata count_if_demo
./count_if_demo
Even  = 5
Large = 4

Dari 9 elemen Data, yang genap ada 5 buah: 12, 88, 56, 44, 62. Yang lebih besar dari 50 ada 4 buah: 88, 56, 91, 62.

11. Menyusun beberapa parameter ── penyimpanan kunci-nilai generik

Pada komponen nyata, jarang cukup satu parameter tipe. Tipe kunci dan nilai, cara membandingkan kunci, jumlah maksimum, dan syarat lain perlu digabung.

Di sini contohnya adalah penyimpanan kunci-nilai sederhana berukuran tetap.

Generic_KV_StoreKey_TypeValue_Typefungsi kesamaan kunciMax_EntriesPut / Get / Containspenyimpanan nilai konfigurasicache skala kecilkamus panjang tetap untuk embedded

11.1 Spesifikasi

generic
   type Key_Type is private;
   type Value_Type is private;
   with function "=" (Left, Right : Key_Type) return Boolean is <>;
   Max_Entries : Positive := 50;
package Generic_KV_Store is
   procedure Put (Key : Key_Type; Val : Value_Type);
   function Get (Key : Key_Type) return Value_Type;
   function Contains (Key : Key_Type) return Boolean;

   Key_Not_Found : exception;
   Store_Full    : exception;
end Generic_KV_Store;

Paket ini punya empat parameter formal.

Parameter Jenis Peran
Key_Type tipe Tipe kunci
Value_Type tipe Tipe nilai
"=" subprogram Penentuan kesamaan kunci
Max_Entries nilai Jumlah entri maksimum

Max_Entries diberi nilai default := 50. Karena itu, jika tidak ditentukan, hasilnya adalah store berisi 50 entri.

11.2 Body

package body Generic_KV_Store is
   subtype Index_Type is Positive range 1 .. Max_Entries;

   type Key_Array   is array (Index_Type) of Key_Type;
   type Value_Array is array (Index_Type) of Value_Type;
   type Used_Array  is array (Index_Type) of Boolean;

   Keys   : Key_Array;
   Values : Value_Array;
   Used   : Used_Array := (others => False);

   function Find_Index (Key : Key_Type) return Natural is
   begin
      for I in Index_Type loop
         if Used (I) and then Keys (I) = Key then
            return I;
         end if;
      end loop;

      return 0;
   end Find_Index;

   function Find_Free return Natural is
   begin
      for I in Index_Type loop
         if not Used (I) then
            return I;
         end if;
      end loop;

      return 0;
   end Find_Free;

   procedure Put (Key : Key_Type; Val : Value_Type) is
      Pos : Natural := Find_Index (Key);
   begin
      if Pos = 0 then
         Pos := Find_Free;

         if Pos = 0 then
            raise Store_Full;
         end if;

         Used (Pos) := True;
         Keys (Pos) := Key;
      end if;

      Values (Pos) := Val;
   end Put;

   function Get (Key : Key_Type) return Value_Type is
      Pos : constant Natural := Find_Index (Key);
   begin
      if Pos = 0 then
         raise Key_Not_Found;
      end if;

      return Values (Pos);
   end Get;

   function Contains (Key : Key_Type) return Boolean is
   begin
      return Find_Index (Key) /= 0;
   end Contains;
end Generic_KV_Store;

Implementasi ini memakai pencarian linear, jadi tidak ditujukan untuk data dalam jumlah besar. Namun bentuknya mudah dipakai di situasi yang mengutamakan panjang tetap, skala kecil, dan tanpa alokasi memori dinamis.

Keys/Values/UsedInstance Generic_KV_StoreSisi pemanggilKeys/Values/UsedInstance Generic_KV_StoreSisi pemanggilalt[kunci sudah ada][kunci baru]Put(Key, Value)Find_Index(Key)Values(Pos) := ValueFind_FreeKeys(Pos) := KeyValues(Pos) := ValueUsed(Pos) := TrueGet(Key)Find_Index(Key)PosValues(Pos)

11.3 Contoh instansiasi

with Ada.Text_IO;           use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Generic_KV_Store;

procedure KV_Demo is
   package Int_String_Store is new Generic_KV_Store
     (Key_Type    => Integer,
      Value_Type  => Unbounded_String,
      Max_Entries => 10);
begin
   Int_String_Store.Put (1, To_Unbounded_String ("Ada"));
   Int_String_Store.Put (2, To_Unbounded_String ("SPARK"));

   if Int_String_Store.Contains (1) then
      Put_Line ("1 => " & To_String (Int_String_Store.Get (1)));
   else
      Put_Line ("1 => (not found)");
   end if;

   --  Put pada kunci yang sudah ada menimpa nilai lama
   Int_String_Store.Put (1, To_Unbounded_String ("Ada 2022"));
   Put_Line ("1 => " & To_String (Int_String_Store.Get (1)));

   if Int_String_Store.Contains (9) then
      Put_Line ("9 => " & To_String (Int_String_Store.Get (9)));
   else
      Put_Line ("9 => (not found)");
   end if;
end KV_Demo;
gnatchop ../src/snippets/06_kv_store.ada
gnatmake -gnata kv_demo
./kv_demo
1 => Ada
1 => Ada 2022
9 => (not found)

Jika Get dipanggil tanpa memastikan keberadaan lewat Contains, Key_Not_Found dilempar ketika kunci tidak ada. Pilih salah satu: bercabang dengan Contains seperti contoh di atas, atau menulis exception handler.

"=" dihilangkan. Integer punya operator kesetaraan standar, dan is <> membuat operator itu yang dipakai.

Jika kuncinya misalnya string yang mengabaikan huruf besar-kecil, fungsi kesetaraan sendiri dapat diteruskan.

function Same_Key (Left, Right : Unbounded_String) return Boolean is
  (To_Lower (To_String (Left)) = To_Lower (To_String (Right)));

package String_Key_Store is new Generic_KV_Store
  (Key_Type    => Unbounded_String,
   Value_Type  => Integer,
   "="         => Same_Key,
   Max_Entries => 100);

12. Parameter paket formal ── menjadikan generic semakin modular

Generic Ada dapat menerima paket itu sendiri sebagai parameter formal. Dengan itu, “instance yang dibuat dari suatu paket generik” dapat diperlakukan sebagai masukan generic lain.

Generic_StackInt_StackGeneric_Stack_LoggerInt_Stack_Logger_Instance

12.1 Logger yang menerima stack

Misalnya, dibuat logger yang menerima instance Generic_Stack di atas, lalu menampilkan ukurannya.

generic
   with package Stack is new Generic_Stack (<>);
package Generic_Stack_Logger is
   procedure Print_Size;
end Generic_Stack_Logger;

Bodynya sebagai berikut.

with Ada.Text_IO; use Ada.Text_IO;

package body Generic_Stack_Logger is
   procedure Print_Size is
   begin
      Put_Line ("Stack size =" & Natural'Image (Stack.Size));
   end Print_Size;
end Generic_Stack_Logger;

Di sisi pemakai, stack dibuat dulu, lalu stack itu diteruskan ke logger.

package Int_Stack is new Generic_Stack
  (Element_Type => Integer,
   Max_Size     => 10);

package Int_Stack_Logger is new Generic_Stack_Logger
  (Stack => Int_Stack);

Desain seperti ini memungkinkan komponen generik dikombinasikan satu sama lain.

Tahap 2Tahap 1Int_Stack_LoggerGeneric_Stack_LoggerInt_StackGeneric_StackPrint_Size

Kegunaannya dekat dengan template template parameter di C++, tetapi di Ada dapat dinyatakan secara eksplisit: “terima instance paket generik ini”. Pada kode Ada berskala besar, cara ini nyaman ketika kontainer, algoritme, log, pemeriksaan, dan bantuan pengujian dipisah lalu dikombinasikan.

13. Contract Model ── gagasan terpenting generic Ada

Yang penting untuk memahami generic Ada adalah contract model.

Body generik harus ditulis hanya dengan operasi yang dijanjikan parameter formal. Misalnya, jika hanya type Element is private; yang dideklarasikan, < tidak dapat dipakai pada Element. Jika < ingin dipakai, nyatakan sebagai subprogram formal, atau buat kategori tipe lebih konkret.

generic formal partkontrakgeneric bodyimplementasi dalam lingkup kontrakpemeriksaan tipe pada body secara mandiriinstansiasiactual parameterstipe, fungsi, nilai aktualperiksa apakah parameter aktual memenuhi kontrakpaket / subprogram biasa

Desain ini melindungi bukan hanya pemakai generic, tetapi juga penulis generic.

13.1 Perbedaan tampilan dengan template C++

Template C++ kuat, tetapi secara historis punya sifat “error baru muncul setelah body templat diinstansiasi”. Hal itu diperbaiki oleh concepts di C++20, sedangkan generic Ada sejak awal adalah model yang menuliskan kontrak secara eksplisit.

C++ templatesekspresi kebutuhan dikonkretkan saat instantiationtulis template bodybatasan dapat dinyatakan dengan conceptsAdabody diperiksa dalam kontraktulis kontrak di formal partperiksa actual saat instantiation

Pada generics Java atau C#, pusat desainnya adalah relasi dengan tipe referensi, batasan, type erasure, dan representasi runtime. Generic Ada lebih condong ke gagasan membuat instance konkret pada waktu kompilasi.

Sudut pandang Ada C++ Java Rust
Cara menulis kontrak Menulis tipe, nilai, fungsi, paket di formal part templates / concepts Parameter tipe dan bounds trait bounds
Pemeriksaan body Diperiksa dalam kontrak parameter formal Inti pada konkretisasi saat instansiasi Diperiksa dalam bounds Diperiksa dalam trait bounds
Biaya runtime Resolusi statis sebagai dasar Pembangkitan statis sebagai dasar Terpengaruh type erasure Monomorphization sebagai dasar
Parameter nilai Ada Ada Terbatas const generics
Subprogram sebagai parameter formal Ada Dinyatakan lewat function object, dll. Lambda / interface tipe fungsi Closure / fungsi / trait
Paket sebagai parameter formal Ada Template template, dll. Tidak ada Terpisah dari struktur modul

Fitur bahasa rincinya berbeda-beda, tetapi ciri Ada adalah “menulis kontrak sebagai sintaksis lebih dulu”.

14. Keputusan desain di praktik ── apa yang sebaiknya dijadikan generic

Generic nyaman, tetapi tidak berarti segala sesuatu harus dijadikan generic. Di praktik, keputusan berikut membuat kegagalan lebih jarang.

YesNoYesNoYesNoYesNoTerdapat pemrosesan yang ingin di-reuseHanya tipenya yang berbeda?pertimbangkan parameter tipeUkuran atau ambang juga berbeda?tambahkan parameter nilaiPerilaku perbandingan atau penentuan yang berbeda?tambahkan subprogram formalIngin merangkum status internal dan API?paket generiksubprogram biasa sudah cukup

14.1 Situasi yang cocok untuk subprogram generik

Subprogram generik cocok untuk algoritme yang tidak punya status.

  • Swap
  • Sort
  • Count_If
  • Find
  • Transformasi semacam Map
  • Min / Max

Jika body algoritme pendek dan masukan serta keluaran jelas, subprogram lebih mudah dibaca daripada paket.

14.2 Situasi yang cocok untuk paket generik

Paket generik cocok jika bersama tipe ingin dibawa beberapa operasi dan status internal.

  • Stack panjang tetap
  • Ring buffer
  • Kamus skala kecil
  • Set pemrosesan statistik
  • Abstraksi I/O per perangkat
  • Kumpulan operasi untuk tipe numerik yang punya satuan

Khususnya di Ada, spesifikasi paket adalah API publik dan body paket adalah implementasi, sehingga paket generik dapat dipakai sebagai “templat modul yang type-safe”.

disembunyikanpackage specAPI publiksisi pemakaipackage bodyimplementasi internalgeneric formal partkontrak tipe, nilai, fungsi

14.3 Mulai dengan sedikit parameter formal

Jika parameter formal terlalu banyak, instansiasi menjadi sulit dibaca. Yang aman: mulai seminimal mungkin, lalu tambahkan ketika ada alasan untuk mengganti.

-- contoh yang mudah menjadi sulit dibaca
package X is new Generic_Foo
  (A, B, C, D, E, F, G);

-- named association agar niat tetap terbaca
package X is new Generic_Foo
  (Element_Type => Integer,
   Index_Type   => Positive,
   Buffer_Size  => 128,
   "<"          => Less);

Di Ada, named association dapat dipakai saat instansiasi. Titik penting desain generic muncul pada instansiasi, jadi pada kode praktis menulis secara named sering lebih mudah dipelihara.

15. Titik tersandung yang sering muncul

Generic Ada kuat, tetapi ada hal yang mudah membuat tersandung di awal.

15.1 Tipe private tidak dapat dibandingkan urutannya

Body seperti berikut tidak dapat ditulis.

generic
   type Element is private;
function Bad_Min (A, B : Element) return Element;

function Bad_Min (A, B : Element) return Element is
begin
   if A < B then      -- error di sini
      return A;
   else
      return B;
   end if;
end Bad_Min;

Element hanya dideklarasikan private, jadi < tidak selalu tersedia. Jika ingin membandingkan, tambahkan ke kontrak sebagai berikut.

generic
   type Element is private;
   with function "<" (Left, Right : Element) return Boolean is <>;
function Generic_Min (A, B : Element) return Element;
Ingin memakai perbandingan di bodytulis fungsi perbandingan di formal partkonfirmasi dapat dibandingkan saat instansiasihanya privateerror kompilasi di body generik

15.2 is <> bukan “inferensi otomatis untuk apa pun”

is <> nyaman, tetapi bukan sihir. Di titik instansiasi, operator atau subprogram yang cocok harus terlihat. Jika fungsi perbandingan sendiri diletakkan di paket lain, yang aman adalah with atau use yang tepat, atau meneruskannya secara eksplisit dengan nama.

procedure Sort_By_Age is new Generic_Insertion_Sort
  (Item_Type  => Person,
   Index      => Positive,
   Item_Array => Person_Array,
   "<"        => Younger_Than);

15.3 Exception juga berbeda per instance

Jika exception dideklarasikan di spesifikasi paket generik, tiap instance mendapat exception yang berbeda.

package Int_Stack   is new Generic_Stack (Integer, 5);
package Float_Stack is new Generic_Stack (Float, 3);

Dalam kasus ini, Int_Stack.Stack_Overflow dan Float_Stack.Stack_Overflow diperlakukan sebagai exception yang berbeda. Jika ingin diperlakukan sebagai exception bersama, pertimbangkan juga desain yang mendefinisikan exception di luar generic.

exception berbedaGeneric_Stackdeklarasi Stack_OverflowInt_Stack.Stack_OverflowFloat_Stack.Stack_Overflow

15.4 Ukuran kode dapat bertambah

Generic mudah menghindari referensi tak langsung yang berlebihan di runtime, tetapi karena instance dibuat per tipe, ukuran kode dapat bertambah jika jumlah instance banyak.

Ini trade-off yang juga muncul pada template C++ dan monomorphization di Rust. Pada pengembangan yang condong ke keandalan tinggi, embedded, dan real-time, gagasannya menjadi: mengurangi ketidakpastian runtime, dan sebagai gantinya mengelola ukuran hasil build.

satu body genericversi Integerversi Floatversi Long_Floatversi My_Typekode yang dihasilkanpenentuan tipe atau boxing di runtime lebih mudah dihindarijika instance banyak, perhatikan kenaikan ukuran

15.5 Kapan limited private sebaiknya dipakai

type Element is private; mengasumsikan assignment. Jika yang ditangani tidak ingin disalin — handle berkas, lock, handle perangkat — pertimbangkan limited private.

generic
   type Resource is limited private;
   with procedure Close (R : in out Resource);
procedure Generic_Use_And_Close (R : in out Resource);

Pada desain yang menangani tipe yang tidak dapat disalin, algoritme yang menerapkan prosedur, atau desain yang menyatakan acuan secara eksplisit, lebih aman daripada kontainer yang menyimpan nilai.

16. Kumpulan pola desain kecil

Dari sini, bentuk yang sering dipakai di praktik diringkas singkat.

16.1 Menyediakan Min hanya untuk nilai yang dapat dibandingkan

generic
   type Element is private;
   with function "<" (Left, Right : Element) return Boolean is <>;
function Generic_Min (A, B : Element) return Element;

function Generic_Min (A, B : Element) return Element is
begin
   if A < B then
      return A;
   else
      return B;
   end if;
end Generic_Min;
Elementfungsi perbandingan diperlukanGeneric_Minmengembalikan yang lebih kecil

16.2 Menjadikan ambang sebagai parameter nilai

generic
   type Count_Type is range <>;
   Threshold : Count_Type;
function Generic_Is_Over (Value : Count_Type) return Boolean;

function Generic_Is_Over (Value : Count_Type) return Boolean is
begin
   return Value > Threshold;
end Generic_Is_Over;

Parameter nilai cocok untuk nilai yang ingin dikunci sebagai sifat instance, bukan sebagai pengaturan runtime.

16.3 Menginjeksikan sarana keluaran

generic
   type Element is private;
   with procedure Put (Item : Element);
procedure Generic_Print_Twice (Item : Element);

procedure Generic_Print_Twice (Item : Element) is
begin
   Put (Item);
   Put (Item);
end Generic_Print_Twice;

Dengan bentuk ini, tujuan keluaran dapat diganti: keluaran standar, log, buffer pengujian, dan sebagainya.

Generic_Print_Twicemenerima Put sebagai subprogram formalkeluaran konsolkeluaran logbuffer untuk pengujian

16.4 Tidak mengunci tipe indeks array

Di Ada, tipe indeks array juga merupakan informasi tipe yang penting. Jangan mengunci ke Positive; jika perlu, jadikan tipe indeks juga parameter formal agar reuse naik.

generic
   type Element is private;
   type Index is (<>);
   type Array_Type is array (Index range <>) of Element;
procedure Generic_Clear (Arr : in out Array_Type; Value : Element);

Dengan desain ini, bukan hanya array berindeks Positive, array berindeks enumerasi pun dapat ditangani.

Index is discretePositive rangeenumerasi Dayenumerasi Statetipe integer sendiri

17. Daftar periksa agar API terasa khas Ada

Saat menulis generic, tinjau ulang dari sudut berikut agar lebih mudah dibaca.

Periksa desain genericApakah parameter formal seminimal mungkin?Apakah operasi yang dibutuhkan dinyatakan di formal part?Apakah kategori seperti private / range / digits tepat?Dapatkah diinstansiasi dengan mudah dibaca lewat named association?Apakah status dan exception per instance disadari?Apakah kenaikan ukuran kode dapat diterima?Apakah instance untuk pengujian disiapkan?

Dalam kalimat, ringkasannya sebagai berikut.

  • Operasi yang dipakai di body harus terlihat sebagai kontrak parameter formal.
  • Jika private cukup, pakai private. Jika aritmetika perlu, pakai range <> atau digits <>.
  • Perilaku yang berubah per tipe — perbandingan, hash, keluaran, konversi — jadikan subprogram formal.
  • Jika ukuran atau ambang adalah sifat instance, jadikan parameter nilai.
  • Jika punya status, pikirkan dulu paket generik; jika tidak punya status, pikirkan dulu subprogram generik.
  • Semakin banyak argumen instansiasi, semakin perlu named association.
  • Rancang dengan asumsi bahwa exception dan status internal independen per instance.

18. Contoh struktur keseluruhan sampel

Jika sampel dalam artikel dipisah ke berkas, struktur berikut membuatnya lebih mudah dibaca.

ada-generic-programmingsrcgenericsdemosgeneric_swap.adsgeneric_swap.adbgeneric_stack.adsgeneric_stack.adbgeneric_insertion_sort.adsgeneric_insertion_sort.adbgeneric_statistics.adsgeneric_statistics.adbgeneric_count_if.adsgeneric_count_if.adbgeneric_kv_store.adsgeneric_kv_store.adbswap_demo.adbstack_demo.adbsort_demo.adbstatistics_demo.adbcount_if_demo.adbkv_demo.adb

Untuk sampel artikel kecil, merangkum ke satu berkas lalu gnatchop juga nyaman, tetapi jika memikirkan praktik dan pemeliharaan jangka panjang, memisahkan spesifikasi .ads dan body .adb adalah struktur yang lebih khas Ada.

19. Ringkasan ── memutuskan batas reuse dengan tipe

Pemrograman generik Ada bukan sekadar “fitur menulis kode yang tidak bergantung pada tipe”. Esensinya justru menuliskan secara eksplisit, sebagai kontrak tipe, subprogram, dan nilai, apa yang dituntut komponen yang dapat di-reuse.

tulis kontraktulis body generikinstansiasi dengan meneruskan tipe, nilai, fungsipakai secara type-safereuse tanpa menyalin

Seperti yang dilihat artikel ini, generic Ada dapat menerima hal berikut sebagai parameter formal.

  • Tipe
  • Nilai
  • Subprogram
  • Paket

Lebih jauh, untuk tipe dapat ditentukan kategori yang cukup rinci: private, limited private, range <>, mod <>, digits <>, delta <>, (<>), dan sebagainya. Dengan itu, body generik tidak bergantung pada “operasi yang belum jelas tersedia atau tidak”, melainkan dapat diimplementasikan dengan aman hanya dengan operasi yang tertulis di kontrak.

Pada aset C atau C++ lama, reuse kadang memakai makro, void*, function pointer, atau percabangan tipe yang ditulis tangan. Generic Ada dapat mengganti banyak dari keperluan itu dengan bentuk yang type-safe dan mudah dibaca. Khususnya pada perangkat lunak pemeliharaan jangka panjang, embedded, real-time, dan keandalan tinggi, “desain yang memutuskan batas pada waktu kompilasi” ini punya nilai besar.

20. Area konsultasi terkait

KomuraSoft LLC menangani pengembangan aplikasi Windows, investigasi dan perbaikan aset yang ada, penataan batas COM / ActiveX / 32bit / 64bit, serta konsultasi teknis dan tinjauan desain. Selain desain yang condong ke pengetikan statis dan keandalan tinggi seperti Ada, bagaimana menata aset C/C++, C#, VB6, MFC, dan COM yang ada untuk diperpanjang umurnya atau dimigrasi juga merupakan tema yang dalam praktik sering berdekatan.

Referensi

Artikel terbaru dengan tag yang sama untuk mendalami topik-topik terdekat.

Halaman-halaman ini menempatkan topik dalam konteks layanan dan keputusan yang lebih luas.

Pertanyaan yang sering diajukan

Pertanyaan yang sering muncul dalam konsultasi tentang topik artikel ini.

Apa itu generic di Ada?
Mekanisme reuse yang menerima tipe, nilai, subprogram, bahkan paket itu sendiri sebagai parameter formal, lalu memeriksa tipenya secara statis pada saat instansiasi dengan new. Bukan sekadar substitusi teks: pada waktu kompilasi ia menetapkan apakah komponen ini memenuhi kontrak tersebut. Subprogram generik tidak dapat dipanggil hanya karena sudah dideklarasikan; baru setelah diberi tipe konkret dan diinstansiasi ia menjadi prosedur atau fungsi biasa yang dapat dipakai.
Apa bedanya generic Ada dengan template C++?
Ada sejak awal memakai contract model yang menuliskan kontrak secara eksplisit. Body generik hanya memakai operasi yang dijanjikan parameter formal, dan body itu sendiri diperiksa tipenya secara mandiri. Template C++ secara historis baru menampilkan error saat instansiasi; hal itu diperbaiki oleh concepts di C++20. Selain parameter nilai dan subprogram, Ada juga dapat menerima paket itu sendiri sebagai parameter formal, sehingga komponen generik dapat dikombinasikan satu sama lain.
Mengapa kategori tipe ditentukan pada parameter tipe formal Ada?
Agar operasi yang boleh dipakai di body menjadi kontrak yang eksplisit. Dengan type T is private, yang boleh diasumsikan hanyalah operasi dasar seperti assignment dan perbandingan kesetaraan; perbandingan urutan dan aritmetika tidak dapat dipakai. Jika perlu aritmetika integer, tentukan range <>; jika perlu aritmetika floating-point, tentukan digits <>; jika perlu operasi bit, tentukan mod <>. Penentuan kategori juga berfungsi sebagai spesifikasi tingkat tipe yang memberi tahu pembaca apa yang dituntut komponen ini.
Apa yang perlu diperhatikan pada generic Ada?
Karena instance dibuat per tipe, ukuran kode dapat bertambah jika jumlah instance banyak. Ini trade-off yang juga muncul pada template C++ dan monomorphization di Rust. Selain itu, exception yang dideklarasikan di spesifikasi paket generik menjadi exception yang berbeda per instance. Pedoman praktisnya: mulai dengan parameter formal seminimal mungkin dan tambahkan hanya ketika ada alasan untuk mengganti, serta gunakan named association ketika instansiasi punya banyak argumen.

Profil penulis

Halaman perkenalan penulis artikel.

Go Komura

Direktur KomuraSoft LLC

Berspesialisasi dalam pengembangan perangkat lunak Windows, konsultasi teknis, dan investigasi bug, terutama pada proyek dengan sistem yang sudah ada dan bug yang sulit direproduksi.

Kembali ke blog