Pemrograman generik Ada ── menulis kontrak dengan tipe, reuse tanpa biaya runtime
· Diperbarui pada: · Go Komura · 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.
flowchart LR
A[Pemrosesan yang ingin di-reuse] --> B{Bagaimana cara reuse-nya?}
B --> C[Salin dan tempel]
B --> D[void* / Object / cast]
B --> E[Generic Ada]
C --> C1[Perbaikan mudah terlewat]
D --> D1[Error runtime atau rusaknya tipe mudah terjadi]
E --> E1[Type-safe]
E --> E2[Pemeriksaan waktu kompilasi]
E --> E3[Tanpa 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.
mindmap
root((Ada Generics))
Generic Subprogram
Swap
Count_If
Sort
Generic Package
Stack
Statistics
KV Store
Formal Parameters
Type
private
limited private
range box
mod box
digits box
delta box
discrete box
Object
Max_Size
Threshold
Subprogram
Less function
Equals function
Predicate
Package
with package P is new Generic
Design Ideas
Contract Model
Static Checking
Zero-Cost Abstraction
Separate Specification and Body
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.
flowchart TB
R[ada-generic-programming/] --> S[src/]
S --> N[snippets/]
N --> A[01_swap.ada]
N --> B[02_stack.ada]
N --> C[03_sort.ada]
N --> D[04_statistics.ada]
N --> E[05_filter.ada]
N --> F[06_kv_store.ada]
R --> README[README.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.
sequenceDiagram
participant Dev as Pengembang
participant Chop as gnatchop
participant Build as gnatmake
participant Exe as Berkas eksekusi
Dev->>Chop: menyerahkan satu berkas .ada
Chop-->>Dev: membagi menjadi .ads / .adb / main
Dev->>Build: gnatmake -gnata main
Build-->>Dev: bind dan link sampai selesai
Dev->>Exe: ./main
Exe-->>Dev: hasil eksekusi
4. Model dasar generic Ada
Generic Ada paling mudah dipahami sebagai tiga langkah berikut.
- Menulis unit generik
- Menulis parameter formal di bagian
generic - Di sisi pemakai, menginstansiasi dengan
new
flowchart LR
G[deklarasi generic] --> F[parameter formal]
F --> B[body generik]
B --> I[instansiasi dengan new]
I --> U[dipakai sebagai subprogram atau paket biasa]
F --> F1[tipe]
F --> F2[nilai]
F --> F3[subprogram]
F --> F4[paket]
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.
flowchart TB
Template[Generic_Swap<br/>type Element is private] -->|meneruskan Integer| SwapInt[Swap_Integer]
Template -->|meneruskan Character| SwapChar[Swap_Character]
Template -->|meneruskan My_Record| SwapRecord[Swap_My_Record]
SwapInt --> ICall[menukar variabel Integer]
SwapChar --> CCall[menukar variabel Character]
SwapRecord --> RCall[menukar 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.
flowchart TB
P[generic formal parameters] --> T[parameter tipe]
P --> O[parameter objek / nilai]
P --> S[parameter subprogram]
P --> PKG[parameter paket]
T --> T1[type Element is private]
T --> T2[type Index is box]
T --> T3[type Real is digits box]
O --> O1[Max_Size : Positive]
O --> O2[Default_Value : Element]
S --> S1[with function Less...]
S --> S2[with procedure Put ...]
PKG --> P1[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_Swapdapat dipakai untuk tipeElementmana pun- Body tidak bergantung pada struktur internal
Elementmaupun 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.
flowchart LR
subgraph Before[Sebelum pemanggilan]
A1[A = 10]
B1[B = 20]
end
A1 --> T[Temp = A]
B1 --> A2[A = B]
T --> B2[B = Temp]
subgraph After[Setelah pemanggilan]
A2[A = 20]
B2[B = 10]
end
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.
flowchart TB
G[Generic_Stack] --> P1[Element_Type]
G --> P2[Max_Size]
G --> Ops[Push / Pop / Size / Is_Empty / Is_Full]
G --> I1[Int_Stack<br/>Element=Integer<br/>Max_Size=5]
G --> I2[Float_Stack<br/>Element=Float<br/>Max_Size=3]
G --> I3[String_Stack<br/>Element=Unbounded_String<br/>Max_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_Typeadalah parameter tipeMax_Sizeadalah 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.
flowchart LR
Template[Generic_Stack] --> IntStack[Int_Stack]
Template --> FloatStack[Float_Stack]
subgraph I[Status Int_Stack]
ITop[Top]
IData[Data : array Integer]
end
subgraph F[Status Float_Stack]
FTop[Top]
FData[Data : array Float]
end
IntStack --> I
FloatStack --> F
7.3 Transisi status stack
Stack lebih mudah dipahami sebagai mesin status.
stateDiagram-v2
[*] --> Empty
Empty --> NonEmpty: Push
NonEmpty --> NonEmpty: Push / Pop
NonEmpty --> Empty: Pop mengambil elemen terakhir
NonEmpty --> Full: Push mencapai Max_Size
Full --> NonEmpty: Pop
Full --> Overflow: Push
Empty --> Underflow: Pop
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.
flowchart LR
A[Generic_Insertion_Sort] --> T[Item_Type]
A --> I[Index]
A --> ARR[Item_Array]
A --> CMP[fungsi perbandingan]
CMP --> ASC[memakai perbandingan standar]
CMP --> DESC[meneruskan Greater untuk urutan menurun]
CMP --> CUSTOM[meneruskan 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.
Item_Type: tipe elemen arrayIndex: tipe indeks arrayItem_Array: tipe array yang sebenarnya"<": 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.
flowchart TB
Start[Array belum terurut] --> Pick[Ambil Key dari kiri ke kanan]
Pick --> Compare{Key lebih depan dari elemen sebelumnya?}
Compare -->|Yes| Shift[Geser elemen sebelumnya ke kanan]
Shift --> Compare
Compare -->|No| Insert[Sisipkan Key]
Insert --> Done{Sudah sampai akhir?}
Done -->|No| Pick
Done -->|Yes| End[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.
flowchart LR
Data[99, 3, 47, 12] --> A[Sort_Asc<br/>perbandingan standar]
Data --> D[Sort_Desc<br/>Greater sebagai fungsi perbandingan]
A --> AO[3, 12, 47, 99]
D --> DO[99, 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.
flowchart TB
FormalType[Formal Type] --> Private[private]
FormalType --> Limited[limited private]
FormalType --> Discrete[discrete box: tipe diskret]
FormalType --> Signed[range box: integer bertanda]
FormalType --> Modular[mod box: integer modular]
FormalType --> Float[digits box: floating-point]
FormalType --> Fixed[delta box: fixed-point]
FormalType --> Access[tipe access]
Discrete --> Enum[tipe enumerasi]
Discrete --> Int[tipe integer]
Float --> F1[Float]
Float --> F2[Long_Float]
Float --> F3[tipe 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.
flowchart LR
Stats[Generic_Statistics<br/>Real is digits box] --> FS[Float_Stats]
Stats --> LS[Long_Stats]
Stats --> MS[My_Real_Stats]
FS --> FCalc[Mean / Variance dengan Float]
LS --> LCalc[Mean / Variance dengan Long_Float]
MS --> MCalc[Mean / 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.
flowchart LR
Arr[Array] --> Loop[Telusuri tiap elemen]
Loop --> P{"Predicate(Item)?"}
P -->|True| Inc[Naikkan Count]
P -->|False| Skip[Tidak melakukan apa pun]
Inc --> Next[Ke elemen berikutnya]
Skip --> Next
Next --> Result[Kembalikan 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.
flowchart TB
G[Generic_Count_If] --> E[Count_Even<br/>Predicate = Is_Even]
G --> L[Count_Large<br/>Predicate = Is_Large]
Data[12, 7, 88, 3, 56, 91, 44, 19, 62] --> E
Data --> L
E --> ER[jumlah bilangan genap]
L --> LR[jumlah 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.
flowchart TB
KV[Generic_KV_Store] --> K[Key_Type]
KV --> V[Value_Type]
KV --> EQ[fungsi kesamaan kunci]
KV --> M[Max_Entries]
KV --> Ops[Put / Get / Contains]
Ops --> Use1[penyimpanan nilai konfigurasi]
Ops --> Use2[cache skala kecil]
Ops --> Use3[kamus 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.
sequenceDiagram
participant App as Sisi pemanggil
participant Store as Instance Generic_KV_Store
participant Data as Keys/Values/Used
App->>Store: Put(Key, Value)
Store->>Data: Find_Index(Key)
alt kunci sudah ada
Store->>Data: Values(Pos) := Value
else kunci baru
Store->>Data: Find_Free
Store->>Data: Keys(Pos) := Key
Store->>Data: Values(Pos) := Value
Store->>Data: Used(Pos) := True
end
App->>Store: Get(Key)
Store->>Data: Find_Index(Key)
Data-->>Store: Pos
Store-->>App: Values(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.
flowchart LR
GS[Generic_Stack] --> IS[Int_Stack]
IS --> Logger[Generic_Stack_Logger]
Logger --> Logged[Int_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.
flowchart TB
subgraph Layer1[Tahap 1]
T1[Generic_Stack] --> I1[Int_Stack]
end
subgraph Layer2[Tahap 2]
T2[Generic_Stack_Logger] --> I2[Int_Stack_Logger]
end
I1 --> T2
I2 --> API[Print_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.
flowchart TB
Spec[generic formal part<br/>kontrak] --> Body[generic body<br/>implementasi dalam lingkup kontrak]
Body --> Check1[pemeriksaan tipe pada body secara mandiri]
Spec --> Inst[instansiasi]
Actual[actual parameters<br/>tipe, fungsi, nilai aktual] --> Inst
Inst --> Check2[periksa apakah parameter aktual memenuhi kontrak]
Check2 --> Instance[paket / 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.
flowchart LR
subgraph Ada[Ada]
A1[tulis kontrak di formal part] --> A2[body diperiksa dalam kontrak]
A2 --> A3[periksa actual saat instantiation]
end
subgraph CPP[C++ templates]
C1[tulis template body] --> C2[ekspresi kebutuhan dikonkretkan saat instantiation]
C2 --> C3[batasan dapat dinyatakan dengan concepts]
end
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.
flowchart TB
Start[Terdapat pemrosesan yang ingin di-reuse] --> Q1{Hanya tipenya yang berbeda?}
Q1 -->|Yes| GType[pertimbangkan parameter tipe]
Q1 -->|No| Q2{Ukuran atau ambang juga berbeda?}
Q2 -->|Yes| GObject[tambahkan parameter nilai]
Q2 -->|No| Q3{Perilaku perbandingan atau penentuan yang berbeda?}
Q3 -->|Yes| GSubp[tambahkan subprogram formal]
Q3 -->|No| Q4{Ingin merangkum status internal dan API?}
Q4 -->|Yes| GPackage[paket generik]
Q4 -->|No| Normal[subprogram biasa sudah cukup]
14.1 Situasi yang cocok untuk subprogram generik
Subprogram generik cocok untuk algoritme yang tidak punya status.
SwapSortCount_IfFind- 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”.
flowchart LR
Spec[package spec<br/>API publik] --> User[sisi pemakai]
Body[package body<br/>implementasi internal] -.disembunyikan.-> User
Formal[generic formal part<br/>kontrak tipe, nilai, fungsi] --> Spec
Formal --> Body
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;
flowchart LR
Need[Ingin memakai perbandingan di body] --> Contract[tulis fungsi perbandingan di formal part]
Contract --> OK[konfirmasi dapat dibandingkan saat instansiasi]
Need --> NoContract[hanya private]
NoContract --> Error[error 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.
flowchart TB
Generic[Generic_Stack<br/>deklarasi Stack_Overflow] --> I[Int_Stack.Stack_Overflow]
Generic --> F[Float_Stack.Stack_Overflow]
I -.exception berbeda.-> F
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.
flowchart LR
Generic[satu body generic] --> I1[versi Integer]
Generic --> I2[versi Float]
Generic --> I3[versi Long_Float]
Generic --> I4[versi My_Type]
I1 --> Code[kode yang dihasilkan]
I2 --> Code
I3 --> Code
I4 --> Code
Code --> Pros[penentuan tipe atau boxing di runtime lebih mudah dihindari]
Code --> Cons[jika 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;
flowchart LR
T[Element] --> C[fungsi perbandingan diperlukan]
C --> M[Generic_Min]
M --> R[mengembalikan 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.
flowchart TB
Print[Generic_Print_Twice] --> Put[menerima Put sebagai subprogram formal]
Put --> Console[keluaran konsol]
Put --> Log[keluaran log]
Put --> Test[buffer 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.
flowchart LR
Index[Index is discrete] --> Positive[Positive range]
Index --> Day[enumerasi Day]
Index --> State[enumerasi State]
Index --> Slot[tipe integer sendiri]
17. Daftar periksa agar API terasa khas Ada
Saat menulis generic, tinjau ulang dari sudut berikut agar lebih mudah dibaca.
flowchart TB
C[Periksa desain generic] --> C1[Apakah parameter formal seminimal mungkin?]
C --> C2[Apakah operasi yang dibutuhkan dinyatakan di formal part?]
C --> C3[Apakah kategori seperti private / range / digits tepat?]
C --> C4[Dapatkah diinstansiasi dengan mudah dibaca lewat named association?]
C --> C5[Apakah status dan exception per instance disadari?]
C --> C6[Apakah kenaikan ukuran kode dapat diterima?]
C --> C7[Apakah instance untuk pengujian disiapkan?]
Dalam kalimat, ringkasannya sebagai berikut.
- Operasi yang dipakai di body harus terlihat sebagai kontrak parameter formal.
- Jika
privatecukup, pakaiprivate. Jika aritmetika perlu, pakairange <>ataudigits <>. - 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.
flowchart TB
Root[ada-generic-programming] --> Src[src]
Src --> G[generics]
Src --> D[demos]
G --> SwapSpec[generic_swap.ads]
G --> SwapBody[generic_swap.adb]
G --> StackSpec[generic_stack.ads]
G --> StackBody[generic_stack.adb]
G --> SortSpec[generic_insertion_sort.ads]
G --> SortBody[generic_insertion_sort.adb]
G --> StatsSpec[generic_statistics.ads]
G --> StatsBody[generic_statistics.adb]
G --> CountSpec[generic_count_if.ads]
G --> CountBody[generic_count_if.adb]
G --> KVSpec[generic_kv_store.ads]
G --> KVBody[generic_kv_store.adb]
D --> SwapDemo[swap_demo.adb]
D --> StackDemo[stack_demo.adb]
D --> SortDemo[sort_demo.adb]
D --> StatsDemo[statistics_demo.adb]
D --> CountDemo[count_if_demo.adb]
D --> KVDemo[kv_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.
flowchart LR
Contract[tulis kontrak] --> Generic[tulis body generik]
Generic --> Instance[instansiasi dengan meneruskan tipe, nilai, fungsi]
Instance --> Safe[pakai secara type-safe]
Safe --> Reuse[reuse 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.
- Pengembangan aplikasi Windows
- Pengembangan komponen COM
- Penggantian aplikasi Windows
- Konsultasi teknis dan tinjauan desain
- Hubungi kami
Referensi
- Ada 2022 Language Reference Manual, Section 12: Generic Units ── Ketentuan unit generik itu sendiri. 12.1 sesuai deklarasi generik, 12.3 instansiasi, 12.4 objek formal (parameter nilai), 12.5 tipe formal (kategori seperti
private,range <>,digits <>), 12.6 subprogram formal, dan 12.7 paket formal. Jika ragu di suatu bab artikel ini, yang paling andal adalah merujuk pasal yang sesuai. - Ada 2022 Language Reference Manual, 2.2: Lexical Elements, Separators, and Delimiters ── Tempat didefinisikannya bahwa
<>disebut compound delimiter “box”. Ini dasar notasiboxpada diagram. - GNAT User’s Guide for Native Platforms ── Cara memakai
gnatmakedangnatchop, serta daftar opsi kompilasi termasuk-gnata. Lihat di sini jika terhenti pada langkah bab 3. - Alire Documentation ── Cara memasang
alr, mengelola toolchain (GNAT / gprbuild), dan membuat crate. Untuk membangun lingkungan saja, materi ini sudah cukup. - Kode sampel (GitHub) ── Sampel artikel ini yang sudah dipisah ke berkas mengikuti struktur bab 18.
Artikel terkait
Artikel terbaru dengan tag yang sama untuk mendalami topik-topik terdekat.
Pemrograman sistem real-time dengan Ada — praktik pengendalian prioritas, periode, dan waktu eksekusi
Mempelajari Annex D (sistem real-time) pada Ada melalui delapan contoh kode praktis. Prioritas task, Ceiling_Locking, eksekusi periodik d...
Konkurensi yang aman di Ada ── panduan praktis task dan protected object
Artikel pengantar konkurensi yang tertanam di bahasa Ada: task dan protected object. Rendezvous (entry/accept), accept selektif, mutual e...
Kedalaman virtualisasi Windows (Bagian 3) — VM yang siap dalam hitungan detik: apa yang membuat WSL2, Windows Sandbox, dan kontainer terasa ringan
Mengapa WSL2 dan Windows Sandbox boot dalam hitungan detik dan terasa ringan. Artikel ini menguraikan mekanismenya, dari dynamic base ima...
Kedalaman virtualisasi Windows (Bagian 2) — Memori yang bahkan kernel pun tidak bisa lihat: mekanisme VBS, HVCI, dan Credential Guard
Pada instalasi bersih ke perangkat keras yang kompatibel, VBS aktif secara bawaan. Hypervisor dan SLAT membentuk isolasi yang lebih kuat ...
Kedalaman virtualisasi Windows (Bagian 1) — Di mana Windows Anda berjalan: hypervisor dan partisi
Setelah Hyper-V diaktifkan, Windows host sendiri berjalan di atas hypervisor sebagai root partition. Artikel ini menjelaskan fondasi virt...
Topik terkait
Halaman-halaman ini menempatkan topik dalam konteks layanan dan keputusan yang lebih luas.
Topik teknis Windows
Portal tentang pengembangan Windows, investigasi bug, dan pemanfaatan aset yang ada.
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.