Adaのジェネリックプログラミング ── 型で契約を書き、再利用をゼロコストで実現する
· 小村 豪 · Ada, ProgrammingLanguage, Generics, TypeSystem, StaticTyping, ContractModel, ZeroCostAbstraction, GNAT, Alire, 高信頼性, コード再利用
1. はじめに ── 「何でも受け取る」ではなく「何を約束するか」
静的型付き言語で再利用可能なコードを書こうとすると、すぐに同じ悩みにぶつかります。整数用に書いたスタックを文字列でも使いたい。浮動小数点の配列にも同じ統計処理を使いたい。昇順ソートのロジックを降順ソートにも使いたい。ところが、型ごとに同じコードをコピーしていくと、修正漏れが起きます。逆に void* やキャストで何でも受け取る設計にすると、型安全性が崩れます。
Ada の答えは ジェネリック(generic units) です。
Ada のジェネリックは、単なるテキスト置換ではありません。型、値、サブプログラム、さらにはパッケージそのものを仮パラメータとして受け取り、インスタンス化の時点で静的に型チェックされます。つまり、実行時に「この型で本当に大丈夫か」を調べるのではなく、コンパイル時に「この部品はこの契約を満たしているか」を確定させる仕組みです。
flowchart LR
A[再利用したい処理] --> B{どう再利用するか}
B --> C[コピーとペースト]
B --> D[void* / Object / キャスト]
B --> E[Ada ジェネリック]
C --> C1[修正漏れが起きやすい]
D --> D1[実行時エラーや型崩れが起きやすい]
E --> E1[型安全]
E --> E2[コンパイル時チェック]
E --> E3[実行時の余分なディスパッチなし]
本記事では、Ada のジェネリックプログラミングを次の流れで整理します。
- 総称サブプログラム
- 総称パッケージ
- 型パラメータ、値パラメータ、サブプログラムパラメータ
private、range <>、digits <>などの型カテゴリ- ソート、スタック、統計処理、
Count_If、キーバリューストアの実装例 - 正式パッケージパラメータによる高階ジェネリック
- Ada の contract model と実務設計の考え方
このテーマは、当ブログの連載「Ada言語の魅力」「SPARKによる形式検証入門」「安全な並行処理」「リアルタイムシステム」に続く位置づけです。Ada の「型で設計を語る」考え方を、ジェネリックという切り口から掘り下げます。
2. この記事の地図
まず全体像を図で押さえます。Ada のジェネリックは「型を引数に取る機能」とだけ理解すると、かなり狭い見方になります。実際には、再利用したい単位に応じて、サブプログラム、パッケージ、サブプログラムパラメータ、値パラメータ、正式パッケージパラメータを組み合わせます。
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
この記事の読み方は単純です。前半では構文を追い、後半では設計判断を扱います。Ada を初めて読む方は、最初は細かな構文を覚えようとせず、「何を仮パラメータにしているのか」「その仮パラメータにどんな操作を許しているのか」に注目してください。
3. 実行環境とコンパイル方法
本記事のコードは GNAT 15.x 以降 を想定しています。GNAT は Ada の代表的なコンパイラで、Alire から導入できます。Alire は Ada / SPARK のパッケージマネージャであり、ツールチェーン管理やビルドにも使えます。
gnat --version
# GNAT 15.2.1
GNAT は Alire(Ada のパッケージマネージャ)から alr install gnat_native gprbuild で導入し、PATH を通してください。
本記事で扱うサンプルは、リポジトリ内で次のように置く想定です。
flowchart TB
R[ada-generic-programming/] --> S[src/]
S --> N[snippets/]
N --> A[01_swap.ada]
N --> B[02_stack.ada]
N --> C[03_sort.ada]
N --> D[04_statistics.ada]
N --> E[05_filter.ada]
N --> F[06_kv_store.ada]
R --> README[README.md]
複数のコンパイル単位を 1 ファイルにまとめたサンプルは、gnatchop で分割してから gnatmake します。
mkdir work
cd work
gnatchop ../src/snippets/01_swap.ada
gnatmake -gnata swap_demo
./swap_demo
-gnata はアサーションを有効にするオプションです。ジェネリックそのものを使うために必須ではありませんが、学習用のサンプルでは契約や境界条件を確認しやすくなります。
sequenceDiagram
participant Dev as 開発者
participant Chop as gnatchop
participant Build as gnatmake
participant Exe as 実行ファイル
Dev->>Chop: 1つの .ada ファイルを渡す
Chop-->>Dev: .ads / .adb / main に分割
Dev->>Build: gnatmake -gnata main
Build-->>Dev: バインドとリンクまで実行
Dev->>Exe: ./main
Exe-->>Dev: 実行結果
4. Ada ジェネリックの基本モデル
Ada のジェネリックは、大まかには次の 3 つで考えると分かりやすいです。
- 総称単位を書く
generic部分に仮パラメータを書く- 使う側で
newによりインスタンス化する
flowchart LR
G[generic 宣言] --> F[仮パラメータ]
F --> B[総称本体]
B --> I[new によるインスタンス化]
I --> U[通常のサブプログラムまたはパッケージとして使用]
F --> F1[型]
F --> F2[値]
F --> F3[サブプログラム]
F --> F4[パッケージ]
たとえば、2 つの値を入れ替える処理をジェネリックにすると、型だけを仮パラメータにできます。
generic
type Element is private;
procedure Generic_Swap (A, B : in out Element);
この時点では、Generic_Swap はまだ呼び出せません。これは「任意の Element 型について使える入れ替え処理のテンプレート」です。具体的な型を与えることで、初めて通常のプロシージャになります。
procedure Swap_Integer is new Generic_Swap (Integer);
図にすると、次のような関係です。
flowchart TB
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; と宣言した場合、代入や等価比較などの基本的な操作は使えますが、大小比較や算術演算は使えません。つまり、ジェネリックの宣言そのものが「この部品は何を前提にしてよいか」を表します。
5. 仮パラメータの種類 ── Ada ジェネリックの語彙
Ada のジェネリックで受け取れるものは、型だけではありません。ここが C# や Java の一般的なジェネリクスと比べて大きく違う点です。
flowchart TB
P[generic formal parameters] --> T[型パラメータ]
P --> O[オブジェクト / 値パラメータ]
P --> S[サブプログラムパラメータ]
P --> PKG[パッケージパラメータ]
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 ...]
代表的な仮パラメータを表にすると、次のようになります。
| 種類 | 例 | 意味 |
|---|---|---|
| 型パラメータ | type Element is private; |
任意の definite な非 limited 型を受け取る基本形 |
| limited 型パラメータ | type Element is limited private; |
コピーできない型も受け取る |
| 離散型 | type Index is (<>); |
整数型や列挙型など、配列添字に使える型 |
| 符号付き整数型 | type Count is range <>; |
+、-、大小比較など整数演算を前提にできる |
| モジュラー整数型 | type Word is mod <>; |
ビット演算や剰余的な整数を扱う |
| 浮動小数点型 | type Real is digits <>; |
Float、Long_Float、ユーザ定義浮動小数点型など |
| 固定小数点型 | type Money is delta <>; |
固定小数点演算を扱う |
| 値パラメータ | Max_Size : Positive; |
サイズや閾値などをインスタンスごとに固定する |
| サブプログラム | with function Predicate (...) return Boolean; |
比較関数や述語など、振る舞いを注入する |
| パッケージ | with package P is new Some_Generic (<>); |
生成済みのジェネリックパッケージを部品として受け取る |
この語彙を持っているため、Ada では「何でも受け取るが、中で危険なことをする」ではなく、「この操作ができる型だけ受け取る」という書き方が自然にできます。
6. 総称サブプログラム ── Generic_Swap で最小構成を理解する
最初の例として、任意の型の 2 つの変数を入れ替える Generic_Swap を見てみます。
6.1 仕様部
generic
type Element is private;
procedure Generic_Swap (A, B : in out Element);
generic に続く部分が仮パラメータです。ここでは Element という型を受け取ります。is private は、ジェネリック本体から見たときに、その型の内部表現を知らないという意味です。
この宣言で分かることは、次の 2 つです。
Generic_Swapは任意のElement型に対して使える- 本体では、
Elementの内部構造や大小比較には依存しない
6.2 本体
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、レコード型、列挙型など、代入可能な型であれば自然に使えます。
flowchart LR
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 インスタンス化
使う側では new を使います。
procedure Swap_Int is new Generic_Swap (Integer);
procedure Swap_Char is new Generic_Swap (Character);
これで Swap_Int と Swap_Char は通常のプロシージャとして呼び出せます。
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 は Integer 用にインスタンス化された通常のプロシージャだからです。ジェネリックは「何でも入る穴」ではなく、「型ごとに安全な具体物を作る仕組み」だと捉えると理解しやすくなります。
7. 総称パッケージ ── 型と値をパラメータにする
サブプログラム 1 個ではなく、複数の操作と内部状態をまとめて再利用したい場合は、総称パッケージを使います。代表例はスタックです。
スタックは、要素型と最大サイズだけを変えれば、基本的なロジックは同じです。
flowchart TB
G[Generic_Stack] --> P1[Element_Type]
G --> P2[Max_Size]
G --> Ops[Push / Pop / Size / Is_Empty / Is_Full]
G --> I1[Int_Stack<br/>Element=Integer<br/>Max_Size=5]
G --> I2[Float_Stack<br/>Element=Float<br/>Max_Size=3]
G --> I3[String_Stack<br/>Element=Unbounded_String<br/>Max_Size=20]
7.1 仕様部
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;
ここでは 2 種類の仮パラメータを使っています。
Element_Typeは型パラメータMax_Sizeは値パラメータ
Max_Size は Positive なので、0 以下のサイズではインスタンス化できません。このように、値パラメータにも型による制約を持たせられます。
7.2 本体
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;
このパッケージ本体で重要なのは、Data と Top がインスタンスごとに別々に作られることです。
package Int_Stack is new Generic_Stack (Integer, 5);
package Float_Stack is new Generic_Stack (Float, 3);
この 2 つは同じテンプレートから作られますが、内部状態は共有しません。
flowchart LR
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 スタックの状態遷移
スタックは状態機械として見ると分かりやすいです。
stateDiagram-v2
[*] --> 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;
総称パッケージは、実務では「小さなコンテナ」「固定長バッファ」「リングバッファ」「ログ用キュー」「ハードウェア抽象化層」などでよく効きます。特に Ada では、サイズを実行時に動かすよりも、型や値パラメータとして静的に固定する設計が高信頼システムと相性よく働きます。
8. 仮サブプログラムパラメータ ── 振る舞いを注入する
型だけを受け取っても、まだ表現できないことがあります。たとえばソートでは、要素型だけでなく「どちらを先に並べるか」という比較ロジックが必要です。
Ada では、この比較関数をジェネリックの仮パラメータにできます。
flowchart LR
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 仕様部
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);
ここには 4 つの仮パラメータがあります。
Item_Type:配列要素の型Index:配列添字の型Item_Array:実際の配列型"<":比較関数
type Index is (<>); は離散型を受け取ります。整数型だけでなく、列挙型も受け取れます。配列添字に Positive を使うだけでなく、Day のような列挙型を使えるのが Ada らしいところです。
with function "<" ... is <>; の is <> は、実パラメータを省略した場合に、可視な標準演算子や適合する関数を使うという意味です。つまり、Integer のように既に < がある型なら、比較関数を明示しなくても使えます。
8.2 本体
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;
挿入ソートは、大きな配列には向きませんが、ジェネリックの説明には向いています。比較関数だけを差し替えることで、同じループ構造を昇順にも降順にも使えるからです。
flowchart TB
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
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]
この仕組みは、C++ で比較関数オブジェクトをテンプレート引数に渡す設計や、Rust でトレイト境界を使って順序を要求する設計に近いものです。ただし Ada では、仮サブプログラムパラメータとして「この形の関数を渡す」と明示します。
9. 型カテゴリ ── private より具体的な契約を書く
type T is private; は便利ですが、何でもできるわけではありません。private な型に対しては、四則演算や大小比較を当然のようには使えません。そこで Ada では、仮型パラメータにカテゴリを指定できます。
flowchart TB
FormalType[Formal Type] --> Private[private]
FormalType --> Limited[limited private]
FormalType --> Discrete[discrete box: 離散型]
FormalType --> Signed[range box: 符号付き整数]
FormalType --> Modular[mod box: モジュラー整数]
FormalType --> Float[digits box: 浮動小数点]
FormalType --> Fixed[delta box: 固定小数点]
FormalType --> Access[access 型]
Discrete --> Enum[列挙型]
Discrete --> Int[整数型]
Float --> F1[Float]
Float --> F2[Long_Float]
Float --> F3[ユーザ定義浮動小数点型]
9.1 カテゴリを指定すると何が嬉しいのか
たとえば平均や分散を計算するには、加算、減算、乗算、除算が必要です。private 型ではこれらの演算を前提にできません。そこで、浮動小数点型に限定します。
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 は浮動小数点型であると分かります。したがって、ジェネリック本体では +、-、*、/ などを使えます。
9.2 本体
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);
同じ統計処理を、精度の異なる浮動小数点型に対して再利用できます。
flowchart LR
Stats[Generic_Statistics<br/>Real is digits box] --> FS[Float_Stats]
Stats --> LS[Long_Stats]
Stats --> MS[My_Real_Stats]
FS --> FCalc[Float で Mean / Variance]
LS --> LCalc[Long_Float で Mean / Variance]
MS --> MCalc[ユーザ定義 Real で Mean / Variance]
9.4 カテゴリ指定は「型レベルの仕様書」
カテゴリ指定は、単にコンパイラを黙らせるための構文ではありません。読み手に対しても「この部品は何を要求しているか」を伝える仕様書になります。
| 書きたい処理 | 向いている仮型 | 理由 |
|---|---|---|
| 入れ替え、保存、取り出し | private |
代入できればよい |
| コピーできないリソース管理 | limited private |
代入を前提にしない |
| 配列添字、列挙状態の走査 | (<>) |
First、Last、Succ、Pred を使える |
| 整数の合計、カウンタ | range <> |
整数演算を前提にできる |
| ビットマスク、循環カウンタ | mod <> |
モジュラー演算を前提にできる |
| 平均、分散、数値計算 | digits <> |
浮動小数点演算を前提にできる |
| 金額、制御量などの固定精度 | delta <> |
固定小数点演算を前提にできる |
10. 述語注入 ── Count_If を Ada らしく書く
仮サブプログラムパラメータは、比較関数だけでなく、述語にも使えます。述語とは、値を受け取って Boolean を返す関数です。
C# の Func<T, bool>、Java の Predicate<T>、C++ のラムダや関数オブジェクトに近い役割を、Ada ではジェネリックの仮サブプログラムとして表現できます。
10.1 仕様部
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;
ここでは Predicate に is <> を付けていません。標準で可視な述語関数があるわけではないので、使う側に必ず渡してもらう設計です。
10.2 本体
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
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);
同じ走査ロジックから、条件だけが違う 2 つの関数を作れます。
flowchart TB
G[Generic_Count_If] --> E[Count_Even<br/>Predicate = Is_Even]
G --> L[Count_Large<br/>Predicate = Is_Large]
Data[12, 7, 88, 3, 56, 91, 44, 19, 62] --> E
Data --> L
E --> ER[偶数の個数]
L --> LR[50より大きい個数]
この例では、配列の走査、カウンタの管理、結果の返却はすべて共通です。一方で「何を数えるか」だけを関数として注入しています。これが Ada における高階的な設計の基本形です。
11. 複数パラメータの合成 ── 汎用キーバリューストア
現実の部品では、型パラメータ 1 つだけで済むことは多くありません。キーと値の型、キーの比較方法、最大件数など、複数の条件を組み合わせる必要があります。
ここでは、固定長の簡単なキーバリューストアを例にします。
flowchart TB
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[小規模キャッシュ]
Ops --> Use3[組み込み向け固定長辞書]
11.1 仕様部
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;
このパッケージには 4 つの仮パラメータがあります。
| パラメータ | 種類 | 役割 |
|---|---|---|
Key_Type |
型 | キーの型 |
Value_Type |
型 | 値の型 |
"=" |
サブプログラム | キーの一致判定 |
Max_Entries |
値 | 最大エントリ数 |
Max_Entries には := 50 でデフォルト値を与えています。したがって、特に指定しなければ 50 件のストアになります。
11.2 本体
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;
この実装は線形探索なので、大量データ向けではありません。しかし、固定長・小規模・動的メモリ確保なしという性質が重要な場面では使いやすい形です。
sequenceDiagram
participant App as 呼び出し側
participant Store as 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 インスタンス化例
with Ada.Strings.Unbounded;
use Ada.Strings.Unbounded;
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
-- Get (1) で値を取得できる
null;
end if;
end KV_Demo;
"=" は省略しています。Integer には標準の等価演算子があり、is <> によってそれが使われるからです。
もしキーが大文字小文字を無視した文字列などであれば、独自の等価関数を渡せます。
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. 正式パッケージパラメータ ── ジェネリックをさらに部品化する
Ada のジェネリックでは、パッケージそのものを仮パラメータにできます。これを使うと、「あるジェネリックパッケージから作られたインスタンス」を、別のジェネリックの入力として扱えます。
flowchart LR
GS[Generic_Stack] --> IS[Int_Stack]
IS --> Logger[Generic_Stack_Logger]
Logger --> Logged[Int_Stack_Logger_Instance]
12.1 スタックを受け取るロガー
たとえば、先ほどの 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;
使う側では、まずスタックを作り、そのスタックをロガーに渡します。
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);
このような設計により、ジェネリック部品同士を組み合わせられます。
flowchart TB
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]
C++ のテンプレートテンプレートパラメータに近い用途ですが、Ada では「このジェネリックパッケージのインスタンスを受け取る」と明示できます。大規模な Ada コードでは、コンテナ、アルゴリズム、ログ、検査、テスト補助などを分けて組み合わせるときに便利です。
13. Contract Model ── Ada ジェネリックのいちばん大事な考え方
Ada のジェネリックを理解するうえで重要なのが contract model です。
ジェネリック本体は、仮パラメータが約束した操作だけを使って書かなければなりません。たとえば type Element is private; しか宣言していないのに、Element に対して < を使うことはできません。< を使いたいなら、仮サブプログラムとして明示するか、型カテゴリをより具体的にする必要があります。
flowchart TB
Spec[generic formal part<br/>契約] --> Body[generic body<br/>契約の範囲内で実装]
Body --> Check1[本体単独で型チェック]
Spec --> Inst[インスタンス化]
Actual[actual parameters<br/>実型・実関数・実値] --> Inst
Inst --> Check2[実パラメータが契約を満たすかチェック]
Check2 --> Instance[通常のパッケージ/サブプログラム]
この設計により、ジェネリックの利用者だけでなく、ジェネリックを書く側も守られます。
13.1 C++ テンプレートとの見え方の違い
C++ のテンプレートは強力ですが、歴史的には「テンプレート本体をインスタンス化して初めてエラーが出る」という性質がありました。C++20 の concepts により改善されていますが、Ada のジェネリックは最初から契約を明示するモデルです。
flowchart LR
subgraph Ada[Ada]
A1[formal partに契約を書く] --> A2[bodyは契約内でチェック]
A2 --> A3[instantiationでactualをチェック]
end
subgraph CPP[C++ templates]
C1[template bodyを書く] --> C2[instantiation時に要求式が具体化]
C2 --> C3[conceptsで制約を明示可能]
end
Java や C# のジェネリクスは、参照型や制約、型消去、実行時表現との関係が設計の中心になります。一方、Ada のジェネリックは、コンパイル時に具体的なインスタンスを作る考え方に寄っています。
| 観点 | Ada | C++ | Java | Rust |
|---|---|---|---|---|
| 契約の書き方 | formal part に型・値・関数・パッケージを書く | templates / concepts | 型パラメータと bounds | trait bounds |
| 本体のチェック | 仮パラメータの契約内でチェック | インスタンス化時の具体化が中心 | bounds 内でチェック | trait bounds 内でチェック |
| 実行時コスト | 静的解決が基本 | 静的生成が基本 | 型消去の影響を受ける | 単相化が基本 |
| 値パラメータ | あり | あり | 限定的 | const generics |
| サブプログラムを仮パラメータにする | あり | 関数オブジェクトなどで表現 | ラムダ/関数型インターフェイス | クロージャ/関数/trait |
| パッケージを仮パラメータにする | あり | テンプレートテンプレートなど | なし | モジュール構造とは別 |
細かい言語機能はそれぞれ違いますが、Ada の特徴は「契約を構文として先に書く」ことです。
14. 実務での設計判断 ── 何をジェネリックにするべきか
ジェネリックは便利ですが、何でもジェネリックにすればよいわけではありません。実務では、次のように判断すると失敗しにくくなります。
flowchart TB
Start[再利用したい処理がある] --> Q1{型だけ違う?}
Q1 -->|Yes| GType[型パラメータを検討]
Q1 -->|No| Q2{サイズや閾値も違う?}
Q2 -->|Yes| GObject[値パラメータを追加]
Q2 -->|No| Q3{比較や判定の振る舞いが違う?}
Q3 -->|Yes| GSubp[仮サブプログラムを追加]
Q3 -->|No| Q4{内部状態やAPIをまとめたい?}
Q4 -->|Yes| GPackage[総称パッケージ]
Q4 -->|No| Normal[通常のサブプログラムで十分]
14.1 総称サブプログラムが向いている場面
総称サブプログラムは、状態を持たないアルゴリズムに向いています。
SwapSortCount_IfFindMap的な変換Min/Max
アルゴリズム本体が短く、入力と出力が明確な場合は、パッケージよりもサブプログラムのほうが読みやすくなります。
14.2 総称パッケージが向いている場面
総称パッケージは、型と一緒に複数の操作や内部状態を持ちたい場合に向いています。
- 固定長スタック
- リングバッファ
- 小規模辞書
- 統計処理セット
- デバイスごとの I/O 抽象化
- 単位系つき数値型の演算セット
特に Ada では、パッケージ仕様部が公開 API、パッケージ本体が実装という分離になっているため、ジェネリックパッケージは「型安全なモジュールテンプレート」として使えます。
flowchart LR
Spec[package spec<br/>公開API] --> User[利用側]
Body[package body<br/>内部実装] -.隠蔽.-> User
Formal[generic formal part<br/>型・値・関数の契約] --> Spec
Formal --> Body
14.3 仮パラメータは少なく始める
仮パラメータを増やしすぎると、インスタンス化が読みにくくなります。最初は最小限にし、差し替えたい理由が出たら増やすのが安全です。
-- 読みにくくなりやすい例
package X is new Generic_Foo
(A, B, C, D, E, F, G);
-- 名前付き関連付けで意図を残す
package X is new Generic_Foo
(Element_Type => Integer,
Index_Type => Positive,
Buffer_Size => 128,
"<" => Less);
Ada では、インスタンス化時に名前付き関連付けを使えます。ジェネリックは設計上の重要点がインスタンス化に現れるため、実務コードでは名前付きで書くほうが保守しやすいことが多いです。
15. よくあるつまずきどころ
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
Need[本体で比較を使いたい] --> Contract[formal partに比較関数を書く]
Contract --> OK[インスタンス化時に比較可能性を確認]
Need --> NoContract[privateだけ]
NoContract --> Error[ジェネリック本体でコンパイルエラー]
15.2 is <> は「何でも自動推論」ではない
is <> は便利ですが、魔法ではありません。インスタンス化地点で、適合する演算子やサブプログラムが可視である必要があります。独自の比較関数を別パッケージに置いた場合は、適切に with や use を行うか、名前付きで明示的に渡すのが安全です。
procedure Sort_By_Age is new Generic_Insertion_Sort
(Item_Type => Person,
Index => Positive,
Item_Array => Person_Array,
"<" => Younger_Than);
15.3 インスタンスごとに例外も別物になる
ジェネリックパッケージの仕様部に例外を宣言すると、インスタンスごとに別の例外になります。
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 は別の例外として扱われます。共通の例外として扱いたいなら、ジェネリックの外側に例外を定義する設計も検討します。
flowchart TB
Generic[Generic_Stack<br/>Stack_Overflow宣言] --> I[Int_Stack.Stack_Overflow]
Generic --> F[Float_Stack.Stack_Overflow]
I -.別の例外.-> F
15.4 コードサイズが増えることがある
ジェネリックは実行時の余計な間接参照を避けやすい一方で、型ごとにインスタンスを作るため、インスタンス数が多いとコードサイズが増える可能性があります。
これは C++ のテンプレートや Rust の単相化でも見られるトレードオフです。高信頼・組み込み・リアルタイム寄りの開発では、実行時の不確実性を減らす代わりに、ビルド時の生成物サイズを管理するという考え方になります。
flowchart LR
Generic[1つのgeneric本体] --> I1[Integer版]
Generic --> I2[Float版]
Generic --> I3[Long_Float版]
Generic --> I4[My_Type版]
I1 --> Code[生成コード]
I2 --> Code
I3 --> Code
I4 --> Code
Code --> Pros[実行時の型判定やボクシングを避けやすい]
Code --> Cons[インスタンスが多いとサイズ増加に注意]
15.5 limited private を使うべき場面
type Element is private; は代入を前提にします。ファイルハンドル、ロック、デバイスハンドルのように、コピーされたくないものを扱う場合は limited private を検討します。
generic
type Resource is limited private;
with procedure Close (R : in out Resource);
procedure Generic_Use_And_Close (R : in out Resource);
コピーできない型を扱う設計では、値を保存するコンテナよりも、手続きを適用するアルゴリズムや、参照を明示する設計のほうが安全です。
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
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;
値パラメータは、実行時の設定値ではなく、インスタンスの性質として固定したい値に向いています。
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;
この形にすると、標準出力、ログ、テスト用バッファなど、出力先を差し替えられます。
flowchart TB
Print[Generic_Print_Twice] --> Put[Putを仮サブプログラムとして受け取る]
Put --> Console[コンソール出力]
Put --> Log[ログ出力]
Put --> Test[テスト用バッファ]
16.4 配列の添字型を固定しない
Ada では配列の添字型も重要な型情報です。Positive 決め打ちにせず、必要に応じて添字型も仮パラメータにすると再利用性が上がります。
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 添字の配列だけでなく、列挙型添字の配列にも対応できます。
flowchart LR
Index[Index is discrete] --> Positive[Positive range]
Index --> Day[Day列挙型]
Index --> State[State列挙型]
Index --> Slot[独自整数型]
17. Ada らしい API にするためのチェックリスト
ジェネリックを書くときは、最後に次の観点で見直すと読みやすくなります。
flowchart TB
C[Generic設計チェック] --> C1[仮パラメータは最小限か]
C --> C2[必要な演算をformal partに明示したか]
C --> C3[private / range / digits などのカテゴリは適切か]
C --> C4[名前付き関連付けで読みやすくインスタンス化できるか]
C --> C5[インスタンスごとの状態や例外を意識しているか]
C --> C6[コードサイズ増加を許容できるか]
C --> C7[テスト用インスタンスを用意しているか]
文章でまとめると、次の通りです。
- 本体で使う操作は、必ず仮パラメータの契約として見える形にする。
privateで足りるならprivateにする。算術が必要ならrange <>やdigits <>を使う。- 比較、ハッシュ、出力、変換など、型ごとに変わる振る舞いは仮サブプログラムにする。
- サイズや閾値がインスタンスの性質なら、値パラメータにする。
- 状態を持つなら総称パッケージ、状態を持たないなら総称サブプログラムをまず考える。
- インスタンス化では、引数が多いほど名前付き関連付けを使う。
- 例外や内部状態はインスタンスごとに独立することを前提に設計する。
18. サンプル全体の構成例
記事中のサンプルをファイルに分けるなら、次のような構成にすると読みやすくなります。
flowchart TB
Root[ada-generic-programming] --> Src[src]
Src --> G[generics]
Src --> D[demos]
G --> SwapSpec[generic_swap.ads]
G --> SwapBody[generic_swap.adb]
G --> StackSpec[generic_stack.ads]
G --> StackBody[generic_stack.adb]
G --> SortSpec[generic_insertion_sort.ads]
G --> SortBody[generic_insertion_sort.adb]
G --> StatsSpec[generic_statistics.ads]
G --> StatsBody[generic_statistics.adb]
G --> CountSpec[generic_count_if.ads]
G --> CountBody[generic_count_if.adb]
G --> KVSpec[generic_kv_store.ads]
G --> KVBody[generic_kv_store.adb]
D --> SwapDemo[swap_demo.adb]
D --> StackDemo[stack_demo.adb]
D --> SortDemo[sort_demo.adb]
D --> StatsDemo[statistics_demo.adb]
D --> CountDemo[count_if_demo.adb]
D --> KVDemo[kv_demo.adb]
小さな記事用サンプルなら 1 ファイルにまとめて gnatchop するのも便利ですが、実務や長期保守を考えるなら、仕様部 .ads と本体 .adb を分けるほうが Ada らしい構成になります。
19. まとめ ── 型で再利用の境界を決める
Ada のジェネリックプログラミングは、「型に依存しないコードを書く機能」というだけではありません。むしろ本質は、再利用可能な部品が何を要求するのかを、型とサブプログラムと値の契約として明示することにあります。
flowchart LR
Contract[契約を書く] --> Generic[ジェネリック本体を書く]
Generic --> Instance[型・値・関数を渡してインスタンス化]
Instance --> Safe[型安全に使う]
Safe --> Reuse[コピーなしで再利用]
この記事で見たように、Ada のジェネリックでは次のものを仮パラメータにできます。
- 型
- 値
- サブプログラム
- パッケージ
さらに、型については private、limited private、range <>、mod <>、digits <>、delta <>、(<>) など、かなり細かいカテゴリを指定できます。これにより、ジェネリック本体は「できるかどうか分からない操作」に依存せず、契約に書かれた操作だけで安全に実装できます。
C や古い C++ の資産では、再利用のためにマクロ、void*、関数ポインタ、手書きの型分岐が使われることがあります。Ada のジェネリックは、それらの用途の多くを、型安全で読みやすい形に置き換えられます。特に、長期保守、組み込み、リアルタイム、高信頼性ソフトウェアでは、こうした「コンパイル時に境界を決める設計」が大きな価値を持ちます。
20. 関連する相談領域
合同会社小村ソフトでは、Windows アプリ開発、既存資産の調査・改修、COM / ActiveX / 32bit / 64bit の境界整理、技術相談・設計レビューなどを扱っています。Ada のような静的型付け・高信頼性寄りの設計だけでなく、既存の C/C++、C#、VB6、MFC、COM 資産をどう整理して延命・移行するかも、実務上よく近いテーマになります。
参考リンク
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Ada言語の魅力 ── 型で設計を語り、数十年動き続けるソフトウェアを支える言語
Ada言語の魅力を紹介します。強い型付け、範囲制約、パッケージによる仕様と実装の分離、契約による設計、言語組み込みのタスク、SPARKによる形式検証、GNATとAlireでの開発環境まで、高信頼ソフトウェアを支える設計思想を整理します。
Adaによるリアルタイムシステムプログラミング ── 優先度・周期・実行時間制御の実践
Ada の Annex D(リアルタイムシステム)が提供するリアルタイム機能——タスク優先度、Ceiling_Locking プロトコル、delay until による周期実行、Ravenscar プロファイル、タイミングイベント、保護キュー、タスク別実行時間計測、マルチ周...
Adaにおける安全な並行処理 ── タスクと保護オブジェクトの実践ガイド
Adaの言語組み込み並行処理であるタスクと保護オブジェクトの実践的な入門記事です。ランデブー(entry/accept)、選択的アクセプト、プロデューサー・コンシューマー、保護オブジェクトによる排他制御、バリア付き保護エントリ、タイムアウト付き呼び出し、タスク優先度とリアル...
SPARKによる形式検証入門 ── Adaの契約から数学的証明へ
Adaのサブセット言語SPARKによる形式検証の実践的な入門記事です。契約(Pre/Post)から証明へステップアップする方法、GNATproveの使い方、ループ不変条件、データフロー契約、証明レベル、そして実プロジェクトへの適用方法までを整理します。
Fableが使えなくなったけど諦めるな ── OpenRouter Fusion+中華LLM+レビュー層で凌ぐ
Fableには遠く及びません。ですがOpenRouter Fusionで5つの中華LLMを組み合わせ、gpt-5.5-pro または Codex PRレビューを重ねれば、素のgpt-5.5よりはずっとマシなコーディングエージェントになります。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
著者プロフィール
記事の著者プロフィールページです。
小村 豪
合同会社小村ソフト 代表
Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。
公開リンク