Pemrograman generik di Ada ── menulis kontrak dengan tipe, dan mewujudkan 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

1. Pendahuluan ── bukan «menerima apa pun», melainkan «apa yang dijanjikan»

Ketika Anda mencoba menulis kode yang dapat di-reuse dalam bahasa bertipe statis, Anda segera berhadapan dengan dilema yang sama. 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 ini».

Tiga cara mereuse pemrosesanDiagram yang membandingkan salin-tempel, void*/Object/cast, dan generic Ada dari segi type-safety, pemeriksaan waktu kompilasi, dan biaya runtimePemrosesan yang ingin di-reuseBagaimana cara reuse-nya?Salin dan tempelvoid* / Object / castGeneric AdaMudah ada perbaikan yang terlewatMudah terjadi error runtime atau rusaknya type-safetyType-safePemeriksaan waktu kompilasiTanpa dispatch tambahan di runtime

Artikel ini menata pemrograman generik di 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 Audiens dan apa yang dapat dibawa pulang dari artikel ini

Artikel ini ditujukan kepada pembaca seperti berikut.

  • Anda yang punya pengalaman memakai template C++, generics C# atau Java, atau generics Rust
  • Anda yang belum akrab dengan sintaksis Ada, tetapi tertarik pada gagasan desain «menulis kontrak dengan tipe»
  • Anda 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 dibawa pulang 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, Anda dapat memetik bab 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». Kami mendalami gagasan Ada yang «berbicara desain lewat tipe» dari sudut generic.

Peta pengetahuan artikel ini

Generic Ada adalah mekanisme reuse yang menerima tipe, nilai, subprogram, dan paket sebagai parameter formal, lalu memeriksa tipenya secara statis pada saat instansiasi dengan new. Subprogram generik dan paket generik adalah dua pilar mekanisme ini: parameter tipe formal menyatakan kontrak operasi yang boleh dipakai body lewat kategori seperti private, range <>, dan digits <>; parameter subprogram formal menginjeksikan perilaku seperti perbandingan dan predikat; parameter paket formal menerima paket generik yang sudah diinstansiasi sebagai komponen. Berkat contract model yang memeriksa body secara mandiri hanya dalam lingkup parameter formal, masalah error yang baru diketahui saat instansiasi, yang secara historis dimiliki template C++, secara struktural tidak terjadi di Ada. Dengan GNAT dan Alire, mekanisme ini dapat dicoba di lingkungan tanpa biaya.

Peta pengetahuan pemrograman generik AdaDiagram yang menunjukkan subprogram generik dan paket generik yang dibangun di atas kontrak (contract model) parameter formal berupa tipe, nilai, subprogram, dan paket, pemeriksaan kontrak itu pada instansiasi dengan new, serta perbedaannya dengan masalah error saat instansiasi yang secara historis dimiliki template C++mengimplementasikanmenggunakanmenggunakanmensyaratkanmenggunakanmenggunakanmensyaratkandikonfigurasi denganmensyaratkanmensyaratkanmengimplementasikanmengimplementasikanmencegahdapat menyebabkanmensyaratkanmengimplementasikandikonfigurasi denganmenggunakanmenggunakanmensyaratkanmengimplementasikangeneric Ada (unit generic)contract model generic AdaAda (bahasa pemrograman)subprogram generic (Generic Subprogram)paket generic (Generic Package)parameter tipe formal (Formal Type)parameter subprogram formal (Formal Subprogram)parameter paket formal (Formal Package)instansiasi (instantiation)parameter objek formal (Formal Object)error template yang hanya diketahui saat instansiasitemplate C++GNATAlire

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, Anda mengombinasikan subprogram, paket, parameter subprogram, parameter nilai, dan parameter paket formal.

Ada GenericsGeneric SubprogramSwapCount_IfSortGeneric PackageStackStatisticsKV StoreFormal ParametersTipeprivatelimited privaterange boxmod boxdigits boxdelta boxdiscrete boxObjectMax_SizeThresholdSubprogramfungsi Lessfungsi EqualsPredicatePackagewith package P is newGenericIde desainContract ModelPemeriksaan statisZero-Cost AbstractionPemisahan spesifikasi danbody

