【実務・中級編】Hackの『Tuple』型と『List』型の使い分け:固定長と可変長の型安全な管理 – Hack言語 コア・静的型システムとHHVMのアーキテクチャ解析バイブル

Hackを掌握する極限の知見:TupleとListの境界線 — 固定長と可変長の型安全な管理

コードレビューをしていて、最もエンジニアの「思考の浅さ」が露呈する瞬間がある。それは、要素数が固定された構造体的なデータに対して可変長の `vec` をあてがったり、逆に動的なコレクションに対して無理矢理 `tuple` をキャストしようとしているコードを見たときだ。

HHVMの型チェッカー(hh_client)とJITコンパイラの挙動を熟知していれば、この選択が単なる「書き方の好み」ではないことが分かるはずだ。型システムへの冒涜は、そのままランタイムのパフォーマンス劣化と、プロダクションでの致命的なバグとして跳ね返ってくる。

今回は、Hackの厳格モード(Strict Mode)における `Tuple`型 と `List`(`vec` / `keyset` / `dict`)型 の本質的な使い分けと、実務で絶対に破綻しない堅牢な設計パターンを叩き込む。

—

1. 脳内整理:なぜTupleとListを混同してはならないのか

まず、両者のメモリレイアウトと型システムの扱いにおける根本的な違いを定義する。

| 特性 | Tuple (`(T1, T2, …)`) | vec / List (`vec`) |
| :— | :— | :— |
| 要素数 | 固定(Compile-timeで確定) | 可変(Runtimeで増減可能) |
| 型の一意性 | 位置ごとに異なる型を許容(異種混合) | 全要素が同一の型(またはそのサブタイプ)に統制 |
| HHVMの最適化 | 特殊化された構造体としてインライン化されやすい | 連続したメモリ領域を確保するベクタとして高速処理 |
| 主な用途 | 複数戻り値、厳密な座標・ペア表現 | 同一性質のエンティティのリスト、動的コレクション |

「とりあえず `vec` や `vec` で包んでおけば動く」というPHP的な悪習は、Hackの厳格な静的型システムの前では完全に悪手だ。`mixed` に逃げた瞬間、型チェッカーの守護は失われ、ランタイムの型安全神話は崩壊する。

—

2. 実務におけるアンチパターン:なぜそれを選ぶのか?

よくある現場のコードを見てみよう。

// 【アンチパターン】DBから取得した「ユーザーのIDと名前のペア」を可変長配列で返す
<<__EntryPoint>>
async function bad_practice_example(): Awaitable {
// vec のような曖昧な型、もしくは vec になりがち
$userData = getUserRecordFromDB();

// 読む側は $userData[0] がIDで $userData[1] が名前であることを
// 「ドキュメントや記憶」に頼るしかない(地獄の始まり)
echo “ID: ” . $userData[0] . “, Name: ” . $userData[1];
}

このコードの問題点は明白だ。
1. 意味の欠如: インデックス `0` や `1` が何を指すのか、型システムが一切保証していない。
2. 拡張性の幻想: 「後から要素が増えるかも知れない」という理由で `vec` を使っているが、構造が固定されているならそれはTuple(あるいはShape)の領域だ。

—

3. 堅牢な設計パターン:Tupleによる「構造化された複数戻り値」の制御

関数が密結合した複数の値を返す場合、かつそれが完全に固定長であるならば、Tuple を選択するべきだ。Shape (`shape(…)`) を使う手もあるが、キー名すら不要な純粋な値のペアやトリプルであれば、Tupleが最もミニマルでオーバーヘッドがない。

以下のプロダクションコードを見てほしい。非同期API連携において、レスポンスのメタデータとペイロードを厳密に型安全にハンドリングする例だ。

hh_strict
namespace HackExpert\Architecture;

type ApiResult = (int, T); // HTTPステータスコードとレスポンスデータの厳密なペア
type PaginationMeta = (int, int, bool); // (current_page, total_pages, has_next)

class ApiClient {
/

