Lập trình generic trong Ada — Viết hợp đồng bằng kiểu, tái sử dụng với chi phí zero
· Cập nhật ngày: · Go Komura · Ada, Ngôn ngữ lập trình, Generics, Hệ thống kiểu, Static typing, Contract model, Zero-cost abstraction, GNAT, Alire, Độ tin cậy cao, Tái sử dụng mã
1. Mở đầu — Không phải “nhận mọi thứ”, mà là “hứa điều gì”
Khi bạn cố viết mã tái sử dụng trên ngôn ngữ static typing, bạn sớm đụng cùng một khó khăn. Stack viết cho số nguyên thì cũng muốn dùng với chuỗi. Muốn dùng cùng xử lý thống kê cho mảng floating-point. Muốn dùng logic sort tăng dần cho sort giảm dần nữa. Thế nhưng copy cùng mã theo từng kiểu thì sẽ sót chỗ sửa. Ngược lại, thiết kế nhận mọi thứ qua void* hay cast thì an toàn kiểu sụp.
Câu trả lời của Ada là generic (generic units).
Generic Ada không phải thay thế văn bản. Nó nhận kiểu, giá trị, subprogram, thậm chí cả package làm formal parameter, rồi kiểm tra kiểu tĩnh tại thời điểm instantiation. Nói cách khác, không phải lúc runtime mới hỏi “kiểu này có ổn không”, mà là cơ chế chốt lúc biên dịch “linh kiện này có thỏa hợp đồng này không”.
flowchart LR
accTitle: Ba cách tái sử dụng mã
accDescr: So sánh copy-paste, void-star hoặc cast, và generic Ada về sót sửa, lỗi runtime, và kiểm tra lúc biên dịch
A["Logic muốn tái sử dụng"] --> B{"Tái sử dụng thế nào?"}
B --> C["Copy và paste"]
B --> D["void* / Object / cast"]
B --> E["Generic Ada"]
C --> C1["Dễ sót chỗ sửa"]
D --> D1["Dễ lỗi runtime hoặc mất an toàn kiểu"]
E --> E1["An toàn kiểu"]
E --> E2["Kiểm tra lúc biên dịch"]
E --> E3["Không có dispatch thừa lúc runtime"]
Bài viết này sắp xếp lập trình generic trong Ada theo luồng sau.
- Generic subprogram
- Generic package
- Type parameter, value parameter, subprogram parameter
- Type category như
private,range <>,digits <> - Ví dụ triển khai: sort, stack, thống kê,
Count_If, key-value store - Higher-order generic qua formal package parameter
- Contract model của Ada và cách nghĩ thiết kế thực tiễn
1.1 Đối tượng đọc và những gì bạn mang về được
Bài viết hướng tới những người như sau.
- Đã dùng template C++, generic C# hoặc Java, hoặc generic Rust
- Chưa thuộc cú pháp Ada nhưng quan tâm thiết kế “viết hợp đồng bằng kiểu”
- Đang cân nhắc chính sách thiết kế linh kiện tái sử dụng trong bảo trì dài hạn, embedded, hoặc độ tin cậy cao
Về cú pháp Ada, biết package (tách spec .ads và body .adb) cùng parameter mode in / out / in out là đủ để đọc tiếp. Nếu hai điểm đó còn lung túng, đọc trước “Sức hấp dẫn của ngôn ngữ Ada” sẽ nhanh hơn.
Điều mang về không phải bản thân cú pháp Ada, mà là cách làm thiết kế: viết ra bằng kiểu, trước khi triển khai, rằng linh kiện tái sử dụng đòi gì. Cách nghĩ này áp dụng nguyên vẹn khi bạn quyết viết interface constraint của C# hay concepts của C++20 tới mức nào.
1.2 Hướng dẫn đọc — Không cần đọc hết
Bài có 20 chương. Không cần đọc thẳng từ đầu; có thể nhặt theo mục đích như sau.
| Mục đích | Đọc chương |
|---|---|
| Chỉ nắm cách nghĩ trong thời gian ngắn | Chương 4 (mô hình cơ bản) → chương 6 (generic subprogram tối thiểu) → chương 13 (contract model) |
| Muốn tự viết linh kiện | Chương 4 → chương 6 → chương 7 (generic package) → chương 8 (tiêm hành vi) → chương 9 (type category) |
| Chỉ cần kim chỉ nam quyết định thiết kế | Chương 13 → chương 14 (cái gì nên thành generic) → chương 15 (chỗ dễ vấp) → chương 17 (danh sách kiểm tra) |
| Muốn chạy tay | Chương 3 dựng môi trường, rồi sample hoàn chỉnh ở chương 6 và 7 |
Lộ trình ngắn nhất là ba chương 4, 6 và 13. Ba chương đó đủ formal parameter, instantiation, và contract model. Chương 5 và 9 là danh sách formal parameter; khi cần thì tra như từ điển.
Chủ đề này nối tiếp loạt bài “Sức hấp dẫn của ngôn ngữ Ada”, “Nhập môn formal verification với SPARK”, “Xử lý đồng thời an toàn”, “Hệ thống thời gian thực”. Ada “nói thiết kế bằng kiểu”; bài này đào góc generic.
Bản đồ tri thức của bài viết này
Generic của Ada là cơ chế tái sử dụng nhận kiểu, giá trị, subprogram và package làm formal parameter, rồi kiểm tra kiểu tĩnh tại thời điểm instantiation bằng new. Generic Subprogram và Generic Package là hai trụ cột của cơ chế này: Formal Type nêu tường minh hợp đồng các thao tác mà body được phép dùng, qua các category như private, range<> và digits<>; Formal Subprogram đưa vào hành vi như so sánh hay predicate; Formal Package nhận generic package đã được instantiate làm linh kiện. Nhờ contract model kiểm body độc lập chỉ trong phạm vi các formal parameter, vấn đề lỗi chỉ lộ khi instantiate mà template C++ từng gặp trong lịch sử, ở Ada thì về cấu trúc không xảy ra. Có GNAT và Alire thì có thể thử cơ chế này trong môi trường miễn phí.
flowchart LR
accTitle: Bản đồ tri thức lập trình generic của Ada
accDescr: Sơ đồ cho thấy Generic Subprogram và Generic Package được xây trên hợp đồng (contract model) của các formal parameter kiểu, giá trị, subprogram và package, việc kiểm hợp đồng đó lúc instantiation bằng new, cùng khác biệt với vấn đề lỗi chỉ lộ khi instantiate mà template C++ từng gặp trong lịch sử
ada_generics["Generic của Ada (generic unit)"]
ada_generic_contract_model["Contract model của generic Ada"]
ada["Ada (ngôn ngữ lập trình)"]
ada_generic_subprogram["Generic Subprogram"]
ada_generic_package["Generic Package"]
ada_generic_formal_type["Formal Type (tham số kiểu)"]
ada_generic_formal_subprogram["Formal Subprogram (tham số subprogram)"]
ada_generic_formal_package["Formal Package (tham số package)"]
ada_generic_instantiation["Instantiation (khởi tạo generic)"]
ada_generic_formal_object["Formal Object (tham số giá trị)"]
late_instantiation_error["Lỗi template chỉ phát hiện lúc instantiation"]
cpp_templates["Template C++"]
gnat["GNAT"]
alire["Alire"]
ada -->|"triển khai"| ada_generics
ada_generics -->|"sử dụng"| ada_generic_subprogram
ada_generics -->|"sử dụng"| ada_generic_package
ada_generic_subprogram -.->|"yêu cầu"| ada_generic_formal_type
ada_generics -->|"sử dụng"| ada_generic_formal_subprogram
ada_generics -->|"sử dụng"| ada_generic_formal_package
ada_generics -->|"yêu cầu"| ada_generic_instantiation
ada_generic_formal_type -->|"cấu hình bằng"| ada_generic_instantiation
ada_generic_package -.->|"yêu cầu"| ada_generic_formal_type
ada_generic_package -.->|"yêu cầu"| ada_generic_formal_object
ada_generic_formal_subprogram -->|"triển khai"| ada_generic_contract_model
ada_generic_formal_type -->|"triển khai"| ada_generic_contract_model
ada_generic_contract_model -->|"ngăn chặn"| late_instantiation_error
cpp_templates -.->|"có thể gây"| late_instantiation_error
ada_generic_formal_package -->|"yêu cầu"| ada_generic_instantiation
gnat -->|"triển khai"| ada
gnat -.->|"cấu hình bằng"| alire
ada_generic_subprogram -.->|"sử dụng"| ada_generic_formal_subprogram
ada_generic_package -.->|"sử dụng"| ada_generic_formal_subprogram
ada_generic_instantiation -.->|"yêu cầu"| ada_generic_formal_object
ada_generic_formal_object -->|"triển khai"| ada_generic_contract_model
Trong sơ đồ, đường liền nét biểu thị quan hệ luôn đúng và đường nét đứt biểu thị quan hệ có điều kiện (điều kiện nằm trong phần giải thích từng quan hệ trên trang chi tiết). Danh sách đầy đủ các quan hệ (tổng 21, kèm bằng chứng và mức chắc chắn) cùng định nghĩa các khái niệm chính được tập hợp tại trang chi tiết bản đồ tri thức (bằng tiếng Nhật). Dữ liệu: JSON-LD / Turtle
2. Bản đồ bài viết này
Trước hết nắm toàn cảnh bằng hình. Nếu hiểu generic Ada chỉ là “tính năng nhận kiểu làm đối số”, góc nhìn đó hẹp. Thực tế, tùy đơn vị muốn tái sử dụng, bạn kết hợp subprogram, package, subprogram parameter, value parameter, và formal package parameter.
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
Trong hình, box chính là <> (box compound delimiter) của Ada. Mermaid không vẽ <> nguyên si nên trong hình thôi ta viết box.
Cách đọc bài này đơn giản. Nửa đầu đi theo cú pháp; nửa sau xử lý phán đoán thiết kế. Nếu mới đọc Ada, đừng cố nhớ cú pháp nhỏ: hãy chú ý “đang lấy gì làm formal parameter” và “formal parameter đó cho phép thao tác nào”.
2.1 Từ điển thuật ngữ ngắn
Những từ sẽ lặp lại được đối chiếu Anh–Việt trước. Tài liệu Ada tiếng Nhật thường dịch generic bằng một từ Hán-Việt nghĩa là “tổng quát”; bài tiếng Việt giữ từ generic và nói generic subprogram, generic package — nghĩa vẫn là “mang tính generic”.
| Ký hiệu trong bài | Tiếng Anh | Nghĩa |
|---|---|---|
| generic unit | generic unit | Khai báo bắt đầu bằng generic. Tên chung cho generic subprogram và generic package |
| formal parameter | generic formal parameter | Đối số phía nhận, viết giữa generic và thân khai báo. Là kiểu, giá trị, subprogram, hoặc package |
| formal part | generic formal part | Chính đoạn liệt kê formal parameter. Có thể đọc là “chỗ viết hợp đồng” |
| actual parameter | generic actual parameter | Kiểu, giá trị, subprogram, package thực sự truyền lúc instantiate |
| instantiation | instantiation | Dùng new để tạo subprogram hoặc package thường từ generic unit |
| box | <> |
Tên gọi ký hiệu <> trong Ada. Như range <>, nghĩa là “kiểu cụ thể chốt lúc instantiate” |
| contract model | contract model | Cách của Ada: viết body chỉ trong phạm vi lời hứa ở formal parameter, rồi kiểm kiểu body độc lập (chương 13) |
Trong văn bản ta viết đúng ký pháp Ada range <>, digits <>, nhưng trong hình thôi ta viết range box, digits box. Mermaid không vẽ nhãn chứa <> nguyên si; nghĩa vẫn như văn bản. Như bảng trên, chính Ada gọi <> là box, nên ký hiệu trong hình không lệch thuật ngữ Ada.
3. Môi trường chạy và cách biên dịch
Mã trong bài giả định GNAT 15.x trở lên. GNAT là trình biên dịch đại diện của Ada, cài được từ Alire. Alire là package manager của Ada / SPARK, cũng dùng để quản lý toolchain và build.
gnat --version
# GNAT 15.2.1
Cài GNAT từ Alire (package manager của Ada) bằng alr install gnat_native gprbuild, rồi đưa binary vào PATH.
Sample trong bài được đặt trong repo như sau.
flowchart TB
accTitle: Bố cục sample của bài viết
accDescr: Thư mục ada-generic-programming chứa src/snippets với sáu file .ada và README.md
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]
Sample gộp nhiều compilation unit trong một file thì tách bằng gnatchop rồi mới gnatmake.
mkdir work
cd work
gnatchop ../src/snippets/01_swap.ada
gnatmake -gnata swap_demo
./swap_demo
-gnata bật assertion. Không bắt buộc chỉ để dùng generic, nhưng với sample học thì dễ kiểm hợp đồng và điều kiện biên.
sequenceDiagram
accTitle: Quy trình biên dịch sample
accDescr: gnatchop tách file .ada rồi gnatmake biên dịch và chạy file thực thi
participant Dev as Nhà phát triển
participant Chop as gnatchop
participant Build as gnatmake
participant Exe as File thực thi
Dev->>Chop: Đưa một file .ada
Chop-->>Dev: Tách thành .ads / .adb / main
Dev->>Build: gnatmake -gnata main
Build-->>Dev: Bind và link xong
Dev->>Exe: ./main
Exe-->>Dev: Kết quả chạy
4. Mô hình cơ bản của generic Ada
Generic Ada dễ nắm nếu nghĩ thành ba bước.
- Viết generic unit
- Viết formal parameter ở phần
generic - Phía dùng instantiate bằng
new
flowchart LR
accTitle: Mô hình cơ bản của generic Ada
accDescr: Khai báo generic, formal parameter, body, rồi instantiation bằng new để dùng như subprogram hoặc package thường
G["khai báo generic"] --> F["formal parameter"]
F --> B["generic body"]
B --> I["instantiation bằng new"]
I --> U["Dùng như subprogram hoặc package thường"]
F --> F1["Kiểu"]
F --> F2["Giá trị"]
F --> F3["Subprogram"]
F --> F4["Package"]
Ví dụ, biến thao tác hoán đổi hai giá trị thành generic thì chỉ cần kiểu làm formal parameter.
generic
type Element is private;
procedure Generic_Swap (A, B : in out Element);
Ở thời điểm này Generic_Swap chưa gọi được. Đây là “template hoán đổi dùng được với mọi kiểu Element”. Chỉ khi đưa kiểu cụ thể, nó mới thành procedure thường.
procedure Swap_Integer is new Generic_Swap (Integer);
Vẽ quan hệ thì như sau.
flowchart TB
accTitle: Instantiation Generic_Swap theo từng kiểu
accDescr: Cùng template Generic_Swap tạo Swap_Integer, Swap_Character, Swap_My_Record tùy kiểu truyền vào
Template[Generic_Swap<br/>type Element is private] -->|"Truyền Integer"| SwapInt[Swap_Integer]
Template -->|"Truyền Character"| SwapChar[Swap_Character]
Template -->|"Truyền My_Record"| SwapRecord[Swap_My_Record]
SwapInt --> ICall["Hoán đổi biến Integer"]
SwapChar --> CCall["Hoán đổi biến Character"]
SwapRecord --> RCall["Hoán đổi biến My_Record"]
Điểm quan trọng: template body chỉ viết bằng thao tác được phép trên Element. Khi khai type Element is private;, gán và so sánh bằng thì dùng được, nhưng so sánh lớn/nhỏ hay số học thì không. Bản thân khai báo generic đã nói “linh kiện này được phép giả định gì”.
5. Các loại formal parameter — Từ vựng của generic Ada
Generic Ada không chỉ nhận kiểu. Đây là điểm khác lớn so với generic thông thường của C# hay Java.
flowchart TB
accTitle: Bốn loại formal parameter của generic Ada
accDescr: Generic Ada nhận formal parameter kiểu, giá trị, subprogram, và package chứ không chỉ kiểu
P[generic formal parameters] --> T["Type parameter"]
P --> O["Object / value parameter"]
P --> S["Subprogram parameter"]
P --> PKG["Package parameter"]
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 ...]
Trong hình, box chính là <> (box compound delimiter) của Ada. Mermaid không vẽ <> nguyên si nên trong hình thôi ta viết box.
Các formal parameter tiêu biểu, xếp bảng thì như sau.
| Loại | Ví dụ | Nghĩa |
|---|---|---|
| Type parameter | type Element is private; |
Dạng cơ bản nhận mọi kiểu definite, không limited |
| Limited type parameter | type Element is limited private; |
Cũng nhận kiểu không copy được |
| Discrete type | type Index is (<>); |
Kiểu dùng làm chỉ số mảng, như integer hay enumeration |
| Signed integer | type Count is range <>; |
Được giả định +, -, so sánh lớn/nhỏ, số học nguyên |
| Modular integer | type Word is mod <>; |
Bit và số nguyên theo modulo |
| Floating-point | type Real is digits <>; |
Float, Long_Float, floating-point do người dùng định nghĩa, v.v. |
| Fixed-point | type Money is delta <>; |
Số học fixed-point |
| Value parameter | Max_Size : Positive; |
Cố định kích thước, ngưỡng, v.v. theo từng instance |
| Subprogram | with function Predicate (...) return Boolean; |
Tiêm hành vi như hàm so sánh hay predicate |
| Package | with package P is new Some_Generic (<>); |
Nhận generic package đã instantiate như một linh kiện |
Vì có bộ từ vựng này, Ada viết tự nhiên theo kiểu “chỉ nhận kiểu làm được thao tác này”, chứ không phải “nhận mọi thứ rồi làm điều nguy hiểm bên trong”.
6. Generic subprogram — Hiểu cấu trúc tối thiểu với Generic_Swap
Ví dụ đầu: Generic_Swap hoán đổi hai biến thuộc kiểu bất kỳ.
6.1 Phần spec
generic
type Element is private;
procedure Generic_Swap (A, B : in out Element);
Phần sau generic là formal parameter. Ở đây nhận một kiểu tên Element. is private nghĩa là, nhìn từ generic body, không biết biểu diễn nội bộ của kiểu đó.
Khai báo này cho biết hai điều.
Generic_Swapdùng được với mọi kiểuElement- Body không phụ thuộc cấu trúc nội bộ hay so sánh lớn/nhỏ của
Element
6.2 Phần body
procedure Generic_Swap (A, B : in out Element) is
Temp : constant Element := A;
begin
A := B;
B := Temp;
end Generic_Swap;
Body này chỉ dùng gán trên Element. Không có A < B hay A + B. Vì thế mọi kiểu gán được — Integer, Character, record, enumeration — đều dùng tự nhiên.
flowchart LR
accTitle: Luồng hoán đổi Generic_Swap
accDescr: Temp nhận A, A nhận B, rồi B nhận Temp, hai biến đổi chỗ sau lời gọi
subgraph Before["Trước khi gọi"]
A1["A = 10"]
B1["B = 20"]
end
A1 --> T["Temp = A"]
B1 --> A2["A = B"]
T --> B2["B = Temp"]
subgraph After["Sau khi gọi"]
A2["A = 20"]
B2["B = 10"]
end
6.3 Instantiation
Phía dùng thì dùng new.
procedure Swap_Int is new Generic_Swap (Integer);
procedure Swap_Char is new Generic_Swap (Character);
Swap_Int và Swap_Char giờ gọi được như procedure thường.
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;
Kết quả chạy như sau.
Before: X= 10, Y= 20
After : X= 20, Y= 10
Không thể đưa biến Float vào Swap_Int (X, Y);. Swap_Int đã là procedure thường, instantiate cho Integer. Generic không phải “lỗ nhận mọi thứ”, mà là “cơ chế tạo ra thứ cụ thể, an toàn, theo từng kiểu”.
7. Generic package — Tham số hóa kiểu và giá trị
Khi muốn tái sử dụng không chỉ một subprogram mà cả cụm thao tác cùng trạng thái nội bộ, dùng generic package. Ví dụ điển hình là stack.
Với stack, chỉ cần đổi kiểu phần tử và kích thước tối đa; logic cơ bản vẫn giống.
flowchart TB
accTitle: Generic_Stack nhận kiểu và kích thước
accDescr: Generic_Stack lấy Element_Type và Max_Size rồi sinh các instance stack Integer, Float, Unbounded_String
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 Phần spec
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;
Ở đây dùng hai loại formal parameter.
Element_Typelà type parameterMax_Sizelà value parameter
Max_Size thuộc Positive, nên không instantiate được với kích thước 0 trở xuống. Value parameter cũng mang ràng buộc từ kiểu như vậy.
7.2 Phần 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;
Điểm quan trọng của package body: Data và Top được tạo riêng từng instance.
package Int_Stack is new Generic_Stack (Integer, 5);
package Float_Stack is new Generic_Stack (Float, 3);
Hai cái này sinh từ cùng template nhưng không chia sẻ trạng thái nội bộ.
flowchart LR
accTitle: Mỗi instance stack có trạng thái riêng
accDescr: Int_Stack và Float_Stack sinh từ cùng Generic_Stack nhưng không chia sẻ Top hay Data
Template[Generic_Stack] --> IntStack[Int_Stack]
Template --> FloatStack[Float_Stack]
subgraph I["Trạng thái của Int_Stack"]
ITop[Top]
IData["Data : mảng Integer"]
end
subgraph F["Trạng thái của Float_Stack"]
FTop[Top]
FData["Data : mảng Float"]
end
IntStack --> I
FloatStack --> F
7.3 Chuyển trạng thái của stack
Stack dễ hiểu nếu nhìn như máy trạng thái.
stateDiagram-v2
accTitle: Máy trạng thái của stack
accDescr: Stack chuyển Empty, NonEmpty, Full, Overflow, Underflow theo Push và Pop
[*] --> Empty
Empty --> NonEmpty: Push
NonEmpty --> NonEmpty: Push / Pop
NonEmpty --> Empty: Pop lấy phần tử cuối
NonEmpty --> Full: Push tới Max_Size
Full --> NonEmpty: Pop
Full --> Overflow: Push
Empty --> Underflow: Pop
7.4 Ví dụ dùng
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 và chạy theo cùng bước ở chương 3.
gnatchop ../src/snippets/02_stack.ada
gnatmake -gnata stack_demo
./stack_demo
Size= 3
Pop = 30
Pop = 20
Size= 1
Khoảng trắng một ký tự bên phải = là vì 'Image của kiểu nguyên đặt một khoảng trắng trước giá trị không âm. Push ba lần, Pop hai lần, nên Size cuối là 1. Nếu stack đầy (trong ví dụ này là 5 phần tử, Max_Size) mà vẫn Push thì Int_Stack.Stack_Overflow được raise; nếu rỗng mà Pop thì Int_Stack.Stack_Underflow.
Generic package trong thực tế hay phát huy ở “container nhỏ”, “buffer độ dài cố định”, “ring buffer”, “hàng đợi log”, “hardware abstraction layer”. Đặc biệt với Ada, thiết kế cố định kích thước tĩnh bằng type hoặc value parameter, hơn là đẩy kích thước lúc runtime, hợp với hệ thống độ tin cậy cao.
8. Formal subprogram parameter — Tiêm hành vi
Chỉ nhận kiểu thì vẫn còn điều chưa biểu đạt được. Sort chẳng hạn không chỉ cần kiểu phần tử mà còn cần logic so sánh: “cái nào đứng trước”.
Trong Ada, hàm so sánh đó có thể là formal parameter của generic.
flowchart LR
accTitle: Formal parameter của Generic_Insertion_Sort
accDescr: Sort generic nhận kiểu phần tử, index, mảng, và hàm so sánh để đổi thứ tự tăng hoặc giảm
A[Generic_Insertion_Sort] --> T[Item_Type]
A --> I[Index]
A --> ARR[Item_Array]
A --> CMP["Hàm so sánh"]
CMP --> ASC["Dùng so sánh chuẩn"]
CMP --> DESC["Truyền Greater để giảm dần"]
CMP --> CUSTOM["Truyền thứ tự riêng"]
8.1 Phần spec
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);
Có bốn formal parameter.
Item_Type: kiểu phần tử mảngIndex: kiểu chỉ số mảngItem_Array: kiểu mảng thực sự"<": hàm so sánh
type Index is (<>); nhận discrete type. Không chỉ integer mà cả enumeration. Chỗ mang dấu Ada: chỉ số mảng không chỉ Positive mà còn enumeration như Day.
is <> trong with function "<" ... is <>; nghĩa là, nếu bỏ actual parameter, dùng toán tử chuẩn đang visible hoặc hàm khớp. Kiểu như Integer vốn đã có < thì không cần nêu hàm so sánh.
8.2 Phần 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 không hợp mảng lớn, nhưng hợp để giải thích generic: chỉ thay hàm so sánh là cùng cấu trúc vòng lặp dùng được cho cả tăng dần và giảm dần.
flowchart TB
accTitle: Luồng insertion sort
accDescr: Lấy Key từ trái sang, dịch phần tử đứng trước khi Key đứng trước, rồi chèn Key cho đến hết mảng
Start["Mảng chưa sắp"] --> Pick["Lần lượt lấy Key từ trái"]
Pick --> Compare{"Key đứng trước phần tử ngay trước?"}
Compare -->|Yes| Shift["Dịch phần tử ngay trước sang phải"]
Shift --> Compare
Compare -->|No| Insert["Chèn Key"]
Insert --> Done{"Xử lý hết chưa?"}
Done -->|No| Pick
Done -->|Yes| End["Mảng đã sắp"]
8.3 Tạo tăng dần và giảm dần từ cùng một body
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 dùng < chuẩn. Sort_Desc thì "<" => Greater, thay hàm so sánh.
flowchart LR
accTitle: Cùng body, hai thứ tự nhờ đổi hàm so sánh
accDescr: Sort_Asc dùng so sánh chuẩn, Sort_Desc truyền Greater, nên cùng dữ liệu ra hai thứ tự ngược nhau
Data[99, 3, 47, 12] --> A[Sort_Asc<br/>so sánh chuẩn]
Data --> D[Sort_Desc<br/>truyền Greater làm hàm so sánh]
A --> AO[3, 12, 47, 99]
D --> DO[99, 47, 12, 3]
Cơ chế này gần với việc truyền comparison function object làm template argument trong C++, hoặc đòi thứ tự bằng trait bound trong Rust. Khác là Ada nêu tường minh, như formal subprogram parameter, rằng “truyền hàm dạng này”.
8.4 Ví dụ chạy
Theo cấu trúc chương 18, nếu đặt Generic_Insertion_Sort ở file riêng (generic_insertion_sort.ads / .adb), phía gọi như sau.
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
Hai procedure sinh từ cùng body Generic_Insertion_Sort đảo thứ tự chỉ vì thay hàm so sánh. Khoảng trắng trước mỗi phần tử là vì 'Image của kiểu nguyên đặt một khoảng trắng trước giá trị không âm.
9. Type category — Viết hợp đồng cụ thể hơn private
type T is private; tiện, nhưng không làm được mọi thứ. Trên kiểu private, không mặc nhiên dùng bốn phép tính hay so sánh lớn/nhỏ. Vì thế Ada cho phép chỉ định category trên formal type parameter.
flowchart TB
accTitle: Các category của formal type
accDescr: Formal type có thể là private, limited private, discrete, range, mod, digits, delta, hoặc access
FormalType[Formal Type] --> Private[private]
FormalType --> Limited[limited private]
FormalType --> Discrete["discrete box: discrete type"]
FormalType --> Signed["range box: signed integer"]
FormalType --> Modular["mod box: modular integer"]
FormalType --> Float["digits box: floating-point"]
FormalType --> Fixed["delta box: fixed-point"]
FormalType --> Access["kiểu access"]
Discrete --> Enum["Enumeration"]
Discrete --> Int["Integer"]
Float --> F1[Float]
Float --> F2[Long_Float]
Float --> F3["Floating-point do người dùng định nghĩa"]
Trong hình, box chính là <> (box compound delimiter) của Ada. Mermaid không vẽ <> nguyên si nên trong hình thôi ta viết box.
9.1 Chỉ định category thì được gì
Ví dụ tính trung bình hay phương sai cần cộng, trừ, nhân, chia. Kiểu private không cho giả định các phép đó. Vì thế ta giới hạn ở 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;
type Real is digits <>; cho biết Real là floating-point. Generic body vì thế dùng được +, -, *, /.
9.2 Phần 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 Dùng với Float và 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);
Cùng xử lý thống kê tái sử dụng được trên floating-point khác độ chính xác.
flowchart LR
accTitle: Generic_Statistics cho nhiều độ chính xác
accDescr: Cùng statistics package instantiate cho Float, Long_Float, và Real do người dùng định nghĩa
Stats[Generic_Statistics<br/>Real is digits box] --> FS[Float_Stats]
Stats --> LS[Long_Stats]
Stats --> MS[My_Real_Stats]
FS --> FCalc["Mean / Variance trên Float"]
LS --> LCalc["Mean / Variance trên Long_Float"]
MS --> MCalc["Mean / Variance trên Real do người dùng định nghĩa"]
9.4 Ví dụ chạy
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 là thư viện chuẩn instantiate Ada.Text_IO.Float_IO cho Float. Exp => 0 in dạng thập phân thường chứ không phải dạng mũ; Aft => 3 là số chữ số phần thập phân. Lưu ý Variance này là phương sai tổng thể (chia cho Values'Length), không phải phương sai mẫu.
9.5 Chỉ định category là “đặc tả cấp kiểu”
Category không chỉ là cú pháp để khiến compiler im lặng. Với người đọc, nó còn là đặc tả: linh kiện này đòi gì.
| Xử lý muốn viết | Formal type hợp | Lý do |
|---|---|---|
| Hoán đổi, lưu, lấy ra | private |
Gán được là đủ |
| Quản lý tài nguyên không copy được | limited private |
Không giả định gán |
| Chỉ số mảng, duyệt trạng thái enumeration | (<>) |
Dùng được First, Last, Succ, Pred |
| Tổng nguyên, bộ đếm | range <> |
Giả định số học nguyên |
| Bitmask, bộ đếm vòng | mod <> |
Giả định phép modular |
| Trung bình, phương sai, tính số | digits <> |
Giả định số học floating-point |
| Tiền, đại lượng điều khiển với độ chính xác cố định | delta <> |
Giả định số học fixed-point |
10. Tiêm predicate — Viết Count_If theo cách Ada
Formal subprogram parameter không chỉ dùng cho hàm so sánh mà còn cho predicate. Predicate là hàm nhận giá trị rồi trả Boolean.
Vai trò gần Func<T, bool> của C#, Predicate<T> của Java, lambda hay function object của C++ — trong Ada được viết như formal subprogram của generic.
10.1 Phần spec
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;
Ở đây Predicate không gắn is <>. Không có hàm predicate chuẩn nào đang visible, nên thiết kế bắt phía dùng phải truyền.
10.2 Phần 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;
Luồng xử lý đơn giản.
flowchart LR
accTitle: Luồng Generic_Count_If
accDescr: Duyệt mảng, tăng Count khi Predicate đúng, bỏ qua khi sai, rồi trả Count
Arr["Mảng"] --> Loop["Duyệt từng phần tử"]
Loop --> P{"Predicate(Item)?"}
P -->|True| Inc["Tăng Count"]
P -->|False| Skip["Không làm gì"]
Inc --> Next["Sang phần tử tiếp"]
Skip --> Next
Next --> Result["Trả Count"]
10.3 Đếm số chẵn và đếm theo ngưỡng
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);
Từ cùng logic duyệt, tạo hai hàm chỉ khác điều kiện.
flowchart TB
accTitle: Cùng vòng lặp, hai predicate khác nhau
accDescr: Count_Even đếm số chẵn, Count_Large đếm phần tử lớn hơn 50, cùng Generic_Count_If
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["Số phần tử chẵn"]
L --> LR["Số phần tử lớn hơn 50"]
Trong ví dụ này, duyệt mảng, quản lý bộ đếm, trả kết quả đều chung. Chỉ “đếm cái gì” được tiêm vào như hàm. Đó là dạng cơ bản của thiết kế higher-order trong Ada.
10.4 Ví dụ chạy
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
Trong 9 phần tử của Data, số chẵn là 12, 88, 56, 44, 62 — 5 cái; lớn hơn 50 là 88, 56, 91, 62 — 4 cái.
11. Ghép nhiều tham số — Key-value store tổng quát
Linh kiện thực tế ít khi xong với đúng một type parameter. Kiểu khóa và giá trị, cách so sánh khóa, số mục tối đa — nhiều điều kiện phải ghép.
Ở đây lấy key-value store cố định độ dài, đơn giản, làm ví dụ.
flowchart TB
accTitle: Generic_KV_Store ghép nhiều formal parameter
accDescr: Store nhận Key_Type, Value_Type, hàm so khớp khóa, và Max_Entries cho Put, Get, Contains
KV[Generic_KV_Store] --> K[Key_Type]
KV --> V[Value_Type]
KV --> EQ["Hàm khớp khóa"]
KV --> M[Max_Entries]
KV --> Ops[Put / Get / Contains]
Ops --> Use1["Store giá trị cấu hình"]
Ops --> Use2["Cache quy mô nhỏ"]
Ops --> Use3["Từ điển độ dài cố định cho embedded"]
11.1 Phần spec
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;
Package này có bốn formal parameter.
| Tham số | Loại | Vai trò |
|---|---|---|
Key_Type |
Kiểu | Kiểu khóa |
Value_Type |
Kiểu | Kiểu giá trị |
"=" |
Subprogram | Xác định khóa có khớp |
Max_Entries |
Giá trị | Số mục tối đa |
Max_Entries có mặc định := 50. Không chỉ định thì thành store 50 mục.
11.2 Phần 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;
Cài đặt này là tìm tuyến tính, không dành cho dữ liệu lớn. Nhưng ở chỗ độ dài cố định, quy mô nhỏ, không cấp phát bộ nhớ động là tính chất quan trọng, dạng này dễ dùng.
sequenceDiagram
accTitle: Luồng Put và Get của KV store
accDescr: Put ghi đè khi khóa đã có, cấp slot mới khi khóa mới, Get tìm index rồi trả giá trị
participant App as Phía gọi
participant Store as Instance Generic_KV_Store
participant Data as Keys/Values/Used
App->>Store: Put(Key, Value)
Store->>Data: Find_Index(Key)
alt Khóa đã có
Store->>Data: Values(Pos) := Value
else Khóa mới
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 Ví dụ instantiation
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 trên khóa đã có sẽ ghi đè
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)
Gọi Get mà không Contains trước thì, khi không có khóa, Key_Not_Found được raise. Chọn một trong hai: rẽ nhánh bằng Contains như trên, hoặc viết exception handler.
"=" được lược bỏ. Integer có toán tử bằng chuẩn, và is <> dùng toán tử đó.
Nếu khóa là chuỗi không phân biệt hoa thường chẳng hạn, truyền hàm bằng riêng.
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. Formal package parameter — Biến generic thành linh kiện nhỏ hơn
Generic Ada cho phép chính package làm formal parameter. Nhờ đó, “một instance sinh từ generic package nào đó” trở thành đầu vào của generic khác.
flowchart LR
accTitle: Logger nhận instance của Generic_Stack
accDescr: Int_Stack instantiate từ Generic_Stack rồi được truyền vào Generic_Stack_Logger
GS[Generic_Stack] --> IS[Int_Stack]
IS --> Logger[Generic_Stack_Logger]
Logger --> Logged[Int_Stack_Logger_Instance]
12.1 Logger nhận stack
Ví dụ: nhận instance của Generic_Stack ở trên, rồi làm logger in kích thước.
generic
with package Stack is new Generic_Stack (<>);
package Generic_Stack_Logger is
procedure Print_Size;
end Generic_Stack_Logger;
Body như sau.
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;
Phía dùng thì tạo stack trước, rồi đưa stack đó cho 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);
Thiết kế này cho phép ghép các linh kiện generic với nhau.
flowchart TB
accTitle: Generic lồng nhau qua formal package
accDescr: Tầng một tạo Int_Stack, tầng hai nhận stack đó để tạo logger và API Print_Size
subgraph Layer1["Tầng 1"]
T1[Generic_Stack] --> I1[Int_Stack]
end
subgraph Layer2["Tầng 2"]
T2[Generic_Stack_Logger] --> I2[Int_Stack_Logger]
end
I1 --> T2
I2 --> API[Print_Size]
Công dụng gần template template parameter của C++, nhưng Ada nêu được tường minh: “nhận instance của generic package này”. Trong mã Ada lớn, tiện khi tách rồi ghép container, thuật toán, log, kiểm tra, hỗ trợ kiểm thử.
13. Contract Model — Ý tưởng quan trọng nhất của generic Ada
Để hiểu generic Ada, điểm then chốt là contract model.
Generic body chỉ được viết bằng thao tác mà formal parameter đã hứa. Ví dụ chỉ khai type Element is private; thì không được dùng < trên Element. Muốn dùng < thì phải nêu như formal subprogram, hoặc làm type category cụ thể hơn.
flowchart TB
accTitle: Contract model của generic Ada
accDescr: Formal part là hợp đồng, body được kiểm kiểu độc lập, instantiation kiểm actual có thỏa hợp đồng không
Spec[generic formal part<br/>hợp đồng] --> Body[generic body<br/>cài trong phạm vi hợp đồng]
Body --> Check1["Kiểm kiểu body độc lập"]
Spec --> Inst[instantiation]
Actual[actual parameters<br/>kiểu, hàm, giá trị thực] --> Inst
Inst --> Check2["Kiểm actual có thỏa hợp đồng"]
Check2 --> Instance["Package/subprogram thường"]
Thiết kế này bảo vệ cả người dùng generic lẫn người viết generic.
13.1 Khác biệt bề ngoài với template C++
Template C++ mạnh, nhưng trước đây có tính chất “chỉ khi instantiate template body thì mới ra lỗi”. C++20 concepts đã cải thiện, còn generic Ada từ đầu là mô hình nêu hợp đồng tường minh.
flowchart LR
accTitle: Ada viết hợp đồng trước, template C++ cụ thể hóa lúc instantiate
accDescr: Ada kiểm body trong phạm vi formal part rồi kiểm actual lúc instantiate; C++ trước đây cụ thể hóa lúc instantiate dù concepts đã cải thiện
subgraph Ada[Ada]
A1["Viết hợp đồng ở formal part"] --> A2["body được kiểm trong hợp đồng"]
A2 --> A3["instantiation kiểm actual"]
end
subgraph CPP[C++ templates]
C1["Viết template body"] --> C2["Yêu cầu cụ thể hóa lúc instantiate"]
C2 --> C3["Có thể nêu ràng buộc bằng concepts"]
end
Generic Java hay C# lấy quan hệ với reference type, constraint, type erasure, biểu diễn lúc runtime làm trung tâm thiết kế. Generic Ada nghiêng về việc tạo instance cụ thể lúc biên dịch.
| Góc nhìn | Ada | C++ | Java | Rust |
|---|---|---|---|---|
| Cách viết hợp đồng | Viết kiểu, giá trị, hàm, package ở formal part | templates / concepts | Type parameter và bounds | trait bounds |
| Kiểm body | Kiểm trong hợp đồng của formal parameter | Lấy cụ thể hóa lúc instantiate làm trung tâm | Kiểm trong bounds | Kiểm trong trait bounds |
| Chi phí runtime | Phân giải tĩnh là mặc định | Sinh tĩnh là mặc định | Chịu ảnh hưởng type erasure | Monomorphization là mặc định |
| Value parameter | Có | Có | Hạn chế | const generics |
| Subprogram làm formal parameter | Có | Biểu đạt bằng function object, v.v. | Lambda / functional interface | Closure / hàm / trait |
| Package làm formal parameter | Có | Template template, v.v. | Không | Tách khỏi cấu trúc module |
Từng ngôn ngữ khác nhau ở chi tiết, nhưng đặc trưng Ada là “viết hợp đồng bằng cú pháp trước”.
14. Quyết định thiết kế thực tiễn — Cái gì nên thành generic
Generic tiện, nhưng không phải cái gì cũng nên biến thành generic. Trong thực tế, phán đoán như sau thì ít thất bại.
flowchart TB
accTitle: Quyết định cái gì nên thành generic
accDescr: Khác mỗi kiểu thì type parameter, khác kích thước thì value parameter, khác hành vi thì formal subprogram, có trạng thái thì generic package
Start["Có xử lý muốn tái sử dụng"] --> Q1{"Chỉ khác kiểu?"}
Q1 -->|Yes| GType["Xem type parameter"]
Q1 -->|No| Q2{"Kích thước hay ngưỡng cũng khác?"}
Q2 -->|Yes| GObject["Thêm value parameter"]
Q2 -->|No| Q3{"Hành vi so sánh hay kiểm điều kiện cũng khác?"}
Q3 -->|Yes| GSubp["Thêm formal subprogram"]
Q3 -->|No| Q4{"Muốn gom trạng thái nội bộ và API?"}
Q4 -->|Yes| GPackage["Generic package"]
Q4 -->|No| Normal["Subprogram thường là đủ"]
14.1 Chỗ hợp generic subprogram
Generic subprogram hợp thuật toán không giữ trạng thái.
SwapSortCount_IfFind- Biến đổi kiểu
Map Min/Max
Khi body thuật toán ngắn, đầu vào và đầu ra rõ, subprogram dễ đọc hơn package.
14.2 Chỗ hợp generic package
Generic package hợp khi muốn mang nhiều thao tác và trạng thái nội bộ cùng với kiểu.
- Stack độ dài cố định
- Ring buffer
- Từ điển quy mô nhỏ
- Bộ xử lý thống kê
- Trừu tượng hóa I/O theo thiết bị
- Bộ phép toán trên kiểu số có hệ đơn vị
Đặc biệt Ada tách package spec là public API, package body là cài đặt, nên generic package dùng được như “module template an toàn kiểu”.
flowchart LR
accTitle: Spec công khai, body ẩn, formal part là hợp đồng
accDescr: Formal part ràng spec và body, phía dùng chỉ thấy public API của package spec
Spec[package spec<br/>public API] --> User["Phía dùng"]
Body[package body<br/>cài đặt nội bộ] -.ẩn.-> User
Formal[generic formal part<br/>hợp đồng kiểu, giá trị, hàm] --> Spec
Formal --> Body
14.3 Bắt đầu với ít formal parameter
Formal parameter quá nhiều thì instantiation khó đọc. An toàn là bắt đầu tối thiểu, chỉ tăng khi xuất hiện lý do cần thay.
-- Ví dụ dễ trở nên khó đọc
package X is new Generic_Foo
(A, B, C, D, E, F, G);
-- Giữ ý định bằng named association
package X is new Generic_Foo
(Element_Type => Integer,
Index_Type => Positive,
Buffer_Size => 128,
"<" => Less);
Ada cho named association lúc instantiate. Điểm thiết kế quan trọng của generic hiện ra ở instantiation, nên mã thực tế thường dễ bảo trì hơn nếu viết theo tên.
15. Những chỗ dễ vấp
Generic Ada mạnh, nhưng có điểm dễ vấp lúc mới dùng.
15.1 Kiểu private không so sánh lớn/nhỏ được
Body như sau không viết được.
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 -- lỗi tại đây
return A;
else
return B;
end if;
end Bad_Min;
Element chỉ được khai là private, nên không chắc đã có <. Muốn so sánh thì thêm vào hợp đồng như sau.
generic
type Element is private;
with function "<" (Left, Right : Element) return Boolean is <>;
function Generic_Min (A, B : Element) return Element;
flowchart LR
accTitle: So sánh trên private phải được ghi vào formal part
accDescr: Muốn so sánh trong body thì viết hàm so sánh vào formal part; chỉ private sẽ lỗi biên dịch
Need["Muốn so sánh trong body"] --> Contract["Viết hàm so sánh ở formal part"]
Contract --> OK["Lúc instantiate xác nhận có so sánh được"]
Need --> NoContract["Chỉ private"]
NoContract --> Error["Lỗi biên dịch trong generic body"]
15.2 is <> không phải “suy luận tự động mọi thứ”
is <> tiện nhưng không phải phép màu. Tại điểm instantiate, toán tử hoặc subprogram khớp phải đang visible. Nếu hàm so sánh riêng nằm ở package khác, with / use cho đúng, hoặc truyền tường minh theo tên, thì an toàn.
procedure Sort_By_Age is new Generic_Insertion_Sort
(Item_Type => Person,
Index => Positive,
Item_Array => Person_Array,
"<" => Younger_Than);
15.3 Exception cũng khác nhau theo từng instance
Exception khai báo trong spec của generic package trở thành exception riêng từng instance.
package Int_Stack is new Generic_Stack (Integer, 5);
package Float_Stack is new Generic_Stack (Float, 3);
Khi đó Int_Stack.Stack_Overflow và Float_Stack.Stack_Overflow được coi là hai exception khác nhau. Nếu muốn một exception chung, cân nhắc định nghĩa exception bên ngoài generic.
flowchart TB
accTitle: Exception khai báo trong generic là riêng từng instance
accDescr: Int_Stack.Stack_Overflow và Float_Stack.Stack_Overflow là hai exception khác nhau
Generic[Generic_Stack<br/>khai báo Stack_Overflow] --> I[Int_Stack.Stack_Overflow]
Generic --> F[Float_Stack.Stack_Overflow]
I -.exception khác.-> F
15.4 Kích thước mã có thể tăng
Generic dễ tránh indirection thừa lúc runtime, nhưng vì mỗi kiểu sinh một instance, nhiều instantiation có thể làm tăng kích thước mã.
Đây là trade-off cũng thấy ở template C++ và monomorphization của Rust. Phát triển nghiêng độ tin cậy cao, embedded, real-time thì chấp nhận quản kích thước sản phẩm lúc build, đổi lấy việc giảm bất định lúc runtime.
flowchart LR
accTitle: Mỗi instantiation sinh mã riêng
accDescr: Một generic body sinh bản Integer, Float, Long_Float, My_Type, tránh boxing lúc runtime nhưng có thể tăng kích thước
Generic["Một generic body"] --> I1["Bản Integer"]
Generic --> I2["Bản Float"]
Generic --> I3["Bản Long_Float"]
Generic --> I4["Bản My_Type"]
I1 --> Code["Mã sinh ra"]
I2 --> Code
I3 --> Code
I4 --> Code
Code --> Pros["Dễ tránh kiểm kiểu hay boxing lúc runtime"]
Code --> Cons["Nhiều instance thì chú ý tăng kích thước"]
15.5 Chỗ nên dùng limited private
type Element is private; giả định gán. File handle, lock, device handle — thứ không muốn bị copy — thì cân nhắc limited private.
generic
type Resource is limited private;
with procedure Close (R : in out Resource);
procedure Generic_Use_And_Close (R : in out Resource);
Thiết kế kiểu không copy được thì, hơn là container lưu giá trị, thuật toán áp dụng procedure hoặc thiết kế nêu tường minh tham chiếu thường an toàn hơn.
16. Tập mẫu thiết kế nhỏ
Từ đây, sắp xếp ngắn các dạng hay dùng trong thực tế.
16.1 Chỉ cung cấp Min cho giá trị so sánh được
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
accTitle: Generic_Min đòi hàm so sánh
accDescr: Element cộng hàm so sánh tạo Generic_Min, trả về giá trị nhỏ hơn
T[Element] --> C["Cần hàm so sánh"]
C --> M[Generic_Min]
M --> R["Trả về cái nhỏ hơn"]
16.2 Lấy ngưỡng làm value parameter
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;
Value parameter hợp giá trị muốn cố định như tính chất của instance, không phải giá trị cấu hình lúc runtime.
16.3 Tiêm phương tiện xuất
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;
Dạng này cho phép thay đích xuất: stdout, log, buffer kiểm thử, v.v.
flowchart TB
accTitle: Tiêm thủ tục Put để đổi đích xuất
accDescr: Generic_Print_Twice nhận Put rồi có thể in console, log, hoặc buffer kiểm thử
Print[Generic_Print_Twice] --> Put["Nhận Put như formal subprogram"]
Put --> Console["Xuất console"]
Put --> Log["Xuất log"]
Put --> Test["Buffer kiểm thử"]
16.4 Không cố định kiểu chỉ số mảng
Trong Ada, kiểu chỉ số mảng cũng là thông tin kiểu quan trọng. Đừng cố định Positive; khi cần, biến cả kiểu chỉ số thành formal parameter thì tính tái sử dụng tăng.
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);
Thiết kế này không chỉ mảng chỉ số Positive mà còn mảng chỉ số enumeration.
flowchart LR
accTitle: Không cố định kiểu index của mảng
accDescr: Formal Index kiểu discrete nhận Positive, enumeration Day hoặc State, và kiểu số nguyên riêng
Index[Index is discrete] --> Positive[Positive range]
Index --> Day["Enumeration Day"]
Index --> State["Enumeration State"]
Index --> Slot["Kiểu nguyên riêng"]
17. Danh sách kiểm tra để API mang phong cách Ada
Khi viết generic, soi lại theo các góc sau thì dễ đọc hơn.
flowchart TB
accTitle: Danh sách kiểm tra thiết kế generic Ada
accDescr: Kiểm formal tối thiểu, phép toán đã nêu trong formal part, category, named association, exception theo instance, kích thước mã, và instance kiểm thử
C["Kiểm thiết kế generic"] --> C1["Formal parameter đã tối thiểu?"]
C --> C2["Phép toán cần thiết đã nêu ở formal part?"]
C --> C3["Category private / range / digits đã phù hợp?"]
C --> C4["Instantiate được dễ đọc bằng named association?"]
C --> C5["Đã ý thức trạng thái và exception theo từng instance?"]
C --> C6["Có chấp nhận tăng kích thước mã?"]
C --> C7["Đã chuẩn bị instance cho kiểm thử?"]
Viết thành câu thì như sau.
- Thao tác dùng trong body phải hiện ra như hợp đồng của formal parameter.
privateđủ thì đểprivate. Cần số học thì dùngrange <>hoặcdigits <>.- Hành vi đổi theo kiểu — so sánh, hash, xuất, chuyển đổi — thì để formal subprogram.
- Kích thước hay ngưỡng là tính chất của instance thì để value parameter.
- Có trạng thái thì nghĩ generic package trước; không trạng thái thì generic subprogram.
- Instantiation càng nhiều đối số càng nên named association.
- Thiết kế trên giả định exception và trạng thái nội bộ độc lập theo từng instance.
18. Ví dụ cấu trúc tổng thể của sample
Nếu tách sample trong bài thành file, cấu trúc như sau thì dễ đọc.
flowchart TB
accTitle: Tách spec, body, và demo thành file
accDescr: Thư mục src/generics giữ ads và adb, src/demos giữ các chương trình demo
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]
Sample nhỏ cho bài viết thì gộp một file rồi gnatchop cũng tiện, nhưng nghĩ tới thực tế và bảo trì dài hạn thì tách spec .ads và body .adb mới là cấu trúc mang phong cách Ada.
19. Kết luận — Xác định ranh giới tái sử dụng bằng kiểu
Lập trình generic trong Ada không chỉ là “tính năng viết mã không phụ thuộc kiểu”. Bản chất hơn là nêu tường minh, như hợp đồng của kiểu, subprogram và giá trị, rằng linh kiện tái sử dụng đòi gì.
flowchart LR
accTitle: Từ hợp đồng tới tái sử dụng an toàn kiểu
accDescr: Viết hợp đồng, viết generic body, instantiate với kiểu giá trị hàm, rồi dùng an toàn kiểu mà không copy mã
Contract["Viết hợp đồng"] --> Generic["Viết generic body"]
Generic --> Instance["Instantiate với kiểu, giá trị, hàm"]
Instance --> Safe["Dùng an toàn kiểu"]
Safe --> Reuse["Tái sử dụng không copy"]
Như bài đã thấy, generic Ada nhận các formal parameter sau.
- Kiểu
- Giá trị
- Subprogram
- Package
Với kiểu, còn chỉ định category khá mịn: private, limited private, range <>, mod <>, digits <>, delta <>, (<>). Nhờ đó generic body không phụ thuộc thao tác “không biết có làm được không”, mà cài an toàn chỉ bằng thao tác đã ghi trong hợp đồng.
Tài sản C hay C++ cũ đôi khi tái sử dụng bằng macro, void*, function pointer, rẽ nhánh kiểu viết tay. Generic Ada thay được nhiều chỗ đó bằng dạng an toàn kiểu, dễ đọc. Đặc biệt bảo trì dài hạn, embedded, real-time, phần mềm độ tin cậy cao: thiết kế “chốt ranh giới lúc biên dịch” mang giá trị lớn.
20. Lĩnh vực tư vấn liên quan
KomuraSoft LLC nhận phát triển ứng dụng Windows, khảo sát và chỉnh sửa tài sản hiện có, sắp xếp ranh giới COM / ActiveX / 32-bit / 64-bit, tư vấn kỹ thuật và rà soát thiết kế. Không chỉ thiết kế nghiêng static typing và độ tin cậy cao như Ada; cách sắp xếp, kéo dài vòng đời, rồi di chuyển tài sản C/C++, C#, VB6, MFC, COM hiện có cũng là chủ đề gần trong thực tế.
- Phát triển ứng dụng Windows
- Phát triển thành phần COM
- Thay thế ứng dụng Windows
- Tư vấn kỹ thuật và rà soát thiết kế
- Liên hệ với chúng tôi
Tài liệu tham khảo
- Ada 2022 Language Reference Manual, Section 12: Generic Units — Chính quy định generic unit. 12.1 là generic declaration, 12.3 là instantiation, 12.4 là formal object (value parameter), 12.5 là formal type (category như
private,range <>,digits <>), 12.6 là formal subprogram, 12.7 là formal package. Lúc phân vân ở từng chương của bài, tra đúng mục tương ứng là chắc nhất. - Ada 2022 Language Reference Manual, 2.2: Lexical Elements, Separators, and Delimiters — Chỗ định nghĩa
<>là delimiter ghép gọi là “box”. Đây là căn cứ cho ký hiệuboxtrong hình. - GNAT User’s Guide for Native Platforms — Cách dùng
gnatmake,gnatchop, và danh sách tùy chọn biên dịch gồm-gnata. Khi kẹt ở bước chương 3 thì xem đây. - Alire Documentation — Cách cài
alr, quản lý toolchain (GNAT / gprbuild), và tạo crate. Chỉ dựng môi trường thì tài liệu này là đủ. - Sample code (GitHub) — Sample của bài, tách file theo cấu trúc chương 18.
Bài viết liên quan
Các bài viết gần đây có cùng thẻ để tìm hiểu sâu hơn những chủ đề lân cận.
Xử lý đồng thời an toàn trong Ada — Hướng dẫn thực hành task và protected object
Bài nhập môn về xử lý đồng thời gắn sẵn trong ngôn ngữ Ada: task và protected object. Hệ thống hóa rendezvous (entry/accept), selective a...
Lập trình hệ thống real-time bằng Ada — thực hành ưu tiên, chu kỳ và kiểm soát thời gian thực thi
Học Annex D (Real-Time Systems) của Ada qua 8 ví dụ mã thực hành. Bài viết sắp xếp từng bước từ ưu tiên task, Ceiling_Locking, thực thi t...
Bên trong ảo hóa Windows (Phần 3) — Máy ảo khởi động trong vài giây: Vì sao WSL2, Windows Sandbox và container lại nhẹ
Vì sao WSL2 và Windows Sandbox khởi động trong vài giây và cảm giác nhẹ đến vậy? Bài viết này giải thích các cơ chế, từ ảnh cơ sở động và...
Bên trong ảo hóa Windows (Phần 2) — Bộ nhớ ngay cả kernel cũng không thấy: VBS, HVCI và Credential Guard
Khi cài sạch trên phần cứng tương thích, VBS được bật mặc định và dùng hypervisor cùng SLAT để tạo cô lập mạnh hơn kernel. Bài viết này g...
Bên trong ảo hóa Windows (Phần 1) — Windows của bạn thực sự chạy ở đâu? Hypervisor và phân vùng
Khi bạn bật Hyper-V, chính Windows máy chủ chạy trên hypervisor như phân vùng gốc. Bài viết này giải thích nền tảng ảo hóa qua vai trò củ...
Chủ đề liên quan
Các trang này đặt chủ đề trong bối cảnh rộng hơn của dịch vụ và quyết định.
Chủ đề kỹ thuật Windows
Cổng vào phát triển Windows, điều tra lỗi và khai thác tài sản hiện có.
Câu hỏi thường gặp
Các câu hỏi thường gặp khi tư vấn về chủ đề của bài viết.
- Generic trong Ada là gì?
- Đó là cơ chế tái sử dụng nhận kiểu, giá trị, subprogram, thậm chí cả package làm formal parameter, rồi kiểm tra kiểu tĩnh tại thời điểm instantiation bằng new. Không phải thay thế văn bản: lúc biên dịch đã chốt được "linh kiện này có thỏa hợp đồng này không". Generic subprogram chỉ khai báo thì chưa gọi được; phải đưa kiểu cụ thể để instantiate thì mới dùng như procedure hay function thường.
- Generic Ada khác template C++ ở điểm nào?
- Ada từ đầu dùng contract model: ghi hợp đồng tường minh. Generic body chỉ được dùng thao tác mà formal parameter đã hứa, và bản thân body được kiểm kiểu độc lập. Template C++ trước đây chỉ lộ lỗi khi instantiate; C++20 concepts đã cải thiện điều đó. Ngoài value parameter và subprogram parameter, Ada còn cho phép chính package làm formal parameter, nên các linh kiện generic ghép được với nhau.
- Vì sao formal type parameter của Ada lại chỉ định type category?
- Để nêu tường minh, như một hợp đồng, những thao tác mà body được phép dùng. Với type T is private chỉ được giả định thao tác cơ bản như gán và so sánh bằng; so sánh lớn/nhỏ hay số học thì không. Cần số học nguyên thì range <>, cần floating-point thì digits <>, cần bit thì mod <>. Category còn là đặc tả cấp kiểu cho người đọc: linh kiện này đòi gì.
- Generic Ada có điểm nào cần chú ý?
- Vì mỗi kiểu sinh một instance, nhiều instantiation có thể làm tăng kích thước mã. Đây là trade-off cũng thấy ở template C++ và monomorphization của Rust. Exception khai báo trong spec của generic package cũng thành exception riêng từng instance. Hướng dẫn thực tiễn: bắt đầu với formal parameter tối thiểu, chỉ tăng khi có lý do cần thay, và dùng named association khi instantiation có nhiều đối số.