box pada diagram merujuk pada <> Ada (box compound delimiter). Karena Mermaid tidak dapat menggambar <> apa adanya, pada diagram saja ia ditulis box.

Cara membaca artikel ini sederhana. Bab-bab awal mengikuti sintaksis; bab-bab kemudian membahas keputusan desain. Jika ini pertama kali Anda membaca Ada, jangan berusaha menghafal detail sintaksis sejak awal; perhatikan «apa yang dijadikan parameter formal» dan «operasi apa yang diizinkan pada parameter formal itu».

2.1 Kamus mini istilah

Istilah yang akan berulang muncul disusun dulu dalam padanan Indonesia–Inggris. Literatur Ada berbahasa Jepang sering memakai istilah yang setara «generic» (sōshō); artikel ini memakai «subprogram generik» dan «paket generik», tetapi maknanya sama dengan «generic».

Notasi dalam artikel ini Inggris 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 di antara generic dan body deklarasi. Dapat 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 saat instansiasi
instansiasi instantiation Membuat subprogram atau paket biasa dari unit generik dengan new
box <> Nama simbol <> dalam Ada. Seperti pada range <>, menyatakan «tipe konkret diputuskan saat instansiasi»
contract model contract model Cara Ada menulis body hanya dalam lingkup janji parameter formal, dan memeriksa tipe body secara mandiri (bab 13)

Dalam teks kami menulis range <>, digits <> sesuai notasi Ada, tetapi pada diagram saja kami menulis range box, digits box. Alasannya: Mermaid tidak dapat menggambar label yang berisi <> apa adanya, dan maknanya sama dengan teks. Seperti pada tabel di atas, Ada sendiri menyebut <> sebagai box, jadi notasi diagram pun tidak menyimpang dari istilah Ada.

3. Lingkungan eksekusi dan cara kompilasi

Kode dalam artikel ini mengasumsikan GNAT 15.x atau lebih baru. GNAT adalah compiler Ada yang representatif, dan dapat dipasang dari Alire. Alire adalah package manager Ada / SPARK, dan juga dipakai untuk mengelola toolchain serta build.

gnat --version
# GNAT 15.2.1

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

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

Tata letak sampel di repositoriDiagram yang menunjukkan bahwa sampel artikel diletakkan sebagai berkas .ada terpisah di bawah ada-generic-programming/src/snippets/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 dipisah dulu dengan gnatchop, lalu dikompilasi dengan gnatmake.

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

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

Alur dari satu berkas .ada sampai dijalankanUrutan pengembang menyerahkan satu berkas .ada ke gnatchop, mem-build dengan gnatmake, lalu menjalankan berkas eksekusiBerkas eksekusignatmakegnatchopPengembangBerkas eksekusignatmakegnatchopPengembangMenyerahkan satu berkas .adaDipisah 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 pada bagian generic
  3. Menginstansiasi dengan new di sisi pemakai
Tiga langkah model generic AdaAlur dari deklarasi generic, parameter formal, dan body generik sampai instansiasi dengan new, lalu pemakaian sebagai subprogram atau paket biasadeklarasi genericparameter formalbody generikinstansiasi dengan newdipakai sebagai subprogram atau paket biasatipenilaisubprogrampaket

Misalnya, jika pemrosesan yang menukar dua nilai dijadikan generic, hanya tipe 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 «template pemrosesan tukar yang dapat dipakai untuk sembarang tipe Element». Baru setelah diberi tipe konkret ia menjadi prosedur biasa.

procedure Swap_Integer is new Generic_Swap (Integer);

Dalam bentuk diagram, hubungannya sebagai berikut.

