مشكلة HKT في TypeScript
افهموا سبب صعوبة مُنشئات الأنواع العامة
مشكلة HKT في TypeScript درس مجاني في TypeScript Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في TypeScript Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة TypeScript Academy 4 دروس في المجموع.
ما النوع عالي الرتبة
يجرّد النوع عالي الرتبة (HKT) من مُنشئ نوع، لا من نوع محدد، مثل Array أو Promise، وهو شيء يحتاج إلى وسيط كي يصبح نوعًا فعليًا.
إن Array وحده ليس نوعًا، أما Array<string> فهو نوع. إن Array دالة تعمل على الأنواع، وتتيح لنا HKTs التعامل مع هذه الدوال بصورة عامة.
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، سواء كانت مصفوفة أو خيارًا أو Promise. واجهة واحدة وحاويات متعددة.
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 معلمة عامة. ويصدر المصرّف الخطأ «Type F is not generic».
// 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)فكرة الترميز
الحل البديل الشائع هو إزالة التوظيف (defunctionalization): فبدلًا من تطبيق F مباشرةً، نمنح كل حاوية وسمًا نصيًا (URI)، ونحتفظ بسجل يربط الوسم مع وسيط بالنوع الفعلي. وهكذا يصبح تطبيق النوع مجرد بحث.
// 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واحدة. - الحل هو ترميز يستخدم وسوم URI وسجلًا.
التالي: تعريف منشئات الأنواع باستخدام سلاسل URI.
الأسئلة الشائعة
هل درس «مشكلة HKT في TypeScript» مجاني؟
نعم — نص درس «مشكلة HKT في TypeScript» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة TypeScript Academy، انتقل إلى CoddyKit PRO. تتضمن دورة TypeScript Academy 4 دروس في المجموع.
ماذا ستتعلم في «مشكلة HKT في TypeScript»؟
افهموا سبب صعوبة مُنشئات الأنواع العامة تتمرن على TypeScript Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ TypeScript Academy؟
لا تُشترط خبرة سابقة. TypeScript Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.
كم من الوقت يستغرق درس «مشكلة HKT في TypeScript»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس TypeScript Academy هذا؟
نعم. كل درس في TypeScript Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- مشكلة HKT في TypeScript
- تعريف مُنشئات الأنواع
- نمط HKT خفيف الوزن
- Functors وMappers العامة