【入門編】 型レベルの四則演算 – TypeScript実践ガイド

こんにちは!TypeScriptの世界へようこそ。日々のフロントエンド開発、本当にお疲れ様です。

「TypeScriptを学び始めたけれど、型(Type)ってなんだか難しそう…」
「エラーを消すためだけの、ちょっと面倒な存在になっていないかな?」

そんな風に思っている方も、どうか安心してくださいね。TypeScriptは、実はただの「静的なチェックツール」ではありません。私たちが普段書いているJavaScriptと同じくらい、あるいはそれ以上に表情豊かで、パズルみたいに面白い知的なおもちゃなんです。

今回は、TypeScriptの「型」の可能性を極限まで広げる、ちょっと不思議でワクワクするテーマをお届けします。それが「型レベルの四則演算(足し算・引き算)」です。

「えっ、型の上で計算をするの? `1 + 1` はプログラムを実行したときに計算するんじゃないの?」

そう思ったあなた、大正解です!普通はそうです。しかし、TypeScriptの型システムはあまりにも強力なため、「コードを実行する前(コンパイルする段階)」に、型だけで足し算や引き算を終わらせることができるのです。

難しそうな専門用語はできるだけ使わず、おもちゃのブロックやお買い物のような、身近なイメージで一歩ずつ丁寧に解説していきます。お気に入りの飲み物でも片手に、リラックスして読んでみてくださいね。

—

1. 舞台装置:数字を「ブロックの長さ」で表してみよう

TypeScriptの型システムには、残念ながら直接 `type Add<3, 2> = 3 + 2;` のように書く機能はありません。
そこで、私たちは知恵を使います。

想像してみてください。手元におもちゃのブロックがあります。
「3」という数字を、そのまま数字として扱うのではなく、「ブロックが3個つながったおもちゃ」として表現するのです。

「3」という型は、ブロック3個分: [ 🟥, 🟥, 🟥 ]
「2」という型は、ブロック2個分: [ 🟥, 🟥 ]

TypeScriptでこの「ブロックを並べたもの」を表現するのにぴったりなのが、「タプル型(Tuple)」です。

タプル型ってなぁに?

タプル型とは、「中身の個数や順番がキチッと決まった配列」のことです。

// これはただの「文字列の配列」型(何個入るか決まっていない)
type NormalArray = string[];

// これは「1番目が文字列、2番目が数値」と決まった「タプル」型(長さは絶対に2個!)
type MyTuple = [string, number];

このタプル型には、「長さを数字として教えてくれる」という、とてもお利口な性質があります。

type MyList = [‘🍎’, ‘🍏’, ‘🍎’];
type LengthOfList = MyList[‘length’]; // 💡なんと、型レベルで「3」という具体的な数字が手に入ります!

これが今回のすべての基礎になります。
「数字を直接計算できないなら、その数字の数だけ要素が入ったタプルを作って、最後にその長さを測ればいいじゃない!」というアイデアです。

—

2. 準備:数字からブロックを作る「魔法の工場」

まずは、指定した数字(例えば `3`)を渡すと、その個数分の要素が入ったタプル(`[any, any, any]`)を自動で組み立ててくれる「魔法の工場」を作りましょう。

ここで登場するのが、自分自身を何度も呼び出す「再帰(さいき)型」という仕組みです。ロシアのマトリョーシカ人形のように、条件を満たすまで自分自身をパカッと開き続けるようなイメージです。

ゆっくりコードを見てみましょう。大丈夫、一行ずつ分解しますよ。

// 🛠️ 【魔法の工場】指定された数(N)の長さを持つタプルを作る型
type MakeArray =
// もし、今作っている途中のタプル(Accumulator)の長さが、目標の数(N)と同じになったら…
Accumulator[‘length’] extends N
? Accumulator // できた!完成したタプルを返します
: MakeArray; // まだ足りない!タプルにブロック(any)を1個追加して、もう一度工場を回します

🔍 工場の動きをスローモーションで見てみよう

もし `MakeArray<3>` を呼び出したら、裏側ではこんなことが起きています。

1. スタート:目標 `N` は `3`。最初は空っぽの箱 `Accumulator = []` です。
2. 1回目:今の箱の長さは `0`。「3と同じ?」→「違うよ」。じゃあ箱に `any` を1個追加して、`[any]` にします。
3. 2回目:今の箱の長さは `1`。「3と同じ?」→「違うよ」。もう1個追加して、`[any, any]` にします。
4. 3回目:今の箱の長さは `2`。「3と同じ?」→「違うよ」。もう1個追加して、`[any, any, any]` にします。
5. 4回目:今の箱の長さは `3`。「3と同じ?」→「YES!同じだよ!」。
6. ゴール:完成した `[any, any, any]` をお披露目します!

これで、数字を「ブロックの長さ」に変換する準備が整いました!

—

3. 足し算(加算)の型:ブロックをガッチャンコ!

さあ、いよいよ楽しい足し算の時間です。
「3 + 2」をやりたい場合、イメージはこうです。

1. 3個のブロックが入った箱(タプル)を作る:`[any, any, any]`
2. 2個のブロックが入った箱(タプル)を作る:`[any, any]`
3. 2つの箱の中身を、1つの大きな箱にまとめる(合体!):`[any, any, any, any, any]`
4. 最後に、その大きな箱の長さを測る。

これをTypeScriptのコードにすると、驚くほどスッキリ書けます。