Instansiasi Generic_Swap per tipeDiagram yang menunjukkan Generic_Swap diinstansiasi menjadi prosedur biasa untuk Integer, Character, dan My_Recordmeneruskan Integermeneruskan Charactermeneruskan My_RecordGeneric_Swaptype Element is privateSwap_IntegerSwap_CharacterSwap_My_RecordMenukar variabel IntegerMenukar variabel CharacterMenukar variabel My_Record

Yang penting adalah body template hanya ditulis dengan operasi yang boleh dipakai pada Element. Jika dideklarasikan type Element is private;, operasi dasar seperti assignment dan perbandingan kesetaraan dapat 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 titik yang sangat berbeda dibanding generics biasa di C# atau Java.

Empat jenis parameter formal generic AdaDiagram yang menguraikan parameter formal generic menjadi parameter tipe, objek/nilai, subprogram, dan paket beserta contoh penulisannyageneric 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 <> Ada (box compound delimiter). Karena Mermaid tidak dapat menggambar <> apa adanya, pada diagram saja ia ditulis box.

Parameter formal yang representatif, dalam bentuk tabel, adalah sebagai berikut.

Jenis Contoh Arti
Parameter tipe type Element is private; Bentuk dasar yang menerima sembarang tipe definite non-limited
Parameter tipe limited type Element is limited private; Juga menerima tipe yang tidak dapat disalin
Tipe diskret type Index is (<>); Tipe yang dapat dipakai sebagai indeks array, seperti integer atau enumerasi
Integer bertanda type Count is range <>; Boleh mengasumsikan aritmetika integer seperti +, -, dan perbandingan urutan
Integer modular type Word is mod <>; Menangani operasi bit atau integer bersifat modulus
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; Menetapkan ukuran, ambang, dan semacamnya 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 penulisan yang natural bukan «menerima apa pun lalu melakukan hal berbahaya di dalamnya», melainkan «hanya menerima tipe yang dapat melakukan operasi ini».

6. Subprogram generik ── memahami struktur minimum dengan Generic_Swap

Sebagai contoh pertama, lihat Generic_Swap yang menukar dua variabel sembarang tipe.

6.1 Spesifikasi

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

Bagian yang mengikuti 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 dapat dibaca dua hal berikut.

  • Generic_Swap dapat dipakai untuk sembarang tipe Element
  • Body tidak bergantung pada struktur internal Element maupun 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, ia dapat dipakai secara natural pada tipe yang mendukung assignment: Integer, Character, tipe record, tipe enumerasi, dan sebagainya.

Pertukaran dua nilai lewat Generic_SwapAlur Temporary menyalin A, A mengambil B, lalu B mengambil Temporary, sehingga 10 dan 20 tertukarSetelah pemanggilanSebelum pemanggilanA = 20B = 10A = 10B = 20Temp = A

6.3 Instansiasi

Di sisi pemakai, gunakan new.

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

Dengan ini, 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 eksekusinya sebagai berikut.

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

Di sini, 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 menampung 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 beberapa operasi plus status internal, gunakan paket generik. Contoh representatifnya adalah stack.

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

Generic_Stack menghasilkan instance per tipe dan ukuranDiagram yang menunjukkan Generic_Stack menerima Element_Type dan Max_Size, menyediakan Push/Pop dan operasi lain, lalu menghasilkan instance seperti Int_Stack dan Float_StackGeneric_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

Karena Max_Size bertipe Positive, instansiasi dengan ukuran 0 atau kurang tidak dapat dilakukan. Seperti ini, parameter nilai pun dapat diberi batasan lewat 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 template yang sama, tetapi status internal tidak dibagi.

Int_Stack dan Float_Stack tidak berbagi statusDiagram yang menunjukkan dua instance dari Generic_Stack masing-masing memiliki Top dan Data sendiriStatus Float_StackTopData : array FloatStatus Int_StackTopData : array IntegerGeneric_StackInt_StackFloat_Stack

7.3 Transisi status stack

Stack lebih mudah dipahami jika dilihat sebagai mesin status.

Transisi status stackDiagram status yang menunjukkan transisi Empty, NonEmpty, Full, Overflow, dan Underflow lewat Push dan PopPushPush / 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

