Recursive Types
type ที่อ้างถึงตัวเอง
หัวข้อที่มีชื่อว่า “type ที่อ้างถึงตัวเอง”recursion ที่ระดับ type หน้าตาเหมือน recursion ใน code เป๊ะ: type alias ที่พูดถึงตัวเอง ตัวอย่างคลาสสิกคือ JSON value — ข้อมูลที่ซ้อนลึกได้ทุกระดับ
type Json = | string | number | boolean | null | Json[] | { [key: string]: Json };
const ok: Json = { name: "Ada", tags: ["x", "y"], meta: { n: 1 } }; // ✅const bad: Json = { fn: () => 1 }; // ❌ function ไม่ใช่ JsonJson ปรากฏในนิยามของตัวเองสองที่ — ข้างใน array และข้างใน object — นั่นคือสิ่งที่ทำให้ Json อธิบายข้อมูลที่ลึกได้ไม่จำกัด recursive alias แบบนี้เจอบ่อยและ idiomatic เต็มที่
recursion บน tuple ด้วย infer
หัวข้อที่มีชื่อว่า “recursion บน tuple ด้วย infer”รูปแบบที่ทรงพลังกว่าคือการ recurse บน tuple type ลอกทีละ element ด้วย infer — เทียบเท่าการประมวลผล list แบบ head/tail ที่ระดับ type
// reverse tuple typetype Reverse<T extends unknown[]> = T extends [infer Head, ...infer Rest] ? [...Reverse<Rest>, Head] : [];
type R = Reverse<[1, 2, 3]>;// ^? [3, 2, 1]
// ความยาวของ tupletype Length<T extends unknown[]> = T["length"];type L = Length<[1, 2, 3]>; // 3[infer Head, ...infer Rest] แยก tuple เป็น element แรกกับที่เหลือ; type เรียกตัวเองบน Rest จนเจอ base case คือ tuple ว่าง นี่คือวิธีเขียน typed path builder, deep Get<Obj, "a.b.c"> lookup และ tuple math
flowchart LR a["[1, 2, 3]"] --> b["head 1, rest [2, 3]"] b --> c["head 2, rest [3]"] c --> d["head 3, rest []"] d --> e["base case: stop"]
instantiation-depth limit
หัวข้อที่มีชื่อว่า “instantiation-depth limit”type-level recursion ไม่ได้ไม่จำกัด compiler จำกัด recursive instantiation เพื่อป้องกันตัวเองจาก loop ไม่รู้จบ และคุณจะชนเพดานเมื่อ input ใหญ่:
// error เมื่อ recursion ลึกเกิน:// "Type instantiation is excessively deep and possibly infinite."limit เดิมอยู่ราว 50 ระดับสำหรับ recursion แบบตรงไปตรงมา ต่อมาผ่อนให้ tail-recursive conditional type (ที่ recursive call เป็นผลลัพธ์ทั้งหมด) ไปได้ลึกกว่ามาก — ราวหลักพัน การเขียน recursion แบบ tail-recursive (สะสมเข้า type parameter เพิ่มอีกตัว แล้วคืนค่าที่ base case) คือทริกมาตรฐานในการดันเพดาน:
// tail-recursive: สะสมผลลัพธ์ เพื่อให้ compiler optimize ได้type BuildTuple<N extends number, Acc extends unknown[] = []> = Acc["length"] extends N ? Acc : BuildTuple<N, [...Acc, unknown]>;รู้ว่าเมื่อไรควรหยุด
หัวข้อที่มีชื่อว่า “รู้ว่าเมื่อไรควรหยุด”recursive type คือเครื่องมือที่คมที่สุดในกล่อง type-level และใช้ผิดง่ายที่สุด ก่อนเขียนถามตัวเองว่า:
- เพื่อนร่วมทีมจะอ่านออกไหม? type เจ๋ง ๆ ที่ไม่มีใครกล้าแตะคือภาระ ไม่ใช่ทรัพย์สิน
- error message รับได้ไหม? recursive type ที่ลึกให้ error แย่มากเวลาพัง — บางทีแย่กว่า bug ที่กันเสียอีก
- type ที่ง่ายกว่าหรือ runtime check ใช้แทนได้ไหม? บ่อยครั้งคำตอบที่ซื่อสัตย์คือ type ธรรมดาบวก validation ที่ boundary (โมดูล Practical Mastery)
engineer ระดับ type-level ที่เก่งที่สุดเขียน recursion น้อยกว่า ที่ทำได้ เก็บ recursion ไว้ใช้กับไม่กี่กรณี — deep key path, builder API ที่แม่นยำ — ที่คุ้มค่าจริง