0Pricing
TypeScript Academy · บทเรียน

ปัญหา HKT ใน TypeScript

ทำความเข้าใจว่าเหตุใดตัวสร้างชนิดข้อมูลแบบเจเนอริกจึงทำได้ยาก

ปัญหา HKT ใน TypeScript เป็นบทเรียน TypeScript Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน TypeScript Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส TypeScript Academy มีบทเรียนทั้งหมด 4 บทเรียน

ชนิดแบบลำดับสูงคืออะไร

ชนิดแบบลำดับสูง (HKT) ไม่ได้ทำให้ชนิดที่เป็นรูปธรรมเป็นนามธรรม แต่ทำให้ตัวสร้างชนิดเป็นนามธรรม เช่น Array หรือ Promise ซึ่งต้องรับอาร์กิวเมนต์ก่อนจึงจะกลายเป็นชนิดจริงได้

Array เพียงอย่างเดียวยังไม่ใช่ชนิด ส่วน Array<string> เป็นชนิด Array คือฟังก์ชันที่ทำงานกับชนิด และ HKT ช่วยให้เราสร้างฟังก์ชันดังกล่าวให้เป็นแบบทั่วไปได้

type StringArray = Array<string>;
type NumberArray = Array<number>;
// Array by itself is a type constructor, not a type

อธิบายระดับชนิดโดยสังเขป

เช่นเดียวกับที่ค่าต่าง ๆ มีชนิด ตัวสร้างชนิดก็มีระดับชนิด ชนิดธรรมดาอย่าง number มีระดับเป็น * ส่วน Array มีระดับเป็น * -> *: เมื่อป้อนชนิดหนึ่งชนิด ก็จะได้ชนิดกลับมา การทำให้สิ่งเหล่านี้เป็นแบบทั่วไปเรียกว่า "ชนิดแบบลำดับสูง"

type ValueLike = number;        // kind *
// Array needs one argument:    kind * -> *
// Map needs two arguments:     kind * -> * -> *

เป้าหมาย: Functor<F>

ในภาษาที่รองรับ HKT คุณสามารถเขียนนามธรรม Functor<F> เพียงหนึ่งแบบพร้อม map หนึ่งตัว ซึ่งทำงานกับคอนเทนเนอร์ ใด ๆ ใน F ไม่ว่าจะเป็นอาร์เรย์ ออปชัน หรือพรอมิส อินเทอร์เฟซหนึ่งแบบใช้ได้กับคอนเทนเนอร์หลายชนิด

interface Wish_Functor<F> {
  map<A, B>(fa: F<A>, f: (a: A) => B): F<B>;
}
// F<A> here is the wish - and TypeScript rejects it

ปัญหา

TypeScript ไม่รองรับการนำพารามิเตอร์ชนิดไปใช้กับอาร์กิวเมนต์ คุณไม่สามารถเขียน F<A> เมื่อ F เป็นพารามิเตอร์แบบทั่วไปได้ คอมไพเลอร์จะแจ้งข้อผิดพลาดว่า "ชนิด F ไม่ใช่ชนิดทั่วไป"

// interface Functor<F> {
//   map<A, B>(fa: F<A>, f: (a: A) => B): F<B>;
// }
// Error: Type F is not generic

เหตุใด TS จึงไม่มีความสามารถนี้

พารามิเตอร์ชนิดของ TypeScript เป็นแบบลำดับแรก: พารามิเตอร์เหล่านี้ครอบคลุมชนิดที่เป็นรูปธรรม ไม่ใช่ตัวสร้างชนิด ไม่มีไวยากรณ์สำหรับบอกว่า "F คือสิ่งที่รับอาร์กิวเมนต์ชนิดหนึ่งตัว" ดังนั้น F<A> จึงไม่มีความหมายสำหรับคอมไพเลอร์

// You can pass number, string, User as F...
// but not Array, not Promise (the constructors themselves)

จุดติดขัดที่เป็นรูปธรรม

หากไม่มี HKTs คุณต้องทำ map ซ้ำสำหรับคอนเทนเนอร์ทุกชนิด: แบบหนึ่งสำหรับอาร์เรย์ แบบหนึ่งสำหรับออปชัน และแบบหนึ่งสำหรับพรอมิส ตรรกะเหมือนกันทุกประการ แต่ระบบชนิดข้อมูลไม่สามารถรวมสิ่งเหล่านี้เข้าด้วยกันได้

declare function mapArray<A, B>(fa: A[], f: (a: A) => B): B[];
declare function mapOption<A, B>(fa: A | null, f: (a: A) => B): B | null;
// Same shape, but no way to abstract over the container

สิ่งที่เราต้องการสื่อ

