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

これは、単なる「書き方のガイド」ではない。HHVMという巨大なランタイムの上で、いかにして「予測可能で堅牢なシステム」を構築するかという、設計思想の根幹に関わる講義だ。

多くのエンジニアが、Hackの`vec`(可変長配列)と`tuple`(固定長組)を「なんとなく」使い分けている。だが、私のコードレビューを通過したければ、その選択肢の一つひとつに明確な型論理的根拠を持ってもらわねばならない。

今日は、Hackの型チェッカーを最大限に味方につけ、HHVMの実行効率を極限まで引き出すための「TupleとList(vec)の使い分け」について、その深淵を解説する。

—

1. 「意味論」の欠如がバグを生む

まず、君たちに叩き込んでおきたいのは、「何でも入る`vec`は、設計の敗北である」という事実だ。

HackのStrict Modeにおいて、我々が目指すのは「実行前にバグをゼロにする」ことだ。ここで、データの「形状」が固定されているのか、それとも「同質のデータの集まり」なのかを混同すると、型チェッカーはその威力を失う。

Tuple(タプル)の正体

Tupleは「異種混合(Heterogeneous)かつ固定長」のデータ構造だ。
`tuple(int, string, User)` という型は、0番目が整数、1番目が文字列、2番目がUserオブジェクトであることを、コンパイル(型チェック)時に保証する。これは「名前のない構造体」と考えるべきだ。

Vec(リスト)の正体

`vec` は「同質(Homogeneous)かつ可変長」のデータ構造だ。
`vec` は、中身が0個だろうが100個だろうが、すべてがUserであることを保証する。

—

2. 実践:非同期API連携における堅牢な設計パターン

例えば、ユーザーの基本情報と、そのユーザーに紐づく権限リストを同時に取得する非同期関数を考えてみよう。

【悪い例】 `vec` による曖昧な実装

初心者は、複数の戻り値を一つの配列に詰め込んで返そうとする。これはメンテナンスの地獄への入り口だ。

// ❌ 非推奨:型情報が欠落し、呼び出し側で「型変換の博打」を強いる
async function fetchUserDataBad(int $id): Awaitable> {
// … API fetch …
return vec[$user_obj, $permissions_vec];
}

async function run(): Awaitable {
$data = await fetchUserDataBad(123);
// $data[0] が User クラスである保証を型チェッカーは持てない
// 毎回 $data[0] as User とキャストするか、runtime errorのリスクを負う
}

【美しい例】 `tuple` による厳格な契約

HHVMのJITコンパイラにとっても、型チェッカーにとっても、戻り値の型が明確であることは最適化の最大のヒントになる。

/

  • ✅ 推奨:Tupleを用いることで、戻り値のインデックスごとの型を固定する
  • 1番目は User エンティティ、2番目は Permission のコレクションであることを明示

/
async function fetchUserDataBest(
int $id,
): Awaitable<(User, vec)> {
// 並列でデータ取得を行う(HHVMの非同期性能を最大化)
concurrent {
$user = await User::gen($id);
$perms = await Permission::genForUser($id);
}

// Tupleを生成。HHVM内部では非常に軽量な配列として扱われる
return tuple($user, $perms);
}

async function process(): Awaitable {
// 分割代入(list構文または直接代入)により、即座に型安全な変数として利用可能
list($user, $perms) = await fetchUserDataBest(123);

// ここで $user は自動的に User 型として認識される
// $perms も vec として認識され、foreachで安全に回せる
echo $user->getName();

foreach ($perms as $perm) {
echo $perm->getSlug();
}
}

—

3. なぜ Tuple なのか? HHVMの内部動作とメモリ管理

ここで一歩踏み込んで、なぜ私がこれほどまでに Tuple を推すのか、その理由をアーキテクチャの観点から説明する。

1. 静的解析の解像度:
`vec` を使うと、インデックス `[0]` へのアクセスが境界内にあるかどうかはランタイムまで確定しない。しかし、`tuple(A, B)` に対して `list($a, $b)` でアクセスする場合、型チェッカーは「必ず2つの要素が存在し、それぞれの型が A と B である」ことを知っている。
2. メモリ効率:
HHVMにおける `tuple` は、実際には `Packed Array` として実装されているが、そのサイズが固定されているという前提は、JITコンパイラが不要な境界チェックを省略し、レジスタ割り当てを最適化するための重要なヒントになる。
3. リファクタリングの耐性:
APIの戻り値が増えた場合(例:3番目に `AccountStatus` を追加)、`tuple` を使っていれば、受け取り側のコードすべてに型チェッカーが「要素数が足りない」と即座に警告を出す。`vec` ではこうはいかない。

—

4. Tuple と Shape の使い分け:さらなる高みへ

「インデックス番号で管理するのは可読性が低い」と感じるなら、それは Tuple ではなく Shape を使うべきサインだ。

// Tuple よりもさらに明示的な「名前付き構造」
type UserPayload = shape(
‘user’ => User,
‘permissions’ => vec,
‘metadata’ => shape(‘request_id’ => string),
);

使い分けの指針:

  • Tuple: 2〜3個程度の要素で、文脈的に順序が明らかな場合(例:`tuple(x, y)` や `tuple(result, error)`)。
  • Shape: 要素が3個以上になる、あるいは「名前」を与えないと意味が伝わりにくい場合。
  • Vec: 要素数が動的に変化し、すべての要素に対して同じ操作(map, filter, foreach)を行う場合。

—

5. コアアーキテクトからのアドバイス

「動けばいい」というコードは、技術負債という名の時限爆弾だ。
Hackという言語を選択した以上、君たちは「型」という最強の武器を手にしている。

  • List(vec)を「固定長の箱」として使うな。 それは型の情報の欠落を招く。
  • Tuple を「万能な配列」として使うな。 それは柔軟性の欠如を招く。

データの「性質」を見極めろ。
そのデータは、「同じ種類のものの集まり(Collection)」なのか、それとも「異なる意味を持つパーツの複合体(Structure)」なのか。

この判断を誤らなければ、君の書くコードはHHVMの上で美しく、そして高速に鼓動し続けるだろう。

次に君が `vec` と書こうとしたとき、私のこの言葉を思い出してほしい。
「その構造、本当に型を捨ててまで可変である必要があるか?」と。

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