1. מבוא — לא «לקבל הכול», אלא «על מה מתחייבים»
בשפה עם טיפוסים סטטיים, ברגע שמנסים לכתוב קוד שאפשר לעשות לו reuse, נתקלים באותה בעיה. stack שנכתב ל-integers רוצים להשתמש בו גם למחרוזות. אותו עיבוד סטטיסטי רוצים להריץ גם על מערך של floating-point. לוגיקת מיון בסדר עולה רוצים גם לסדר יורד. אם מעתיקים את אותו קוד לכל טיפוס, תיקונים מתפספסים. אם מקבלים הכול עם void* או עם cast, type safety נשברת.
התשובה של Ada היא generics (generic units).
generics ב-Ada אינם החלפת טקסט. הם מקבלים טיפוסים, ערכים, subprograms, ואפילו packages שלמים כ-formal parameters, והטיפוסים נבדקים סטטית ברגע ה-instantiation. כלומר לא בודקים בזמן ריצה «האם הטיפוס הזה באמת בסדר», אלא קובעים בזמן קומפילציה «האם הרכיב הזה מקיים את ה-contract».
flowchart LR
accTitle: שלוש דרכים ל-reuse בקוד עם טיפוסים סטטיים
accDescr: copy-paste נוטה לתיקונים חסרים, void או cast שוברים type safety, ו-generics של Ada נותנים בדיקה בקומפילציה בלי dispatch נוסף בזמן ריצה.
A["עיבוד שרוצים לעשות לו reuse"] --> B{"איך עושים reuse?"}
B --> C["copy-paste"]
B --> D["void* / Object / cast"]
B --> E["generics של Ada"]
C --> C1["קל לפספס תיקונים"]
D --> D1["שגיאות runtime ושבירת טיפוסים"]
E --> E1["type-safe"]
E --> E2["בדיקה בזמן קומפילציה"]
E --> E3["בלי dispatch נוסף בזמן ריצה"]
במאמר הזה מסודר generic programming ב-Ada לפי הסדר הבא.
- generic subprograms
- generic packages
- type parameters, value parameters, subprogram parameters
- קטגוריות טיפוס כמו
private,range <>,digits <> - דוגמאות מימוש של מיון, stack, סטטיסטיקה,
Count_If, ו-key-value store - higher-order generics דרך formal package parameters
- ה-contract model של Ada וחשיבה עיצובית בפועל
1.1 למי זה מיועד, ומה לוקחים מהמאמר
המאמר מכוון לאנשים כאלה.
- מי שעבד עם templates ב-C++, generics ב-C# או Java, או generics ב-Rust
- מי שלא שולט בתחביר של Ada, אבל מתעניין בעיצוב שבו כותבים חוזה בטיפוסים
- מי שבתחזוקה ארוכת טווח, embedded, או high-integrity שוקל איך לתכנן רכיבים ל-reuse
בתחביר של Ada מספיק להכיר packages (פיצול specification ב-.ads ו-body ב-.adb) ואת מצבי הפרמטרים in / out / in out. אם שני אלה לא יציבים, כדאי קודם לקרוא את «מה מושך בשפת Ada».
מה שלוקחים מכאן הוא פחות התחביר עצמו, ויותר הרגל עיצוב: לפני המימוש כותבים בטיפוסים מה רכיב ה-reuse דורש. אותו רעיון עוזר גם כשמחליטים עד כמה לכתוב מגבלות interface ב-C# או concepts ב-C++20.
1.2 מדריך קריאה — אין חובה לקרוא הכול
יש במאמר 20 פרקים. אפשר לקרוא לפי מטרה, בלי לעבור על הכול ברצף.
| מטרה | פרקים לקריאה |
|---|---|
| לתפוס את הרעיון בזמן קצר | פרק 4 (המודל הבסיסי) → פרק 6 (generic subprogram מינימלי) → פרק 13 (contract model) |
| לכתוב רכיבים בעצמך | פרק 4 → פרק 6 → פרק 7 (generic package) → פרק 8 (הזרקת behavior) → פרק 9 (קטגוריות טיפוס) |
| רק הנחיות להחלטות עיצוב | פרק 13 → פרק 14 (מה להפוך ל-generic) → פרק 15 (מלכודות) → פרק 17 (checklist) |
| לשים ידיים על קוד | בונים סביבה בפרק 3, ואז מהדוגמאות המוכנות בפרקים 6 ו-7 |
המסלול הקצר הוא שלושת הפרקים 4, 6 ו-13. בהם מתכנסים הליבה: formal parameters, instantiation, ו-contract model. פרקים 5 ו-9 הם רשימת formal parameters; אפשר לגשת אליהם כמילון כשצריך.
הנושא ממשיך את סדרת הבלוג «מה מושך בשפת Ada», «מבוא לאימות פורמלי עם SPARK», «concurrency בטוח», «מערכות real-time». כאן חופרים את הרעיון של Ada «הטיפוסים מדברים עיצוב» מזווית ה-generics.
knowledge map של המאמר
generics ב-Ada הם מנגנון reuse שמקבל טיפוסים, ערכים, subprograms ו-packages כ-formal parameters, ונבדק סטטית ברגע ה-instantiation עם new. generic subprogram ו-generic package הם שני עמודי התווך של המנגנון הזה: formal type parameters מציינים בקטגוריות כמו private, range <> ו-digits <> את חוזה הפעולות שהגוף רשאי להשתמש בהן, formal subprogram parameters מזריקים behavior כמו השוואה או predicate, ו-formal package parameters מקבלים כרכיב generic package שכבר עבר instantiation. ה-contract model בודק את הגוף לבד, רק בגבול ה-formal parameters, ולכן ב-Ada לא מתרחשת באופן מבני הבעיה ש-templates של C++ נשאו היסטורית, שבה השגיאה מתגלה רק ב-instantiation. עם GNAT ו-Alire אפשר לנסות את המנגנון הזה בסביבה חינמית.
flowchart LR
accTitle: knowledge map: generic programming ב-Ada
accDescr: תרשים שמראה ש-generic subprogram ו-generic package נשענים על חוזה (contract model) של formal parameters מסוג טיפוס, ערך, subprogram ו-package, שהחוזה הזה נבדק ב-instantiation עם new, ואת ההבדל מבעיית השגיאה שמתגלה רק ב-instantiation ש-templates של C++ נשאו היסטורית.
ada_generics["Ada generics"]
ada_generic_contract_model["contract model של Ada generics"]
ada["Ada"]
ada_generic_subprogram["generic subprogram"]
ada_generic_package["generic package"]
ada_generic_formal_type["formal type"]
ada_generic_formal_subprogram["formal subprogram"]
ada_generic_formal_package["formal package"]
ada_generic_instantiation["instantiation"]
ada_generic_formal_object["formal object (פרמטר ערך)"]
late_instantiation_error["שגיאת template שמתגלה רק ב-instantiation"]
cpp_templates["C++ templates"]
gnat["GNAT"]
alire["Alire"]
ada -->|"מממש את"| ada_generics
ada_generics -->|"משתמש ב"| ada_generic_subprogram
ada_generics -->|"משתמש ב"| ada_generic_package
ada_generic_subprogram -.->|"דורש"| ada_generic_formal_type
ada_generics -->|"משתמש ב"| ada_generic_formal_subprogram
ada_generics -->|"משתמש ב"| ada_generic_formal_package
ada_generics -->|"דורש"| ada_generic_instantiation
ada_generic_formal_type -->|"מוגדר ב"| ada_generic_instantiation
ada_generic_package -.->|"דורש"| ada_generic_formal_type
ada_generic_package -.->|"דורש"| ada_generic_formal_object
ada_generic_formal_subprogram -->|"מממש את"| ada_generic_contract_model
ada_generic_formal_type -->|"מממש את"| ada_generic_contract_model
ada_generic_contract_model -->|"מונע"| late_instantiation_error
cpp_templates -.->|"עלול לגרום ל"| late_instantiation_error
ada_generic_formal_package -->|"דורש"| ada_generic_instantiation
gnat -->|"מממש את"| ada
gnat -.->|"מוגדר ב"| alire
ada_generic_subprogram -.->|"משתמש ב"| ada_generic_formal_subprogram
ada_generic_package -.->|"משתמש ב"| ada_generic_formal_subprogram
ada_generic_instantiation -.->|"דורש"| ada_generic_formal_object
ada_generic_formal_object -->|"מממש את"| ada_generic_contract_model
ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 21, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle
2. מפת המאמר
קודם תופסים את התמונה כולה באיור. אם מבינים generics ב-Ada רק כ«פיצ’ר שמקבל טיפוס כארגומנט», זה מבט צר מדי. בפועל מחברים, לפי יחידת ה-reuse, subprograms, packages, subprogram parameters, value parameters, ו-formal package parameters.
mindmap
root((Ada Generics))
Generic Subprogram
Swap
Count_If
Sort
Generic Package
Stack
Statistics
KV Store
Formal Parameters
Type
private
limited private
range box
mod box
digits box
delta box
discrete box
Object
Max_Size
Threshold
Subprogram
Less function
Equals function
Predicate
Package
with package P is new Generic
Design Ideas
Contract Model
Static Checking
Zero-Cost Abstraction
Separate Specification and Body
באיורים, box הוא הסימן <> של Ada (box compound delimiter). Mermaid לא מצייר <> כמו שהוא, ולכן רק באיורים כותבים box.
דרך הקריאה פשוטה. במחצית הראשונה עוקבים אחרי התחביר, במחצית השנייה אחרי החלטות עיצוב. אם זו קריאה ראשונה ב-Ada, אל תנסו בהתחלה לשנן תחביר דק. שימו לב ל«מה הפך ל-formal parameter» ול«אילו פעולות מותרות על הפרמטר הזה».
2.1 מילון קצר
מילים שחוזרות בהמשך, בעברית ובאנגלית. במאמר נשארים עם המונח באנגלית generic, כמו שמהנדסים בישראל אומרים.
| במאמר | English | משמעות |
|---|---|---|
| generic unit | generic unit | הצהרה שמתחילה ב-generic. שם כולל ל-generic subprogram ול-generic package |
| formal parameter | generic formal parameter | הארגומנט בצד המקבל, בין generic לגוף ההצהרה. טיפוס, ערך, subprogram, או package |
| formal part | generic formal part | הרשימה עצמה של ה-formal parameters. אפשר לקרוא לזה «המקום שבו כותבים את ה-contract» |
| actual parameter | generic actual parameter | הטיפוס, הערך, ה-subprogram או ה-package שמועברים בפועל ב-instantiation |
| instantiation | instantiation | יצירת subprogram או package רגיל מ-generic unit באמצעות new |
| box | <> |
השם של הסימן <> ב-Ada. ב-range <> וכדומה: הטיפוס הממשי נקבע ב-instantiation |
| contract model | contract model | השיטה של Ada: כותבים את הגוף רק בגבול ההבטחות שב-formal parameters, ובודקים את הגוף לבד (פרק 13) |
בגוף המאמר נכתוב כמו ב-Ada, range <> ו-digits <>, ורק באיורים range box ו-digits box. Mermaid לא מצייר תוויות שמכילות <>, והמשמעות זהה לגוף. כמו בטבלה, Ada עצמה קוראת ל-<> box, כך שהסימון באיור לא זז ממונחי השפה.
3. סביבת הרצה ואיך מקמפלים
הקוד במאמר מכוון ל-GNAT 15.x ומעלה. GNAT הוא הקומפיילר המייצג של Ada, ומתקינים אותו מ-Alire. Alire הוא package manager של Ada / SPARK, ומשמש גם לניהול toolchain ולבנייה.
gnat --version
# GNAT 15.2.1
מתקינים GNAT מ-Alire (package manager של Ada) עם alr install gnat_native gprbuild, ומוסיפים ל-PATH.
הדוגמאות במאמר מיועדות לשבת ב-repository כך.
flowchart TB
accTitle: מבנה קבצי הדוגמאות במאמר
accDescr: תחת ada-generic-programming נמצאים src/snippets עם קבצי ada ל-swap, stack, sort, statistics, filter ו-key-value store, וגם README.
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]
דוגמה שמאגדת כמה compilation units בקובץ אחד מפצלים קודם ב-gnatchop ואז בונים ב-gnatmake.
mkdir work
cd work
gnatchop ../src/snippets/01_swap.ada
gnatmake -gnata swap_demo
./swap_demo
-gnata מפעיל assertions. זה לא חובה כדי להשתמש ב-generics עצמם, אבל בדוגמאות לימוד קל יותר לבדוק contracts ותנאי גבול.
sequenceDiagram
accTitle: זרימת gnatchop ואז gnatmake
accDescr: מעבירים קובץ ada אחד ל-gnatchop, מקבלים ads adb ו-main, בונים עם gnatmake gnata, ומריצים את הקובץ הבינארי.
participant Dev as מפתח
participant Chop as gnatchop
participant Build as gnatmake
participant Exe as קובץ הרצה
Dev->>Chop: מעבירים קובץ .ada אחד
Chop-->>Dev: פיצול ל-.ads / .adb / main
Dev->>Build: gnatmake -gnata main
Build-->>Dev: bind ו-link עד הסוף
Dev->>Exe: ./main
Exe-->>Dev: פלט הריצה
4. המודל הבסיסי של generics ב-Ada
נוח לחשוב על generics ב-Ada בשלושה שלבים.
- כותבים generic unit
- בחלק ה-
genericכותבים formal parameters - בצד המשתמש עושים instantiation עם
new
flowchart LR
accTitle: שלושת שלבי generic ב-Ada
accDescr: הצהרת generic, formal parameters, גוף, ואז instantiation עם new שנותן subprogram או package רגיל.
G["הצהרת generic"] --> F["formal parameters"]
F --> B["גוף ה-generic"]
B --> I["instantiation עם new"]
I --> U["שימוש כ-subprogram או package רגיל"]
F --> F1["טיפוס"]
F --> F2["ערך"]
F --> F3["subprogram"]
F --> F4["package"]
למשל, אם הופכים החלפת שני ערכים ל-generic, אפשר לקחת רק את הטיפוס כ-formal parameter.
generic
type Element is private;
procedure Generic_Swap (A, B : in out Element);
בשלב הזה אי אפשר עדיין לקרוא ל-Generic_Swap. זה «תבנית להחלפה שעובדת לכל טיפוס Element». רק אחרי שנותנים טיפוס ממשי זה הופך ל-procedure רגיל.
procedure Swap_Integer is new Generic_Swap (Integer);
באיור היחס נראה כך.
flowchart TB
accTitle: instantiation של Generic_Swap לפי טיפוס
accDescr: אותה תבנית Generic_Swap מקבלת Integer, Character או My_Record ויוצרת procedure נפרד לכל אחד.
Template["Generic_Swap<br/>type Element is private"] -->|מעבירים Integer| SwapInt[Swap_Integer]
Template -->|מעבירים Character| SwapChar[Swap_Character]
Template -->|מעבירים My_Record| SwapRecord[Swap_My_Record]
SwapInt --> ICall["מחליף משתני Integer"]
SwapChar --> CCall["מחליף משתני Character"]
SwapRecord --> RCall["מחליף משתני My_Record"]
הנקודה החשובה: גוף התבנית נכתב רק עם הפעולות שמותרות על Element. אם הצהרתם type Element is private;, אפשר השמה והשוואה לשוויון, אבל לא השוואת גודל ולא אריתמטיקה. כלומר ההצהרה של ה-generic עצמה אומרת «מה הרכיב רשאי להניח».
5. סוגי formal parameters — אוצר המילים של generics ב-Ada
ב-generics של Ada מקבלים לא רק טיפוסים. זה הבדל גדול מול generics רגילים ב-C# וב-Java.
flowchart TB
accTitle: ארבעת סוגי ה-formal parameters
accDescr: טיפוס, אובייקט או ערך, subprogram, ו-package, עם דוגמאות כמו private, digits box, Max_Size ו-with package.
P[generic formal parameters] --> T["פרמטר טיפוס"]
P --> O["אובייקט / פרמטר ערך"]
P --> S["פרמטר subprogram"]
P --> PKG["פרמטר package"]
T --> T1[type Element is private]
T --> T2[type Index is box]
T --> T3[type Real is digits box]
O --> O1[Max_Size : Positive]
O --> O2[Default_Value : Element]
S --> S1[with function Less...]
S --> S2[with procedure Put ...]
PKG --> P1[with package P is new ...]
באיורים, box הוא הסימן <> של Ada (box compound delimiter). Mermaid לא מצייר <> כמו שהוא, ולכן רק באיורים כותבים box.
ה-formal parameters הטיפוסיים בטבלה.
| סוג | דוגמה | משמעות |
|---|---|---|
| פרמטר טיפוס | type Element is private; |
הצורה הבסיסית: כל טיפוס definite שאינו limited |
| פרמטר טיפוס limited | type Element is limited private; |
מקבל גם טיפוסים שאי אפשר להעתיק |
| discrete type | type Index is (<>); |
integers, enumerations, וטיפוסים שמשמשים כאינדקס למערך |
| signed integer | type Count is range <>; |
אפשר להניח +, -, השוואת גודל ואריתמטיקה של integers |
| modular integer | type Word is mod <>; |
פעולות על bits ואריתמטיקה מודולרית |
| floating-point | type Real is digits <>; |
Float, Long_Float, וטיפוסי floating-point שהמשתמש הגדיר |
| fixed-point | type Money is delta <>; |
אריתמטיקה של fixed-point |
| פרמטר ערך | Max_Size : Positive; |
מקבעים גודל או סף לכל instance |
| subprogram | with function Predicate (...) return Boolean; |
מזריקים השוואה, predicate, וכדומה |
| package | with package P is new Some_Generic (<>); |
מקבלים כרכיב generic package שכבר עבר instantiation |
עם אוצר המילים הזה, ב-Ada כותבים באופן טבעי «מקבלים רק טיפוסים שיש להם את הפעולות האלה», ולא «מקבלים הכול ועושים משהו מסוכן בפנים».
6. generic subprogram — מבינים את המינימום עם Generic_Swap
כדוגמה ראשונה, Generic_Swap שמחליף שני משתנים מכל טיפוס.
6.1 Specification
generic
type Element is private;
procedure Generic_Swap (A, B : in out Element);
מה שאחרי generic הוא ה-formal parameters. כאן מקבלים טיפוס בשם Element. is private אומר שמנקודת המבט של גוף ה-generic, הייצוג הפנימי של הטיפוס לא ידוע.
מההצהרה הזו ברורים שני דברים.
Generic_Swapעובד לכל טיפוסElement- הגוף לא תלוי במבנה הפנימי של
Elementולא בהשוואת גודל
6.2 Body
procedure Generic_Swap (A, B : in out Element) is
Temp : constant Element := A;
begin
A := B;
B := Temp;
end Generic_Swap;
בגוף הזה משתמשים ב-Element רק בהשמה. אין A < B ואין A + B. לכן זה עובד באופן טבעי לכל טיפוס שאפשר להשים: Integer, Character, records, enumerations.
flowchart LR
accTitle: החלפת שני ערכים ב-Generic_Swap
accDescr: לפני הקריאה A הוא 10 ו-B הוא 20; אחרי השמה דרך Temp, A הוא 20 ו-B הוא 10.
subgraph Before["לפני הקריאה"]
A1[A = 10]
B1[B = 20]
end
A1 --> T[Temp = A]
B1 --> A2[A = B]
T --> B2[B = Temp]
subgraph After["אחרי הקריאה"]
A2[A = 20]
B2[B = 10]
end
6.3 Instantiation
בצד המשתמש כותבים new.
procedure Swap_Int is new Generic_Swap (Integer);
procedure Swap_Char is new Generic_Swap (Character);
מכאן Swap_Int ו-Swap_Char הם procedures רגילים שאפשר לקרוא להם.
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;
פלט לדוגמה:
Before: X= 10, Y= 20
After : X= 20, Y= 10
אי אפשר להעביר ל-Swap_Int (X, Y); משתנה מסוג Float. Swap_Int הוא procedure רגיל שעבר instantiation ל-Integer. generics אינם «חור שמקבל הכול»; הם מנגנון שמייצר לכל טיפוס דבר ממשי ובטוח.
7. generic package — טיפוס וערך כפרמטרים
כשרוצים reuse לא של subprogram אחד אלא של כמה פעולות יחד עם מצב פנימי, משתמשים ב-generic package. הדוגמה הקלאסית היא stack.
ב-stack, אם משנים רק את טיפוס האיבר ואת הגודל המקסימלי, הלוגיקה הבסיסית זהה.
flowchart TB
accTitle: Generic_Stack לפי טיפוס וקיבולת
accDescr: אותו Generic_Stack מקבל Element_Type ו-Max_Size ויוצר Int_Stack, Float_Stack ו-String_Stack עם אותן פעולות Push Pop Size.
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 Specification
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;
כאן שני סוגים של formal parameters.
Element_Typeהוא פרמטר טיפוסMax_Sizeהוא פרמטר ערך
Max_Size הוא Positive, ולכן אי אפשר לעשות instantiation עם גודל 0 או שלילי. גם לפרמטר ערך יש מגבלת טיפוס.
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;
בגוף ה-package חשוב ש-Data ו-Top נוצרים בנפרד לכל instance.
package Int_Stack is new Generic_Stack (Integer, 5);
package Float_Stack is new Generic_Stack (Float, 3);
שניהם נוצרים מאותה תבנית, אבל המצב הפנימי אינו משותף.
flowchart LR
accTitle: מצב נפרד לכל instance של Generic_Stack
accDescr: Int_Stack ו-Float_Stack נוצרים מאותו Generic_Stack אבל לכל אחד Top ומערך Data משלו.
Template[Generic_Stack] --> IntStack[Int_Stack]
Template --> FloatStack[Float_Stack]
subgraph I["המצב של Int_Stack"]
ITop[Top]
IData["Data : מערך Integer"]
end
subgraph F["המצב של Float_Stack"]
FTop[Top]
FData["Data : מערך Float"]
end
IntStack --> I
FloatStack --> F
7.3 מעברי המצב של ה-stack
נוח לראות stack כ-state machine.
stateDiagram-v2
accTitle: מעברי מצב של stack
accDescr: Empty, NonEmpty ו-Full מתחלפים ב-Push ו-Pop; Push מלא מוביל ל-Overflow ו-Pop ריק ל-Underflow.
[*] --> Empty
Empty --> NonEmpty: Push
NonEmpty --> NonEmpty: Push / Pop
NonEmpty --> Empty: Pop מוציא את האיבר האחרון
NonEmpty --> Full: Push מגיע ל-Max_Size
Full --> NonEmpty: Pop
Full --> Overflow: Push
Empty --> Underflow: Pop
7.4 דוגמת שימוש
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;
בונים ומריצים באותו סדר כמו בפרק 3.
gnatchop ../src/snippets/02_stack.ada
gnatmake -gnata stack_demo
./stack_demo
Size= 3
Pop = 30
Pop = 20
Size= 1
הרווח אחרי = מגיע מכך ש-'Image של טיפוס integer שם תו רווח אחד לפני ערך שאינו שלילי. אחרי 3 פעמים Push ופעמיים Pop, ה-Size האחרון הוא 1. אם קוראים ל-Push כשה-stack מלא (כאן 5, ה-Max_Size) נזרק Int_Stack.Stack_Overflow; אם קוראים ל-Pop כשהוא ריק נזרק Int_Stack.Stack_Underflow.
generic packages עובדים טוב בפועל ב«container קטן», «buffer באורך קבוע», «ring buffer», «תור לוגים», ו«שכבת הפשטה לחומרה». ב-Ada במיוחד, עיצוב שמקבע גודל סטטית כטיפוס או כפרמטר ערך, במקום להזיז אותו בזמן ריצה, יושב טוב עם מערכות high-integrity.
8. formal subprogram — מזריקים behavior
גם אחרי שמקבלים טיפוס, יש דברים שאי אפשר לבטא. במיון, למשל, צריך לא רק את טיפוס האיבר אלא גם את לוגיקת ההשוואה: מי בא קודם.
ב-Ada אפשר לקחת את פונקציית ההשוואה עצמה כ-formal parameter של ה-generic.
flowchart LR
accTitle: פרמטרי Generic_Insertion_Sort כולל פונקציית השוואה
accDescr: ה-generic מקבל Item_Type, Index, Item_Array ופונקציית השוואה, ואפשר סדר עולה, יורד, או סדר מותאם.
A[Generic_Insertion_Sort] --> T[Item_Type]
A --> I[Index]
A --> ARR[Item_Array]
A --> CMP["פונקציית השוואה"]
CMP --> ASC["השוואה רגילה"]
CMP --> DESC["מעבירים Greater לסדר יורד"]
CMP --> CUSTOM["מעבירים סדר משלכם"]
8.1 Specification
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);
כאן ארבעה formal parameters.
Item_Type: טיפוס איברי המערךIndex: טיפוס האינדקס של המערךItem_Array: טיפוס המערך עצמו"<": פונקציית ההשוואה
type Index is (<>); מקבל discrete type. לא רק integers, גם enumerations. זה אופייני ל-Ada: אינדקס מערך לא חייב להיות Positive; אפשר גם enumeration כמו Day.
with function "<" ... is <>; — ה-is <> אומר: אם לא מעבירים actual parameter, משתמשים באופרטור הסטנדרטי הנראה או בפונקציה תואמת. בטיפוס כמו Integer שכבר יש לו <, אפשר בלי לציין במפורש פונקציית השוואה.
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 לא מתאים למערכים גדולים, אבל מתאים מאוד להסבר על generics. מחליפים רק את פונקציית ההשוואה, ואותו מבנה לולאה עובד לסדר עולה ולסדר יורד.
flowchart TB
accTitle: זרימת insertion sort
accDescr: לוקחים Key משמאל, מזיזים ימינה כל עוד הוא לפני האיבר הקודם, מכניסים אותו, וחוזרים עד שהמערך ממוין.
Start["מערך לא ממוין"] --> Pick["לוקחים Key משמאל לפי הסדר"]
Pick --> Compare{"Key לפני האיבר הקודם?"}
Compare -->|Yes| Shift["מזיזים את האיבר הקודם ימינה"]
Shift --> Compare
Compare -->|No| Insert["מכניסים את Key"]
Insert --> Done{"סיימנו?"}
Done -->|No| Pick
Done -->|Yes| End["מערך ממוין"]
8.3 סדר עולה וסדר יורד מאותו גוף
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 משתמש ב-< הסטנדרטי. Sort_Desc מחליף את ההשוואה עם "<" => Greater.
flowchart LR
accTitle: אותו גוף מיון לסדר עולה וליורד
accDescr: Sort_Asc עם השוואה רגילה נותן 3 12 47 99, ו-Sort_Desc עם Greater נותן 99 47 12 3.
Data[99, 3, 47, 12] --> A["Sort_Asc<br/>השוואה רגילה"]
Data --> D["Sort_Desc<br/>Greater כפונקציית השוואה"]
A --> AO[3, 12, 47, 99]
D --> DO[99, 47, 12, 3]
המנגנון קרוב להעברת function object כארגומנט template ב-C++, או לדרישת סדר עם trait bounds ב-Rust. ב-Ada כותבים במפורש כ-formal subprogram: «מעבירים function בצורה הזו».
8.4 דוגמת הרצה
לפי המבנה בפרק 18, אם Generic_Insertion_Sort יושב בקבצים נפרדים (generic_insertion_sort.ads / .adb), צד הקריאה נראה כך.
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
שני procedures מאותו גוף של Generic_Insertion_Sort מתהפכים רק בהחלפת פונקציית ההשוואה. הרווח לפני כל איבר מגיע מכך ש-'Image של integer שם תו רווח אחד לפני ערך שאינו שלילי.
9. קטגוריות טיפוס — חוזה מדויק יותר מ-private
type T is private; נוח, אבל לא עושה הכול. על טיפוס private אי אפשר להניח ארבע פעולות החשבון או השוואת גודל כאילו הן מובנות מאליהן. לכן ב-Ada אפשר לציין category על formal type parameter.
flowchart TB
accTitle: קטגוריות של formal type
accDescr: private, limited private, discrete, range, mod, digits, delta ו-access, עם פיצול ל-enumeration, integer, Float ו-Long_Float.
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["טיפוס access"]
Discrete --> Enum["enumeration"]
Discrete --> Int["integer"]
Float --> F1[Float]
Float --> F2[Long_Float]
Float --> F3["טיפוס floating-point שהמשתמש הגדיר"]
באיורים, box הוא הסימן <> של Ada (box compound delimiter). Mermaid לא מצייר <> כמו שהוא, ולכן רק באיורים כותבים box.
9.1 מה מרוויחים מציון category
למשל ממוצע ושונות דורשים חיבור, חיסור, כפל וחילוק. על טיפוס private אי אפשר להניח את הפעולות האלה. לכן מצמצמים ל-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 <>; ברור ש-Real הוא טיפוס floating-point. לכן בגוף ה-generic אפשר להשתמש ב-+, -, *, /.
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 שימוש ב-Float וב-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);
אותו עיבוד סטטיסטי עובר reuse על טיפוסי floating-point בדיוק שונה.
flowchart LR
accTitle: Generic_Statistics על כמה דיוקי floating-point
accDescr: אותו package עם Real is digits box יוצר Float_Stats, Long_Stats ו-My_Real_Stats ל-Mean ו-Variance.
Stats["Generic_Statistics<br/>Real is digits box"] --> FS[Float_Stats]
Stats --> LS[Long_Stats]
Stats --> MS[My_Real_Stats]
FS --> FCalc["Mean / Variance ב-Float"]
LS --> LCalc["Mean / Variance ב-Long_Float"]
MS --> MCalc["Mean / Variance ב-Real שהמשתמש הגדיר"]
9.4 דוגמת הרצה
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 הוא ספרייה סטנדרטית: instantiation של Ada.Text_IO.Float_IO ל-Float. עם Exp => 0 מקבלים כתיב עשרוני רגיל במקום צורת מעריך, ו-Aft => 3 הוא מספר ספרות אחרי הנקודה. שימו לב ש-Variance כאן הוא שונות האוכלוסייה (חילוק ב-Values'Length), לא שונות המדגם.
9.5 ציון category הוא מפרט ברמת הטיפוס
ציון category אינו תחביר רק כדי להשתיק את הקומפיילר. לקורא זה מפרט שאומר «מה הרכיב דורש».
| מה רוצים לכתוב | formal type מתאים | למה |
|---|---|---|
| החלפה, שמירה, שליפה | private |
מספיק שאפשר להשים |
| ניהול משאב שאי אפשר להעתיק | limited private |
לא מניחים השמה |
| אינדקס מערך, מעבר על מצבי enumeration | (<>) |
יש First, Last, Succ, Pred |
| סכום integers, מונה | range <> |
אפשר להניח אריתמטיקה של integers |
| bit mask, מונה מעגלי | mod <> |
אפשר להניח אריתמטיקה מודולרית |
| ממוצע, שונות, חישוב מספרי | digits <> |
אפשר להניח אריתמטיקה של floating-point |
| כסף, גדלי בקרה בדיוק קבוע | delta <> |
אפשר להניח אריתמטיקה של fixed-point |
10. הזרקת predicate — Count_If בסגנון Ada
formal subprogram משמש לא רק להשוואה, גם ל-predicate. predicate הוא function שמקבל ערך ומחזיר Boolean.
את התפקיד שקרוב ל-Func<T, bool> ב-C#, ל-Predicate<T> ב-Java, ול-lambda או function object ב-C++, מבטאים ב-Ada כ-formal subprogram של generic.
10.1 Specification
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;
כאן אין is <> על Predicate. אין predicate סטנדרטי שנראה מעצמו, ולכן צד השימוש חייב תמיד להעביר אחד.
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;
הזרימה פשוטה.
flowchart LR
accTitle: ספירה עם Predicate ב-Generic_Count_If
accDescr: עוברים על איברי המערך, מגדילים Count כש-Predicate מחזיר True, ומחזירים את Count.
Arr["מערך"] --> Loop["עוברים על האיברים"]
Loop --> P{"Predicate(Item)?"}
P -->|True| Inc["מגדילים את Count"]
P -->|False| Skip["לא עושים כלום"]
Inc --> Next["לאיבר הבא"]
Skip --> Next
Next --> Result["מחזירים את Count"]
10.3 ספירת זוגיים וספירה מעל סף
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);
מאותה לוגיקת מעבר יוצרים שתי functions ששונות רק בתנאי.
flowchart TB
accTitle: שני instantiations של Generic_Count_If
accDescr: אותם נתונים נספרים ב-Count_Even עם Is_Even וב-Count_Large עם Is_Large.
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["מספר הזוגיים"]
L --> LR["כמה גדולים מ-50"]
בדוגמה הזו, מעבר על המערך, ניהול המונה, והחזרת התוצאה משותפים. רק «מה סופרים» מוזרק כ-function. זו הצורה הבסיסית של עיצוב higher-order ב-Ada.
10.4 דוגמת הרצה
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
מתוך 9 האיברים ב-Data, הזוגיים הם 12, 88, 56, 44, 62 — חמישה; הגדולים מ-50 הם 88, 56, 91, 62 — ארבעה.
11. הרכבת כמה פרמטרים — key-value store גנרי
ברכיב אמיתי לעיתים רחוקות מספיק פרמטר טיפוס אחד. צריך לחבר טיפוס מפתח, טיפוס ערך, איך משווים מפתחות, ומספר רשומות מקסימלי.
כאן דוגמה של key-value store פשוט באורך קבוע.
flowchart TB
accTitle: פרמטרים ושימושים של Generic_KV_Store
accDescr: ה-store מקבל Key_Type, Value_Type, פונקציית שוויון מפתח ו-Max_Entries, ומשמש כמאגר הגדרות, cache קטן, או מילון באורך קבוע ל-embedded.
KV[Generic_KV_Store] --> K[Key_Type]
KV --> V[Value_Type]
KV --> EQ["פונקציית שוויון מפתח"]
KV --> M[Max_Entries]
KV --> Ops[Put / Get / Contains]
Ops --> Use1["מאגר ערכי הגדרה"]
Ops --> Use2["cache קטן"]
Ops --> Use3["מילון באורך קבוע ל-embedded"]
11.1 Specification
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 הזה ארבעה formal parameters.
| פרמטר | סוג | תפקיד |
|---|---|---|
Key_Type |
טיפוס | טיפוס המפתח |
Value_Type |
טיפוס | טיפוס הערך |
"=" |
subprogram | בדיקת שוויון מפתח |
Max_Entries |
ערך | מספר רשומות מקסימלי |
ל-Max_Entries יש ברירת מחדל := 50. אם לא מציינים, מקבלים store של 50 רשומות.
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;
המימוש הוא linear search, ולכן לא מיועד לנפח גדול. אבל במקומות שבהם חשובים אורך קבוע, קנה מידה קטן, ובלי הקצאת זיכרון דינמית, הצורה נוחה.
sequenceDiagram
accTitle: Put ו-Get ב-Generic_KV_Store
accDescr: Put מחפש מפתח קיים או משבצת פנויה ומעדכן Keys Values Used; Get מחפש אינדקס ומחזיר את הערך.
participant App as צד הקריאה
participant Store as instance של Generic_KV_Store
participant Data as Keys/Values/Used
App->>Store: Put(Key, Value)
Store->>Data: Find_Index(Key)
alt יש מפתח קיים
Store->>Data: Values(Pos) := Value
else מפתח חדש
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 דוגמת 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 על מפתח קיים דורס את הערך
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)
אם קוראים ל-Get בלי לבדוק קודם ב-Contains, כשאין מפתח נזרק Key_Not_Found. בחרו לפצל עם Contains כמו בדוגמה, או לכתוב exception handler.
"=" הושמט. ל-Integer יש אופרטור שוויון סטנדרטי, ו-is <> משתמש בו.
אם המפתח הוא מחרוזת בלי הבחנה בין אותיות גדולות לקטנות, אפשר להעביר function שוויון משלכם.
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 — generic כרכיב
ב-generics של Ada אפשר לקחת package שלם כ-formal parameter. כך «instance שנוצר מ-generic package מסוים» הופך לקלט של generic אחר.
flowchart LR
accTitle: Generic_Stack נכנס ל-Generic_Stack_Logger
accDescr: Int_Stack שנוצר מ-Generic_Stack מועבר ל-Generic_Stack_Logger ויוצר instance של לוגר.
GS[Generic_Stack] --> IS[Int_Stack]
IS --> Logger[Generic_Stack_Logger]
Logger --> Logged[Int_Stack_Logger_Instance]
12.1 לוגר שמקבל stack
למשל, לוגר שמקבל instance של Generic_Stack מהפרק הקודם ומדפיס את הגודל.
generic
with package Stack is new Generic_Stack (<>);
package Generic_Stack_Logger is
procedure Print_Size;
end Generic_Stack_Logger;
הגוף:
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;
בצד השימוש יוצרים קודם stack, ואז מעבירים אותו ללוגר.
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);
עם העיצוב הזה מחברים רכיבי generic זה לזה.
flowchart TB
accTitle: שתי שכבות של instantiation
accDescr: בשלב הראשון נוצר Int_Stack מ-Generic_Stack, ובשני Int_Stack_Logger מ-Generic_Stack_Logger עם Print_Size.
subgraph Layer1["שלב 1"]
T1[Generic_Stack] --> I1[Int_Stack]
end
subgraph Layer2["שלב 2"]
T2[Generic_Stack_Logger] --> I2[Int_Stack_Logger]
end
I1 --> T2
I2 --> API[Print_Size]
השימוש קרוב ל-template template parameters ב-C++, אבל ב-Ada כותבים במפורש «מקבלים instance של ה-generic package הזה». בקוד Ada גדול זה נוח כשמפרידים container, אלגוריתם, לוג, בדיקה ועזרים לטסטים, ואז מחברים.
13. Contract model — הרעיון הכי חשוב ב-generics של Ada
כדי להבין generics ב-Ada, חשוב ה-contract model.
גוף ה-generic חייב להיכתב רק עם הפעולות שה-formal parameters מבטיחים. אם הצהרתם רק type Element is private; אי אפשר להשתמש ב-< על Element. אם רוצים <, מוסיפים אותו כ-formal subprogram או מחדדים את קטגוריית הטיפוס.
flowchart TB
accTitle: contract model של generic ב-Ada
accDescr: ה-formal part הוא החוזה, הגוף ממומש בגבולו ונבדק לבד, וב-instantiation בודקים שה-actual parameters מקיימים את החוזה.
Spec["generic formal part<br/>החוזה"] --> Body["generic body<br/>מימוש בגבול החוזה"]
Body --> Check1["בדיקת טיפוסים של הגוף לבד"]
Spec --> Inst["instantiation"]
Actual["actual parameters<br/>טיפוס, function וערך ממשיים"] --> Inst
Inst --> Check2["בדיקה שה-actual parameters מקיימים את החוזה"]
Check2 --> Instance["package או subprogram רגיל"]
העיצוב הזה מגן לא רק על מי שמשתמש ב-generic, גם על מי שכותב אותו.
13.1 איך זה נראה מול templates של C++
templates של C++ חזקים, אבל היסטורית «השגיאה מופיעה רק אחרי שעושים instantiate לגוף ה-template». C++20 concepts שיפרו את זה, אבל generics ב-Ada הם מודל שכותב חוזה במפורש מההתחלה.
flowchart LR
accTitle: Ada מול templates של C++
accDescr: ב-Ada כותבים חוזה ב-formal part ובודקים את הגוף לפני instantiation; ב-C++ הדרישות התגבשו היסטורית ב-instantiation, ועכשיו אפשר concepts.
subgraph Ada[Ada]
A1["כותבים חוזה ב-formal part"] --> A2["הגוף נבדק בגבול החוזה"]
A2 --> A3["ב-instantiation בודקים את ה-actual"]
end
subgraph CPP[C++ templates]
C1["כותבים template body"] --> C2["ב-instantiation מתממשות דרישות"]
C2 --> C3["אפשר לציין מגבלות ב-concepts"]
end
ב-generics של Java ו-C# מרכז העיצוב הוא טיפוסי reference, מגבלות, type erasure, והייצוג בזמן ריצה. generics ב-Ada נוטים יותר ליצירת instance ממשי בזמן קומפילציה.
| זווית | Ada | C++ | Java | Rust |
|---|---|---|---|---|
| איך כותבים חוזה | טיפוס, ערך, function ו-package ב-formal part | templates / concepts | פרמטרי טיפוס ו-bounds | trait bounds |
| בדיקת הגוף | בגבול חוזה ה-formal parameters | בעיקר התממשות ב-instantiation | בגבול ה-bounds | בגבול ה-trait bounds |
| עלות בזמן ריצה | פתרון סטטי כברירת מחדל | יצירה סטטית כברירת מחדל | מושפע מ-type erasure | monomorphization כברירת מחדל |
| פרמטר ערך | יש | יש | מוגבל | const generics |
| subprogram כ-formal parameter | יש | function object וכדומה | lambda / functional interface | closure / function / trait |
| package כ-formal parameter | יש | template template וכדומה | אין | נפרד ממבנה המודולים |
פרטי השפה שונים, אבל הייחוד של Ada הוא «כותבים את החוזה קודם, כתחביר».
14. החלטות עיצוב בפועל — מה כדאי להפוך ל-generic
generics נוחים, אבל לא כל דבר צריך להיות generic. בפועל, השיפוט הבא מפחית כשלונות.
flowchart TB
accTitle: עץ החלטה מה להפוך ל-generic
accDescr: לפי מה שמשתנה בוחרים פרמטר טיפוס, פרמטר ערך, formal subprogram, generic package, או subprogram רגיל.
Start["יש עיבוד שרוצים לעשות לו reuse"] --> Q1{"רק הטיפוס שונה?"}
Q1 -->|Yes| GType["שוקלים פרמטר טיפוס"]
Q1 -->|No| Q2{"גם גודל או סף שונים?"}
Q2 -->|Yes| GObject["מוסיפים פרמטר ערך"]
Q2 -->|No| Q3{"ה-behavior של השוואה או בדיקה שונה?"}
Q3 -->|Yes| GSubp["מוסיפים formal subprogram"]
Q3 -->|No| Q4{"רוצים לארוז מצב פנימי ו-API?"}
Q4 -->|Yes| GPackage["generic package"]
Q4 -->|No| Normal["מספיק subprogram רגיל"]
14.1 מתי generic subprogram מתאים
generic subprogram מתאים לאלגוריתם בלי מצב.
SwapSortCount_IfFind- המרה בסגנון
Map Min/Max
כשגוף האלגוריתם קצר והקלט והפלט ברורים, subprogram קריא יותר מ-package.
14.2 מתי generic package מתאים
generic package מתאים כשרוצים כמה פעולות ומצב פנימי יחד עם הטיפוס.
- stack באורך קבוע
- ring buffer
- מילון קטן
- סט פעולות סטטיסטיות
- הפשטת I/O לפי device
- סט פעולות על טיפוס מספרי עם יחידות
ב-Ada במיוחד, specification של package הוא ה-API הציבורי והגוף הוא המימוש. לכן generic package משמש כ«תבנית מודול type-safe».
flowchart LR
accTitle: פיצול spec, body ו-formal part
accDescr: ה-formal part הוא חוזה הטיפוס הערך וה-function; ה-spec הוא API לקורא; ה-body מוסתר.
Spec["package spec<br/>API ציבורי"] --> User["צד השימוש"]
Body["package body<br/>מימוש פנימי"] -.מוסתר.-> User
Formal["generic formal part<br/>חוזה של טיפוס, ערך ו-function"] --> Spec
Formal --> Body
14.3 מתחילים עם מעט formal parameters
יותר מדי formal parameters הופכים את ה-instantiation לקשה לקריאה. בטוח יותר להתחיל במינימום, ולהוסיף כשיש סיבה אמיתית להחלפה.
-- דוגמה שנהיית קשה לקריאה
package X is new Generic_Foo
(A, B, C, D, E, F, G);
-- named association משאיר את הכוונה
package X is new Generic_Foo
(Element_Type => Integer,
Index_Type => Positive,
Buffer_Size => 128,
"<" => Less);
ב-Ada אפשר named association ב-instantiation. נקודות העיצוב החשובות של generic מופיעות ב-instantiation, ולכן בקוד מעשי named association בדרך כלל קל יותר לתחזק.
15. מלכודות נפוצות
generics ב-Ada חזקים, אבל בהתחלה קל להיתקע בכמה נקודות.
15.1 על טיפוס private אי אפשר להשוות גודל
את הגוף הבא אי אפשר לכתוב.
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 -- כאן תהיה שגיאה
return A;
else
return B;
end if;
end Bad_Min;
Element הוצהר רק כ-private, ולכן אין הבטחה שיש <. אם רוצים השוואה, מוסיפים אותה לחוזה.
generic
type Element is private;
with function "<" (Left, Right : Element) return Boolean is <>;
function Generic_Min (A, B : Element) return Element;
flowchart LR
accTitle: השוואה דורשת חוזה ב-formal part
accDescr: אם הגוף צריך השוואה כותבים function ב-formal part; עם private בלבד הקומפילציה של הגוף נכשלת.
Need["רוצים השוואה בגוף"] --> Contract["כותבים פונקציית השוואה ב-formal part"]
Contract --> OK["ב-instantiation בודקים שאפשר להשוות"]
Need --> NoContract["רק private"]
NoContract --> Error["שגיאת קומפילציה בגוף ה-generic"]
15.2 is <> אינו «inference אוטומטי להכול»
is <> נוח, אבל זה לא קסם. בנקודת ה-instantiation חייב להיות אופרטור או subprogram תואם שנראה. אם פונקציית השוואה ייחודית יושבת ב-package אחר, בטוח יותר לעשות with / use כמו שצריך, או להעביר במפורש בשם.
procedure Sort_By_Age is new Generic_Insertion_Sort
(Item_Type => Person,
Index => Positive,
Item_Array => Person_Array,
"<" => Younger_Than);
15.3 לכל instance יש exception משלו
אם מצהירים exception ב-specification של generic package, לכל instance יש exception נפרד.
package Int_Stack is new Generic_Stack (Integer, 5);
package Float_Stack is new Generic_Stack (Float, 3);
כאן Int_Stack.Stack_Overflow ו-Float_Stack.Stack_Overflow הם exceptions שונים. אם רוצים exception משותף, שוקלים להגדיר אותו מחוץ ל-generic.
flowchart TB
accTitle: exception נפרד לכל instance
accDescr: Stack_Overflow של Int_Stack ושל Float_Stack שנוצרים מאותו Generic_Stack הם exceptions שונים.
Generic["Generic_Stack<br/>הצהרת Stack_Overflow"] --> I[Int_Stack.Stack_Overflow]
Generic --> F[Float_Stack.Stack_Overflow]
I -.exception אחר.-> F
15.4 גודל הקוד עלול לגדול
generics מקלים על הימנעות מ-indirection מיותר בזמן ריצה, אבל כי יוצרים instance לכל טיפוס, הרבה instantiations עלולים להגדיל את גודל הקוד.
זה אותו trade-off כמו templates ב-C++ ו-monomorphization ב-Rust. בפיתוח high-integrity, embedded, או real-time, מקבלים את ניהול גודל הפלט בזמן בנייה בתמורה להפחתת אי-ודאות בזמן ריצה.
flowchart LR
accTitle: כמה instantiations מגדילים קוד
accDescr: גוף generic אחד יוצר גרסאות Integer Float Long_Float ו-My_Type; נמנעים מבדיקת טיפוס בזמן ריצה אבל הקוד גדל.
Generic["גוף generic אחד"] --> I1["גרסת Integer"]
Generic --> I2["גרסת Float"]
Generic --> I3["גרסת Long_Float"]
Generic --> I4["גרסת My_Type"]
I1 --> Code["קוד שנוצר"]
I2 --> Code
I3 --> Code
I4 --> Code
Code --> Pros["קל להימנע מבדיקת טיפוס ומ-boxing בזמן ריצה"]
Code --> Cons["אם יש הרבה instances, לשים לב לגודל"]
15.5 מתי להשתמש ב-limited private
type Element is private; מניח השמה. ל-file handle, lock, device handle, ודברים שאינכם רוצים שיעתיקו, שוקלים limited private.
generic
type Resource is limited private;
with procedure Close (R : in out Resource);
procedure Generic_Use_And_Close (R : in out Resource);
כשמטפלים בטיפוס שאי אפשר להעתיק, בטוח יותר אלגוריתם שמפעיל procedure, או עיצוב שמצהיר על reference, מאשר container ששומר ערכים.
16. כמה דפוסי עיצוב קצרים
מכאן כמה צורות נפוצות בפועל, בקצרה.
16.1 Min רק לערכים שאפשר להשוות
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 דורש פונקציית השוואה
accDescr: Element מגיע עם פונקציית השוואה ל-Generic_Min, שמחזיר את הקטן יותר.
T[Element] --> C["צריך פונקציית השוואה"]
C --> M[Generic_Min]
M --> R["מחזיר את הקטן יותר"]
16.2 סף כפרמטר ערך
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;
פרמטר ערך מתאים לערך שרוצים לקבע כתכונה של ה-instance, לא כהגדרה בזמן ריצה.
16.3 מזריקים אמצעי פלט
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;
בצורה הזו מחליפים יעד פלט: stdout, לוג, buffer לטסטים.
flowchart TB
accTitle: הזרקת Put ב-Generic_Print_Twice
accDescr: Put מגיע כ-formal subprogram ואפשר לכוון אותו לקונסול, ללוג, או ל-buffer של טסט.
Print[Generic_Print_Twice] --> Put["מקבלים Put כ-formal subprogram"]
Put --> Console["פלט לקונסול"]
Put --> Log["פלט ללוג"]
Put --> Test["buffer לטסט"]
16.4 לא מקבעים את טיפוס האינדקס של המערך
ב-Ada גם טיפוס האינדקס של מערך הוא מידע טיפוס חשוב. במקום לקבע Positive, אם צריך לוקחים גם את טיפוס האינדקס כ-formal parameter, וה-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);
העיצוב הזה מכסה לא רק מערך עם אינדקס Positive, גם מערך עם אינדקס enumeration.
flowchart LR
accTitle: Index כ-discrete type
accDescr: אותו Index יכול להיות Positive, enumeration בשם Day או State, או טיפוס integer משלכם.
Index[Index is discrete] --> Positive[Positive range]
Index --> Day["enumeration בשם Day"]
Index --> State["enumeration בשם State"]
Index --> Slot["טיפוס integer משלכם"]
17. checklist ל-API שנראה כמו Ada
כשכותבים generic, בסוף עוברים על הנקודות האלה והקריאות עולה.
flowchart TB
accTitle: checklist לעיצוב generic
accDescr: בודקים מינימום formal parameters, פעולות ב-formal part, קטגוריה נכונה, named association, מצב ו-exception לכל instance, גודל קוד, ו-instance לטסט.
C["בדיקת עיצוב generic"] --> C1["ה-formal parameters במינימום?"]
C --> C2["הפעולות הדרושות כתובות ב-formal part?"]
C --> C3["הקטגוריה private / range / digits מתאימה?"]
C --> C4["אפשר instantiation קריא עם named association?"]
C --> C5["מודעים למצב ול-exception לכל instance?"]
C --> C6["אפשר לקבל גידול בגודל הקוד?"]
C --> C7["יש instance לטסטים?"]
במילים:
- כל פעולה שהגוף משתמש בה חייבת להופיע כחוזה של formal parameter.
- אם
privateמספיק, משאיריםprivate. אם צריך אריתמטיקה,range <>אוdigits <>. - behavior שמשתנה לפי טיפוס — השוואה, hash, פלט, המרה — הופכים ל-formal subprogram.
- אם גודל או סף הם תכונה של ה-instance, הם פרמטר ערך.
- עם מצב, קודם generic package; בלי מצב, קודם generic subprogram.
- ב-instantiation, ככל שיש יותר ארגומנטים כך named association חשוב יותר.
- מתכננים מתוך הנחה ש-exceptions ומצב פנימי עצמאיים לכל instance.
18. דוגמה למבנה הקבצים של הדוגמאות
אם מפצלים את הדוגמאות במאמר לקבצים, המבנה הבא קריא יותר.
flowchart TB
accTitle: פיצול דוגמאות ל-generics ול-demos
accDescr: תחת src יושבים generics עם ads ו-adb לכל רכיב, ו-demos עם תוכניות ההדגמה.
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]
לדוגמה קצרה במאמר, נוח לאגד לקובץ אחד ולפצל ב-gnatchop. לתחזוקה ארוכה ולעבודה מעשית, פיצול specification ב-.ads וגוף ב-.adb הוא המבנה שאופייני ל-Ada.
19. סיכום — גבולות ה-reuse נקבעים בטיפוסים
generic programming ב-Ada אינו רק «פיצ’ר לקוד שלא תלוי בטיפוס». הליבה היא לכתוב במפורש, כחוזה של טיפוסים, subprograms וערכים, מה רכיב ה-reuse דורש.
flowchart LR
accTitle: מחוזה עד reuse בלי העתקה
accDescr: כותבים חוזה, מממשים generic, עושים instantiate עם טיפוס ערך ו-function, ומשתמשים באופן type-safe בלי להעתיק קוד.
Contract["כותבים חוזה"] --> Generic["כותבים את גוף ה-generic"]
Generic --> Instance["instantiation עם טיפוס, ערך ו-function"]
Instance --> Safe["שימוש type-safe"]
Safe --> Reuse["reuse בלי copy-paste"]
כפי שראינו, ב-generics של Ada אפשר לקחת כ-formal parameters את אלה.
- טיפוס
- ערך
- subprogram
- package
ועל טיפוס אפשר לציין קטגוריות דקות: private, limited private, range <>, mod <>, digits <>, delta <>, (<>). כך גוף ה-generic לא תלוי בפעולה ש«אולי קיימת», וממומש בבטחה רק עם מה שכתוב בחוזה.
בנכסי C ו-C++ ישן, reuse נעשה לפעמים במאקרו, void*, מצביעי functions, ופיצול טיפוסים ידני. generics ב-Ada מחליפים הרבה מהשימושים האלה בצורה type-safe וקריאה. במיוחד בתחזוקה ארוכה, embedded, real-time, ותוכנה high-integrity, לעיצוב שקובע גבולות בזמן קומפילציה יש ערך גדול.
20. תחומי ייעוץ קשורים
KomuraSoft LLC מטפלת בפיתוח אפליקציות Windows, חקירה ותיקון של נכסים קיימים, סידור גבולות COM / ActiveX / 32bit / 64bit, ייעוץ טכני וסקירת תכנון. לא רק עיצוב סטטי high-integrity כמו Ada, גם איך מסדרים נכסי C/C++, C#, VB6, MFC ו-COM קיימים כדי להאריך חיים או להגר — אלה נושאים קרובים בפועל.
- Windows Custom Software Development
- פיתוח רכיבי COM
- החלפת אפליקציות Windows
- ייעוץ טכני וסקירת תכנון
- יצירת קשר
מקורות
- Ada 2022 Language Reference Manual, Section 12: Generic Units — זה הסעיף עצמו על generic units. 12.1 הוא הצהרת generic, 12.3 instantiation, 12.4 formal object (פרמטר ערך), 12.5 formal type (קטגוריות כמו
private,range <>,digits <>), 12.6 formal subprogram, ו-12.7 formal package. אם פרק במאמר לא ברור, הכי בטוח לפתוח את הסעיף המתאים. - Ada 2022 Language Reference Manual, 2.2: Lexical Elements, Separators, and Delimiters — כאן מוגדר ש-
<>הוא compound delimiter בשם box. זה הבסיס לסימוןboxבאיורים. - GNAT User’s Guide for Native Platforms — שימוש ב-
gnatmakeוב-gnatchop, ורשימת אפשרויות קומפילציה כולל-gnata. אם נתקעים בסדר של פרק 3, כאן בודקים. - Alire Documentation — התקנת
alr, ניהול toolchain (GNAT / gprbuild), ואיך בונים crate. לבניית סביבה המסמך הזה מספיק. - Sample code (GitHub) — דוגמאות המאמר מפוצלות לקבצים לפי המבנה בפרק 18.
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
Concurrency בטוח ב-Ada — מדריך מעשי ל-task ול-protected object
מאמר מבוא ל-concurrency שמובנה בשפת Ada עצמה: task ו-protected object. נעבור על rendezvous (entry/accept), selective accept, mutual exclu...
תכנות מערכות real-time ב-Ada — עדיפויות, מחזוריות ובקרת execution time
לומדים את Annex D של Ada (מערכות real-time) דרך שמונה דוגמאות קוד מעשיות. המאמר עובר בהדרגה על עדיפות של tasks, Ceiling_Locking, הרצה מחז...
Windows Virtualization Internals (חלק 3) — VM שעולה תוך שניות: WSL2, Windows Sandbox ו-containers
למה WSL2 ו-Windows Sandbox עולים בשניות ומרגישים קלים? המאמר מסביר את המנגנונים, מ-dynamic base image ו-direct map דרך הקצאת זיכרון דינמי...
Windows Virtualization Internals (חלק 2) — זיכרון שאפילו ה-kernel לא רואה: VBS, HVCI ו-Credential Guard
בהתקנה נקייה על חומרה תואמת, VBS מופעל כברירת מחדל ומשתמש ב-hypervisor וב-SLAT כדי ליצור בידוד חזק מה-kernel. המאמר מסביר את המבנה של VTL...
Windows Virtualization Internals (חלק 1) — איפה Windows שלכם באמת רץ: hypervisor ו-partitions
כשמפעילים Hyper-V, Windows המארח עצמו רץ מעל ה-hypervisor כ-root partition. המאמר מסביר את יסודות הווירטואליזציה דרך התפקידים של VT-x, SL...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- מה הם generics ב-Ada?
- מנגנון reuse שמקבל טיפוסים, ערכים, subprograms, ואפילו packages שלמים כ-formal parameters, ונבדק סטטית ברגע ה-instantiation עם new. זה לא החלפת טקסט. בזמן קומפילציה נקבע אם הרכיב מקיים את ה-contract. generic subprogram אי אפשר לקרוא רק כי הצהרת עליו; הוא הופך ל-procedure או function רגיל רק אחרי שנותנים טיפוס ממשי ועושים instantiation.
- במה generics של Ada שונים מ-templates של C++?
- Ada משתמשת מיום הראשון ב-contract model מפורש: גוף ה-generic נכתב רק עם הפעולות שה-formal parameters מבטיחים, והגוף עצמו נבדק לבד. templates של C++ היסטורית הראו שגיאה רק ב-instantiation; C++20 concepts שיפרו את זה. ב-Ada אפשר גם, מעבר לפרמטרי ערך ו-subprogram, לקבל package שלם כ-formal parameter ולחבר רכיבי generic זה לזה.
- למה מציינים type category ב-formal type parameter של Ada?
- כדי לכתוב במפורש, כ-contract, אילו פעולות הגוף רשאי להניח. ב-type T is private אפשר להניח בעיקר השמה והשוואה לשוויון, לא השוואת גודל ולא אריתמטיקה. אם צריך אריתמטיקה של integers משתמשים ב-range <>, אם צריך floating-point ב-digits <>, אם צריך פעולות על bits ב-mod <>. ציון הקטגוריה הוא גם מפרט ברמת הטיפוס לקורא: מה הרכיב דורש.
- למה כדאי לשים לב ב-generics של Ada?
- כי לכל טיפוס נוצר instance, מספר גדול של instantiations עלול להגדיל את גודל הקוד. זה אותו trade-off כמו templates ב-C++ ו-monomorphization ב-Rust. exception שמוצהר ב-specification של generic package הוא exception נפרד לכל instance. בפועל מתחילים עם מעט formal parameters, מוסיפים רק כשיש סיבה להחלפה, וב-instantiation עם הרבה ארגומנטים משתמשים ב-named association.