// ➕ 【足し算の型】 A と B を足します
type Add = [
…MakeArray
, // A個のブロックを展開して入れる
…MakeArray // B個のブロックも展開して入れる
][‘length’]; // 合体したタプルの長さを測る!

// 🎉 実験してみましょう!
type ThreePlusTwo = Add<3, 2>; // 💡 結果は「5」になります!
type TenPlusFive = Add<10, 5>; // 💡 結果は「15」になります!

いかがでしょうか?
`…`(スプレッド演算子)を使って、2つのタプルを「ガッチャンコ」と連結しているだけなんです。JavaScriptでお馴染みの書き方が、型の上でもそのまま使えるなんて、ちょっと感動的ですよね。

—

4. 引き算(減算)の型:箱からブロックを取り出す

次は引き算「5 – 3」に挑戦です。
引き算のイメージは「型を使った型抜き(パズル)」です。

1. 5個のブロックが入った大きな箱を用意します。
2. その中から、3個のブロックを「これだよ」と型に当てはめて、残った部分を抜き出します。

ここで使うのが、TypeScriptの超強力な魔法`infer`(インファー)です。
`infer` は「ここに当てはまる部分を、後から使えるように名前をつけてキープしておいて!」とTypeScriptにお願いする、超能力のような機能です。

// ➖ 【引き算の型】 A から B を引きます(※ A のほうが大きいと仮定します)
type Subtract
=
// A個のブロックが、 「B個のブロック」 と 「残りのブロック(R)」 に分解できるなら…
MakeArray
extends […MakeArray, …infer R]
? R[‘length’] // 分解できた!その「残りのブロック(R)」の長さを測って返します
: never; // 分解できなかった(引く数のほうが大きい)場合は、計算不可能(never)にします

// 🎉 実験してみましょう!
type FiveMinusThree = Subtract<5, 3>; // 💡 結果は「2」になります!
type TenMinusFour = Subtract<10, 4>; // 💡 結果は「6」になります!

🔍 引き算のパズルを解説

`MakeArray<5> extends […MakeArray<3>, …infer R]` という部分を、視覚的に見てみましょう。

  • 左側(5個のブロック):`[any, any, any, any, any]`
  • 右側(3個 + 残り):`[any, any, any, …infer R]`

TypeScriptはこれを見比べて、
「ふむふむ、最初の3つの `any` はピッタリ重なるな。ということは、残りの2つの `any, any` が `R` に入るんだな!」
と自動的に見抜いて(推論して)くれるのです。

その結果、`R` は `[any, any]` になり、その長さ(`length`)である `2` が答えとして返ってきます。パズルがピタッとはまった時の爽快感がありますね!

—

5. エディタに貼り付けて遊べる!完全版サンプルコード

ここまでご紹介した型をまとめたコードです。ぜひお手元のVS Codeなどのエディタに貼り付けて、マウスを乗せて(ホバーして)結果を確認してみてください。型がリアルタイムに計算を行っている様子が観察できますよ。

// =================================================================
// 1. 魔法の工場:数字からタプルを作る型
// =================================================================
type MakeArray =
Accumulator[‘length’] extends N
? Accumulator
: MakeArray;

// =================================================================
// 2. 足し算(加算)の型
// =================================================================
type Add
= [
…MakeArray
,
…MakeArray
][‘length’];

// =================================================================
// 3. 引き算(減算)の型
// =================================================================
type Subtract
=
MakeArray
extends […MakeArray, …infer R]
? R[‘length’]
: never;

// =================================================================
// 🧪 実験コーナー(マウスを乗せて結果を確認してみてね!)
// =================================================================

// 例1: 8 + 7 = 15
type ResultAdd = Add<8, 7>;
// ^? type ResultAdd = 15

// 例2: 10 – 4 = 6
type ResultSubtract = Subtract<10, 4>;
// ^? type ResultSubtract = 6

// 例3: ちょっと大きめの計算もできます (20 – 15 = 5)
type ComplexCalculation = Subtract, 15>;
// ^? type ComplexCalculation = 5

—

6. チーフアーキテクトからの、心優しいアドバイス

今回学んだ「型レベルの四則演算」は、正直にお話しすると、普段のWeb制作や一般的なお仕事の開発で毎日ガシガシ使うものではありません。

「えっ、使わないの!?」と驚かれるかもしれませんね(笑)。
実務で複雑すぎる型パズルを書きすぎると、他のメンバーがコードを読めなくなってしまったり、TypeScriptのコンパイルが重くなってしまう原因(再帰の限界を超えてエラーになるなど)になることもあるからです。

ですが、この技術を「知っていること」自体に、もの凄く大きな価値があります。

  • 「型って、こんなに柔軟に形を変えられるんだ!」
  • 「`extends` や `infer` って、こうやって使うとパズルみたいに条件分岐ができるんだ!」

この感覚を一度でも味わっておくと、普段書いている「普通の型定義」が驚くほど簡単に見えてきます。自転車の練習で、急な坂道を登りきった後に、平らな道がものすごく楽に走れるようになるあの感覚と同じです。

もし途中で「うーん、ちょっと頭がこんがらがってきたぞ」と思ったら、いつでもこのブロックのおもちゃの絵を思い出してください。一歩ずつ、楽しみながら、TypeScriptと仲良くなっていきましょう。あなたはもう、型システムの深淵な入り口にしっかりと立っていますよ!

これからも、あなたのフロントエンドの旅を全力で応援しています!

コメント

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