  • 外部APIからデータを取得し、ステータスとペイロードのTupleを返す。
  • 戻り値の構造はコンパイル時に完全に保証される。

/
public async function fetchUserDataAsync(int $userId): Awaitable int, ‘name’ => string)>> {
// 擬似的な非同期通信
await \HH\Asio\usleep(10_000);

// 成功を想定した固定長のTupleを返す
return tuple(200, shape(‘id’ => $userId, ‘name’ => ‘Alice’));
}

public async function fetchLogsAsync(int $page): Awaitable>> {
// 可変長のログリストをペイロードとして持つTuple
$logs = vec[“info: boot”, “debug: connection established”];
return tuple(200, $logs);
}
}

<<__EntryPoint>>
async function main_execution(): Awaitable {
$client = new ApiClient();

// Tupleの分解代入(Destructuring)による極めてクリーンなコード
list($status, $user) = await $client->fetchUserDataAsync(42);

if ($status === 200) {
// $user は shape(‘id’ => int, ‘name’ => string) として完全に型推論される
\printf(“User: %s (ID: %d)\n”, $user[‘name’], $user[‘id’]);
} else {
// エラーハンドリング
\printf(“Failed with status: %d\n”, $status);
}
}

この設計の美しさと優位性

  • 分解代入の強制: `list($status, $user) = …` により、呼び出し側で要素へのアクセスを明確に名前付けできる。
  • 型チェッカーの完全な追跡:Tupleの各要素の位置(Position)と型は厳密にバインドされているため、インデックス間違いによるバグはコンパイル時にすべて弾かれる。

—

4. 可変長データの極限:`vec` による一貫したコレクション管理

一方で、要素数が動的に変わる、あるいはループ処理の文脈で増減するデータ群に対しては、迷わず `vec` を選択する。

ここで重要なのは、「ジェネリクスをサボらないこと」だ。`vec` はHackにおける「静的型付けの放棄」を意味する。常に `vec` もしくはドメインモデルのクラスやShapeの `vec` として定義しなければならない。

hh_strict
namespace HackExpert\Architecture;

/

  • 転送用オブジェクト(DTO)の定義

/
class TransactionItem {
public function __construct(
public string $sku,
public int $amount,
) {}
}

class CartProcessor {
/

  • カート内のアイテム群(可変長)を受け取り、合計金額を算出する。
  • vec を用いることで、要素の型安全性を担保。

/
public function calculateTotal(vec $items): int {
$total = 0;
foreach ($items as $item) {
// $item は確実に TransactionItem インスタンスであることが保証されている
$total += $item->amount;
}
return $total;
}
}

<<__EntryPoint>>
function run_cart(): void {
$processor = new CartProcessor();

// 実行時に動的に要素が増減する可変長リスト
$items = vec[
new TransactionItem(“SKU-001”, 1500),
new TransactionItem(“SKU-002”, 3000),
];

$total = $processor->calculateTotal($items);
\printf(“Total Cart Amount: ¥%d\n”, $total);
}

—

5. チーフアーキテクトからの戒め:境界設計のチェックリスト

実際のコードレビューやアーキテクチャ設計において、以下の基準をチームメンバーに徹底させてほしい。

1. 「要素数がドメインロジックとして固定か?」

  • 答えがYesなら、Tuple(またはShape)を使う。`[値1, 値2]` のような配列や汎用 `vec` でごまかさない。

2. 「要素の型が位置によって異なるか?」

  • 答えがYesなら、Tuple一択である。すべての要素が同じ型なら `vec` だが、混在するなら絶対にTuple、あるいはクラス/Shapeへリファクタリングするべきだ。

3. 「API境界や非同期処理の戻り値で複数かつ異なる性質の値を返すか?」

  • 状態コードとデータ、あるいはメタデータとペイロードのペアには Tuple を活用し、`list(…) = …` で美しくアンパックせよ。

Hack言語の厳格モードは、我々開発者に甘えを許さない。しかし、その厳格さに正面から向き合い、TupleとListを適材適所で使い分けたコードベースは、HHVMの最高峰のパフォーマンスを引き出し、大規模開発であっても一切の矛盾を許さない鉄壁の要塞となる。

妥協のないコードを書け。型チェッカーを味方につけた者だけが、真の高速性と堅牢性を手に入れることができる。

タイトルとURLをコピーしました