Spasi satu karakter di kanan = berasal dari spesifikasi 'Image pada tipe integer, yang menaruh satu spasi di depan nilai non-negatif. Karena Push dipanggil 3 kali dan Pop 2 kali, Size terakhir menjadi 1. Selain itu, jika Push dipanggil lagi ketika stack penuh (dalam contoh ini 5 elemen sesuai Max_Size), Int_Stack.Stack_Overflow dilempar; jika Pop dipanggil ketika kosong, Int_Stack.Stack_Underflow dilempar.

Paket generik dalam praktik sering efektif sebagai «kontainer kecil», «buffer panjang tetap», «ring buffer», «antrean log», atau «lapisan abstraksi perangkat keras». Khususnya di Ada, desain yang menetapkan ukuran secara statis sebagai tipe atau parameter nilai, alih-alih menggerakkannya di runtime, selaras dengan sistem berkeandalan tinggi.

8. Parameter subprogram formal ── menginjeksikan perilaku

Meski hanya menerima tipe, masih ada hal yang belum dapat diekspresikan. Misalnya pada pengurutan, selain tipe elemen, diperlukan juga logika perbandingan «mana yang diletakkan lebih dulu».

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

Generic_Insertion_Sort menerima fungsi perbandinganDiagram yang menunjukkan Generic_Insertion_Sort menerima Item_Type, Index, Item_Array, dan fungsi perbandingan, lalu menghasilkan urutan menaik, menurun, atau kustomGeneric_Insertion_SortItem_TypeIndexItem_Arrayfungsi perbandinganmemakai perbandingan standarmeneruskan Greater untuk urutan menurunmeneruskan urutan kustom

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);

Di sini 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 tipe integer, enumerasi pun dapat diterima. Yang khas Ada: indeks array tidak harus Positive; enumerasi seperti Day juga dapat dipakai.

is <> pada with function "<" ... is <>; berarti, jika parameter aktual dihilangkan, operator standar yang terlihat atau fungsi yang cocok akan dipakai. Jadi, untuk tipe yang sudah punya < seperti Integer, fungsi perbandingan tidak perlu dituliskan secara 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. Hanya dengan mengganti fungsi perbandingan, struktur loop yang sama dapat dipakai untuk urutan menaik maupun menurun.

Alur insertion sortDiagram yang menunjukkan Key diambil dari kiri, elemen sebelumnya digeser ke kanan selama Key berada di depannya, lalu Key disisipkan sampai array terurutYesNoNoYesArray belum terurutAmbil Key dari kiri ke kananKey berada sebelum 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. Sementara Sort_Desc mengganti fungsi perbandingan dengan "<" => Greater.

Urutan menaik dan menurun dari body yang samaDiagram yang menunjukkan array yang sama diurutkan menaik dengan perbandingan standar dan menurun dengan Greater99, 3, 47, 12Sort_Ascperbandingan standarSort_DescGreater sebagai fungsi perbandingan3, 12, 47, 9999, 47, 12, 3

Mekanisme ini dekat dengan desain C++ yang meneruskan comparison function object sebagai argumen template, atau desain Rust yang menuntut urutan lewat trait bound. Namun di Ada, hal itu dieksplisitkan sebagai parameter subprogram formal: «teruskan fungsi berbentuk ini».

8.4 Contoh eksekusi

Mengikuti struktur bab 18, sisi pemanggil ketika 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 dikonfirmasi bahwa dua prosedur yang dibuat dari body Generic_Insertion_Sort yang sama menjadi urutan terbalik hanya dengan mengganti fungsi perbandingan. Spasi di depan setiap elemen berasal dari 'Image tipe integer yang menaruh satu spasi di depan nilai non-negatif.

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

type T is private; nyaman, tetapi tidak berarti semuanya bisa. Pada tipe private, aritmetika empat operasi atau perbandingan urutan tidak dapat dipakai seolah-olah sudah ada. Karena itu, di Ada kategori dapat ditentukan pada parameter tipe formal.