เราต้องการลายเซ็นเพียงหนึ่งแบบ: เมื่อกำหนดคอนเทนเนอร์ของ A และฟังก์ชัน A -> B ให้คืนคอนเทนเนอร์ของ B โดย คงชนิดของคอนเทนเนอร์ไว้ อาร์เรย์ยังคงเป็นอาร์เรย์ ออปชันยังคงเป็นออปชัน

// Wish: map<F, A, B>(fa: F<A>, f: (a: A) => B): F<B>
// where F is preserved across the call

ภาษาอื่น

Haskell และ Scala มี HKTs มาให้โดยกำเนิด จึงสามารถเขียนคลาสชนิดข้อมูลอย่าง Functor, Applicative และ Monad ได้ในภาษาเหล่านั้น ส่วน TypeScript ต้องใช้แนวทางแก้ไขเพื่อจำลองความสามารถระดับเดียวกัน

// Haskell: class Functor f where fmap :: (a -> b) -> f a -> f b
// TypeScript: needs an encoding trick (next lessons)

แนวคิดการเข้ารหัส

แนวทางแก้ไขมาตรฐานคือ การแปลงฟังก์ชันเป็นข้อมูล: แทนที่จะใช้ F โดยตรง เรากำหนดแท็กสตริงให้คอนเทนเนอร์แต่ละชนิด (ตัวระบุทรัพยากรแบบสากล) และเก็บรีจิสทรีที่จับคู่แท็กกับอาร์กิวเมนต์เพื่อหาชนิดรูปธรรม การใช้ชนิดจึงกลายเป็น การค้นหา

// Instead of F<A>, we write Kind<F, A> where F is a URI string
// and a registry resolves it to the real type

สิ่งที่คุณจะสร้าง

ในบทเรียนถัดไป คุณจะสร้างการเข้ารหัส HKT แบบ fp-ts ทีละขั้น: รีจิสทรี URItoKind, การค้นหาด้วย Kind และท้ายที่สุดคือ Functor ทั่วไปที่มี map ซึ่งทำงานกับคอนเทนเนอร์ที่ลงทะเบียนไว้ชนิดใดก็ได้

// Roadmap:
// 1. Type constructors via URI strings
// 2. Kind<F, A> resolution
// 3. Generic Functor + instances

เหตุผลที่ควรรู้

โดยปกติคุณแทบไม่ต้องเขียนการเข้ารหัสนี้ด้วยตนเอง แต่ไลบรารีอย่าง fp-ts และ Effect อาศัยแนวทางนี้ การเข้าใจกลเม็ดนี้ช่วยให้คุณอ่านชนิดข้อมูลของไลบรารีเหล่านั้น เขียนนามธรรมทั่วไปเหนือคอนเทนเนอร์ และเห็นคุณค่าของสิ่งที่ระบบชนิดข้อมูลทำได้และทำไม่ได้

// Recognizing Kind<F, A> in library code is the payoff

ตรวจสอบความเข้าใจ

ทดสอบความเข้าใจของคุณเกี่ยวกับปัญหา HKT

ทบทวน

คุณได้พบกับปัญหาชนิดข้อมูลระดับสูงกว่า

  • HKTs ทำให้นามธรรมครอบคลุมตัวสร้างชนิดอย่าง Array ไม่ใช่ชนิดข้อมูลรูปธรรม
  • พารามิเตอร์ของ TypeScript เป็นชนิดข้อมูลลำดับแรก ดังนั้น F<A> จึงไม่ถูกต้อง
  • ข้อจำกัดนี้ทำให้ไม่สามารถสร้าง Functor ทั่วไปตัวเดียวที่มี map เดียวได้
  • แนวทางแก้ไขคือการเข้ารหัสโดยใช้แท็กตัวระบุทรัพยากรและรีจิสทรี

ถัดไป: การกำหนดตัวสร้างชนิดด้วยสตริงตัวระบุทรัพยากร

คำถามที่พบบ่อย

บทเรียน “ปัญหา HKT ใน TypeScript” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ปัญหา HKT ใน TypeScript” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส TypeScript Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส TypeScript Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ปัญหา HKT ใน TypeScript”

ทำความเข้าใจว่าเหตุใดตัวสร้างชนิดข้อมูลแบบเจเนอริกจึงทำได้ยาก คุณปฏิบัติ TypeScript Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน TypeScript Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน TypeScript Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “ปัญหา HKT ใน TypeScript” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน TypeScript Academy นี้ได้ไหม

ได้ บทเรียน TypeScript Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ปัญหา HKT ใน TypeScript
  2. การกำหนดตัวสร้างชนิดข้อมูล
  3. รูปแบบ HKT แบบน้ำหนักเบา
  4. ฟังก์ชันและตัวแมปแบบเจเนอริก
← กลับไปที่ TypeScript Academy