Kategori yang dapat ditentukan pada parameter tipe formalDiagram yang menguraikan formal type menjadi private, limited private, discrete, range, mod, digits, delta, dan access, plus contoh turunannyaFormal 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 <> Ada (box compound delimiter). Karena Mermaid tidak dapat menggambar <> apa adanya, pada diagram saja ia ditulis box.

9.1 Apa manfaat menentukan kategori

Misalnya untuk menghitung rata-rata atau varians, diperlukan penjumlahan, pengurangan, perkalian, dan pembagian. Pada tipe private, operasi-operasi itu tidak dapat diasumsikan. Karena itu, kita membatasi pada 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 <>;, diketahui bahwa Real adalah tipe floating-point. Karena itu, di body generik dapat dipakai +, -, *, /, dan sebagainya.

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 pada tipe floating-point dengan presisi berbeda.

Generic_Statistics diinstansiasi untuk beberapa presisiDiagram yang menunjukkan Generic_Statistics menghasilkan Float_Stats, Long_Stats, dan My_Real_Stats, masing-masing menghitung Mean dan VarianceGeneric_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; dengan Exp => 0, notasi menjadi desimal biasa, bukan bentuk eksponen. Aft => 3 adalah jumlah digit di belakang koma. Perhatikan bahwa Variance ini adalah varians populasi (dibagi Values'Length), bukan varians sampel.

9.5 Penentuan kategori sebagai «spesifikasi tingkat tipe»

Penentuan kategori bukan sekadar sintaksis untuk menenangkan compiler. Bagi pembaca, ia juga 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 resource yang tidak dapat disalin limited private Tidak mengasumsikan assignment
Indeks array, penelusuran status enumerasi (<>) Dapat memakai First, Last, Succ, Pred
Jumlah integer, counter range <> Boleh mengasumsikan aritmetika integer
Bit mask, counter berputar mod <> Boleh mengasumsikan aritmetika modular
Rata-rata, varians, perhitungan numerik digits <> Boleh mengasumsikan aritmetika floating-point
Presisi tetap seperti uang atau besaran kendali delta <> Boleh mengasumsikan aritmetika fixed-point

10. Injeksi predikat ── menulis Count_If dengan gaya Ada

Parameter subprogram formal tidak hanya untuk fungsi perbandingan, tetapi juga untuk predikat. Predikat adalah fungsi yang menerima nilai dan mengembalikan Boolean.

Peran yang dekat dengan Func<T, bool> di C#, Predicate<T> di Java, atau lambda dan function object di C++, di Ada dapat diekspresikan 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 <>. Karena tidak ada fungsi predikat standar yang secara otomatis terlihat, desainnya mewajibkan sisi pemakai untuk selalu 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.

Alur Generic_Count_IfDiagram yang menunjukkan setiap elemen array diuji Predicate, Count bertambah jika True, lalu Count dikembalikanTrueFalsearraytelusuri setiap elemenPredicate(Item)?tambah Counttidak melakukan apa-apake elemen berikutnyakembalikan Count

10.3 Hitung genap dan hitung menurut 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 dapat dibuat dua fungsi yang hanya berbeda kondisinya.

Dua fungsi dari Generic_Count_IfDiagram yang menunjukkan Count_Even dan Count_Large dibuat dari Generic_Count_If dengan predikat berbeda, lalu menghitung genap dan nilai lebih besar dari 50Generic_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 counter, dan pengembalian hasil semuanya sama. Yang diinjeksikan sebagai fungsi hanyalah «apa yang dihitung». Inilah bentuk dasar desain higher-order 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. Menyintesis beberapa parameter ── penyimpanan kunci-nilai generik

Pada komponen nyata, jarang sekali cukup dengan satu parameter tipe. Tipe kunci dan nilai, cara membandingkan kunci, jumlah entri maksimum, dan kondisi lain perlu dikombinasikan.

Di sini kita memakai contoh penyimpanan kunci-nilai sederhana berukuran tetap.

Parameter dan pemakaian Generic_KV_StoreDiagram yang menunjukkan Generic_KV_Store menerima tipe kunci/nilai, fungsi kesetaraan, dan Max_Entries, lalu dipakai sebagai store konfigurasi, cache kecil, atau kamus panjang tetapGeneric_KV_StoreKey_TypeValue_Typefungsi kesetaraan 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 kesetaraan kunci
Max_Entries nilai Jumlah entri maksimum

Max_Entries diberi nilai default := 50. Jadi, jika tidak ditentukan, store-nya berkapasitas 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 berjumlah besar. Namun bentuknya mudah dipakai di situasi yang mengutamakan panjang tetap, skala kecil, dan tanpa alokasi memori dinamis.

Alur Put dan Get pada Generic_KV_StoreUrutan pemanggil memanggil Put, instance mencari kunci yang ada atau slot kosong, lalu Get mengembalikan nilaiKeys/Values/Usedinstance Generic_KV_StorepemanggilKeys/Values/Usedinstance Generic_KV_Storepemanggilalt[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 ke kunci yang sudah ada akan menimpa
   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: cabangkan dengan Contains seperti contoh di atas, atau tulis exception handler.

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

Jika kuncinya misalnya string yang mengabaikan huruf besar/kecil, Anda dapat meneruskan fungsi kesetaraan sendiri.

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 ── memodulkan generic lebih jauh

Pada generic Ada, paket itu sendiri dapat dijadikan parameter formal. Dengan ini, «instance yang dibuat dari suatu paket generik» dapat diperlakukan sebagai masukan generic lain.

Instance stack menjadi masukan loggerDiagram yang menunjukkan Int_Stack dari Generic_Stack diteruskan ke Generic_Stack_Logger lalu menjadi instance loggerGeneric_StackInt_StackGeneric_Stack_LoggerInt_Stack_Logger_Instance

12.1 Logger yang menerima stack

Misalnya, kita membuat logger yang menerima instance Generic_Stack sebelumnya dan 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, buat stack dulu, lalu teruskan stack itu 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);

Dengan desain seperti ini, komponen generik dapat dikombinasikan satu sama lain.

Dua tahap kombinasi genericDiagram yang menunjukkan tahap 1 membuat Int_Stack dari Generic_Stack, lalu tahap 2 membuat Int_Stack_Logger yang menyediakan Print_SizeTahap 2Tahap 1Int_Stack_LoggerGeneric_Stack_LoggerInt_StackGeneric_StackPrint_Size

Kegunaannya dekat dengan template template parameter di C++, tetapi di Ada dapat dieksplisitkan bahwa «instance paket generik ini yang diterima». Pada kode Ada berskala besar, ini berguna ketika memisahkan lalu mengombinasikan kontainer, algoritme, log, pemeriksaan, dan bantuan tes.

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, meski hanya dideklarasikan type Element is private;, < tidak dapat dipakai pada Element. Jika ingin memakai <, itu harus dieksplisitkan sebagai subprogram formal, atau kategori tipe dibuat lebih konkret.

Contract model generic AdaDiagram yang menunjukkan body diperiksa hanya dalam lingkup kontrak formal part, lalu instansiasi memeriksa apakah actual memenuhi kontrakgeneric formal partkontrakgeneric bodyimplementasi dalam lingkup kontrakpemeriksaan tipe pada body sajainstansiasiactual parameterstipe, fungsi, dan nilai aktualperiksa apakah actual 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 template diinstansiasi». Hal itu diperbaiki oleh concepts di C++20, namun generic Ada sejak awal adalah model yang menuliskan kontrak secara eksplisit.

Perbedaan pemeriksaan kontrak Ada dan C++Diagram yang membandingkan alur Ada yang menulis kontrak di formal part lalu memeriksa body dan actual, dengan template C++ yang secara historis mewujudkan requirement saat instansiasiC++ templatesrequirement terwujud saat instantiationtulis template bodyconstraints dapat dieksplisitkan dengan conceptsAdabody diperiksa dalam kontraktulis kontrak di formal partinstantiation memeriksa actual

Generics Java dan C# berpusat pada tipe referensi, constraint, type erasure, dan hubungannya dengan representasi runtime. Generic Ada, sebaliknya, condong pada gagasan membuat instance konkret pada waktu kompilasi.

Sudut pandang Ada C++ Java Rust
Cara menulis kontrak Tipe, nilai, fungsi, dan paket di formal part templates / concepts Parameter tipe dan bounds trait bounds
Pemeriksaan body Diperiksa dalam kontrak parameter formal Berpusat pada perwujudan saat instansiasi Diperiksa dalam bounds Diperiksa dalam trait bounds
Biaya runtime Pada dasarnya resolusi statis Pada dasarnya generasi statis Terpengaruh type erasure Pada dasarnya monomorphization
Parameter nilai Ya Ya Terbatas const generics
Subprogram sebagai parameter formal Ya Dinyatakan dengan function object, dll. Lambda / functional interface Closure / fungsi / trait
Paket sebagai parameter formal Ya Template template, dll. Tidak Terpisah dari struktur modul

Fitur rinci tiap bahasa berbeda, tetapi ciri Ada adalah «menulis kontrak sebagai sintaksis terlebih dulu».

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

Generic nyaman, tetapi bukan berarti segala sesuatu harus dijadikan generic. Dalam praktik, menilai seperti berikut mengurangi risiko gagal.

Cabang keputusan apa yang dijadikan genericDiagram yang menuntun dari perbedaan tipe, ukuran/ambang, perilaku perbandingan, dan kebutuhan merangkum status internal ke parameter tipe, parameter nilai, subprogram formal, atau paket generikYesNoYesNoYesNoYesNoTerdapat pemrosesan yang ingin di-reuseHanya tipenya yang berbeda?pertimbangkan parameter tipeUkuran atau ambang juga berbeda?tambah parameter nilaiPerilaku perbandingan atau predikat berbeda?tambah 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 tanpa status.

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

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

14.2 Situasi yang cocok untuk paket generik

Paket generik cocok ketika Anda ingin membawa beberapa operasi dan status internal bersama tipe.

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

Khususnya di Ada, spesifikasi paket adalah API publik dan body paket adalah implementasi, sehingga paket generik dapat dipakai sebagai «template modul yang type-safe».

Spesifikasi, body, dan formal part paket generikDiagram yang menunjukkan formal part menjadi kontrak bagi spec dan body, spec dipakai pengguna, dan body disembunyikandisembunyikanpackage 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. Lebih aman memulai dari yang minimal, lalu menambah ketika ada alasan untuk mengganti.

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

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

Di Ada, named association dapat dipakai saat instansiasi. Karena titik penting desain generic muncul pada instansiasi, dalam kode praktik menuliskannya secara named sering lebih mudah dipelihara.

15. Titik tersandung yang sering muncul

Generic Ada kuat, tetapi ada beberapa titik yang mudah 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;

Karena Element hanya dideklarasikan sebagai private, tidak ada jaminan < dapat dipakai. 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;
Perbandingan harus ditulis di formal partDiagram yang menunjukkan bahwa memakai perbandingan di body tanpa kontrak menghasilkan error kompilasi, sementara menuliskan fungsi perbandingan di formal part membuat komparabilitas diperiksa saat instansiasiingin memakai perbandingan di bodytulis fungsi perbandingan di formal partkonfirmasi komparabilitas saat instansiasihanya privateerror kompilasi di body generik

15.2 is <> bukan «inferensi otomatis untuk apa pun»

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

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

15.3 Exception juga menjadi benda berbeda per instance

Jika exception dideklarasikan di spesifikasi paket generik, exception itu menjadi berbeda per instance.

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 menanganinya sebagai exception bersama, pertimbangkan juga desain yang mendefinisikan exception di luar generic.

Exception per instance adalah benda berbedaDiagram yang menunjukkan Stack_Overflow Generic_Stack menjadi exception berbeda pada Int_Stack dan Float_Stackexception berbedaGeneric_Stackdeklarasi Stack_OverflowInt_Stack.Stack_OverflowFloat_Stack.Stack_Overflow

15.4 Ukuran kode dapat bertambah

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

Ini trade-off yang juga terlihat pada template C++ dan monomorphization di Rust. Pada pengembangan yang condong ke keandalan tinggi, embedded, atau real-time, gagasannya adalah mengelola ukuran artefak build sebagai ganti mengurangi ketidakpastian runtime.

Satu body generic menghasilkan kode per instanceDiagram yang menunjukkan satu body generic menghasilkan versi Integer, Float, Long_Float, dan My_Type, dengan manfaat menghindari type test runtime dan boxing, serta risiko ukuran kode bertambahsatu body genericversi Integerversi Floatversi Long_Floatversi My_Typekode yang dihasilkanmudah menghindari type test runtime dan boxingjika instance banyak, perhatikan peningkatan ukuran

15.5 Situasi yang sebaiknya memakai limited private

type Element is private; mengasumsikan assignment. Jika menangani hal yang tidak ingin disalin — file handle, lock, device handle, dan semacamnya — 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, lebih aman memakai algoritme yang menerapkan prosedur, atau desain yang mengekspresikan referensi secara eksplisit, daripada kontainer yang menyimpan nilai.

16. Kumpulan pola desain kecil

Dari sini, bentuk yang sering dipakai dalam praktik disusun secara 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;
Generic_Min menuntut fungsi perbandinganDiagram yang menunjukkan Element membutuhkan fungsi perbandingan agar Generic_Min dapat mengembalikan yang lebih kecilElementperlu fungsi perbandinganGeneric_Minkembalikan 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 ditetapkan 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 untuk tes, dan sebagainya.

Generic_Print_Twice menerima PutDiagram yang menunjukkan Generic_Print_Twice menerima Put sebagai subprogram formal, lalu dapat diarahkan ke konsol, log, atau buffer tesGeneric_Print_Twicemenerima Put sebagai subprogram formalkeluaran konsolkeluaran logbuffer untuk tes

16.4 Tidak mematok tipe indeks array

Di Ada, tipe indeks array juga merupakan informasi tipe yang penting. Alih-alih mematok Positive, menjadikan tipe indeks sebagai parameter formal bila perlu akan menaikkan kemampuan reuse.

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 diskret mencakup beberapa jenisDiagram yang menunjukkan Index diskret dapat berupa Positive range, enumerasi Day, enumerasi State, atau tipe integer sendiriIndex is discretePositive rangeenumerasi Dayenumerasi Statetipe integer sendiri

17. Daftar periksa untuk API yang khas Ada

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

Daftar periksa desain genericDiagram yang merangkum tujuh butir pemeriksaan: jumlah parameter formal, operasi di formal part, kategori tipe, named association, status dan exception per instance, ukuran kode, serta instance untuk tescek desain genericapakah parameter formal minimal?apakah operasi yang diperlukan dieksplisitkan di formal part?apakah kategori seperti private / range / digits sudah tepat?dapat diinstansiasi dengan named association yang mudah dibaca?sadar akan status dan exception per instance?peningkatan ukuran kode dapat diterima?sudah menyiapkan instance untuk tes?

Dirangkum dalam kalimat, sebagai berikut.

  • Operasi yang dipakai di body harus terlihat sebagai kontrak parameter formal.
  • Jika private cukup, pakai private. Jika perlu aritmetika, 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.

Contoh pemisahan berkas spesifikasi, body, dan demoDiagram yang menunjukkan generic_swap dan lainnya di bawah src/generics, serta swap_demo dan demo lain di bawah src/demosada-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 di 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.

Dari kontrak sampai reuse yang type-safeAlur menulis kontrak, menulis body generik, menginstansiasi dengan tipe/nilai/fungsi, memakai secara type-safe, lalu reuse tanpa menyalintulis 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 / 32-bit / 64-bit, 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 ini». 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, dan 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