TYPE ⊆ SET منهج TypeScriptمن الجذور tsc --noEmit ✓
الإقليم ٠00

السؤال The Question

لماذا أنواع في لغة بلا أنواع؟


النبذة

قبل أن تكتب سطر TypeScript واحداً، عليك أن تفهم الجريمة التي جاءت لتمنعها. TypeScript ليست لغة جديدة؛ هي طبقة من البرهان فوق JavaScript. ولفهم لماذا احتاج الناس هذه الطبقة، علينا أولاً أن نشعر بالألم الذي بلا طبقة. لن نشرح TypeScript في هذا الدرس إطلاقاً. سنشرح الجرح.


اللغز المستفزّ

تخيّل أنك تكتب JavaScript. هذه دالة بسيطة:

javascript
function area(shape) { return shape.width * shape.height; }

سؤال: ما الذي يمنع هذا الكود من الانفجار؟

لا شيء. لا شيء على الإطلاق. كل هذه الاستدعاءات تُترجم وتعمل وتبدأ بالتنفيذ:

javascript
area({ width: 3, height: 4 }); // 12 — جيد area({ width: 3 }); // NaN — لا خطأ، فقط نتيجة سامّة area("rectangle"); // NaN area(null); // ينفجر وقت التشغيل: Cannot read properties of null area({ w: 3, h: 4 }); // NaN — اسم خاطئ، لا أحد يشتكي

في C، لو حاولت تمرير char* لدالة تتوقّع struct Shape، المترجم يصرخ قبل أن ينتج برنامجاً أصلاً. النوع جزء من العقد، والمترجم يحرس العقد. في Python، الوضع أقرب لـ JS: ستكتشف الخطأ فقط حين يصل التنفيذ لتلك السطر — وربما لا يصل إلا بعد ساعة من تشغيل الخادم في الإنتاج.

الآن السؤال الحقيقي، وفكّر فيه قبل أن تكمل:

ملاحظة

JavaScript نجحت وانتشرت في كل متصفّح وخادم على الأرض بدون أنواع. لو كانت الأنواع ضرورية لما عاشت اللغة. إذاً ليست ضرورية. فلماذا اخترع الناس TypeScript وتبنّتها أضخم الشركات؟ ما الذي تغيّر؟ ما الذي يشترونه بالضبط، وما الثمن الذي يدفعونه؟

اكتب إجابتك في ذهنك قبل أن تقرأ. "تكتشف الأخطاء مبكراً" إجابة سطحية. ادفعها أعمق: أي أخطاء؟ ولماذا صارت مكلفة الآن بينما لم تكن كذلك في ٢٠٠٩؟


الدرس: ليش وُجدت TypeScript

المشكلة ليست "الأخطاء"، المشكلة هي الحجم والزمن

area أعلاه دالة من سطر. أنت ترى عيبها بعينك. لكن تخيّلها داخل ملف من ٢٠٠٠ سطر، تستدعيها دالة تستدعيها دالة، والكائن shape بُني قبل ١٤ خطوة في ملف آخر كتبه شخص آخر قبل سنتين. الآن السؤال "هل لهذا الكائن خاصية height؟" لم يعد سؤالاً تجيب عليه بالنظر. صار سؤالاً يحتاج تحقيقاً.

JavaScript في ٢٠٠٩ كانت تكتب سكربتات صغيرة تُحرّك صورة في صفحة. JavaScript في ٢٠٢٠ تكتب أنظمة من مئات آلاف الأسطر يصونها مئات المبرمجين لسنين. اللغة نفسها لم تتغيّر؛ الحجم الذي تُستعمل فيه انفجر. وعند هذا الحجم، السؤال البسيط "ما شكل هذه القيمة؟" يصير أغلى سؤال في اليوم.

هذا أول "ليش" جوهري: TypeScript ليست حلاً لمشكلة في اللغة، بل حل لمشكلة في المقياس (scale). هي إجابة آليّة، فورية، ومضمونة على السؤال "ما شكل هذه القيمة، وهل أستعملها بشكل سليم؟" — مكرّراً ملايين المرّات عبر قاعدة كود ضخمة، في كل مرّة تكتب فيها نقطة ..

العقد الضمني الذي كان دائماً موجوداً

انظر area مرّة أخرى. حتى في JavaScript الخام، الدالة لها عقد: "أعطني شيئاً له width رقمي و height رقمي". هذا العقد موجود في رأس من كتبها، وربما في تعليق، وربما لا. إنه حقيقي — البرنامج يعتمد عليه — لكنه غير مكتوب وغير مفحوص.

ملاحظة

الفكرة المركزية: كل برنامج JavaScript لديه أنواع بالفعل. لكل دالة عقد عن شكل مدخلاتها ومخرجاتها. الأنواع موجودة في تصميمك سواء كتبتها أو لا. الفرق الوحيد: هل هي مكتوبة ومفحوصة آلياً، أم مخبّأة في رؤوس البشر وتنكسر بصمت؟

TypeScript لا تضيف أنواعاً لبرنامجك. هي تجعلك تكتب الأنواع التي كانت موجودة أصلاً، ثم تتحقّق منها نيابةً عنك. هذا فرق جوهري عن C، حيث الأنواع ضرورية حتى يعمل الكود (المترجم يحتاجها ليعرف كم بايت يحجز). في TypeScript الأنواع ليست ضرورية للتشغيل أبداً — وهذا يقودنا للسر الأكبر.

السر الأكبر: الأنواع تُمحى

في C، النوع int يقرّر أن المتغيّر يحجز ٤ بايت، وأن + يعني جمع أعداد صحيحة لا أرقام عائمة. النوع يقرّر ماذا يحدث وقت التشغيل. انزع الأنواع من C ولا يبقى برنامج.

في TypeScript، العكس تماماً. خذ هذا:

typescript
function area(shape: { width: number; height: number }): number { return shape.width * shape.height; }

مرّره على المترجم، وانظر ما ينتج. الأنواع تختفي بالكامل:

javascript
function area(shape) { return shape.width * shape.height; }

هذا ليس تبسيطاً — هذا حرفياً ما يخرج. tsc يحذف كل علامة نوع ويُخرج JavaScript عادية. المتصفّح أو Node لا يرى نوعاً واحداً. النوع لم يكن موجوداً أصلاً وقت التشغيل؛ كان موجوداً فقط أثناء الترجمة، ليُفحَص، ثم يُرمى.

تأمّل كم هذا غريب مقارنةً بكل ما تعرفه:

CPythonTypeScript
متى تُعرف الأنواع؟وقت الترجمةوقت التشغيلوقت الترجمة
هل تؤثّر على التنفيذ؟نعم (الذاكرة، العمليات)نعم (dispatch)لا — تُمحى تماماً
ماذا لو كان النوع خاطئاً؟لا يُترجمينفجر وقت التشغيللا يُترجم — لكن لو أجبرته، يعمل ويكذب

هذا هو التوتّر المركزي الذي ستعيش معه طوال هذا المنهج، وهو الذي تلتحم عنده شجرتا الخريطة في النهاية: TypeScript تتكلّم عن قيمٍ موجودة وقت التشغيل، بأنواعٍ غير موجودة وقت التشغيل. هي تبرهن نظريات عن عالمٍ لا تستطيع رؤيته لحظة تنفيذه. طوال الوقت هناك عالمان: عالم الأنواع (يعيش وقت الترجمة، يُمحى) وعالم القيم (يعيش وقت التشغيل). فهم الجدار بينهما هو فهم TypeScript.

إذاً ماذا نشتري، وبأي ثمن؟

نشتري: إجابة فورية مبرهَنة على "هل أستعمل هذه القيمة بشكل متّسق مع شكلها؟"، عبر كامل قاعدة الكود، في كل لحظة تحرير. نشتري قدرة المحرّر على إكمال أسماء الخصائص، وإعادة التسمية الآمنة، والانتقال للتعريف. نشتري توثيقاً لا يكذب لأنه مفحوص. نشتري ثقةً عند تعديل كود قديم: إن كسرت عقداً، سيخبرك ألف مكان فوراً بدل أن تكتشفه من شكوى مستخدم.

ندفع: كتابة الأنواع (عبء). وقت ترجمة (المُبرهِن يحتاج وقتاً). وأحياناً قتالاً مع المترجم على أشياء تعرف أنها صحيحة لكنه لا يستطيع برهانها — لأن كل نظام أنواع حاسم لا بد أن يرفض بعض البرامج الصحيحة (سنرى لماذا هذا حتميّ رياضياً في الإقليم ٩). وندفع وهماً خطيراً: لأن الأنواع تُمحى، فهي لا تحميك وقت التشغيل. إن دخلت بيانات من الشبكة بشكل لا يطابق النوع الذي ادّعيته، TypeScript لن تعرف ولن تمنع شيئاً — لأنها ليست هناك أصلاً. هذا الثمن سنفهمه عميقاً في الإقليم الأخير.


لا لغز عملياً هنا — بل تمرين تأمّل

هذا درس مفهومي بحت؛ لا كود تكتبه. لكن قبل أن تكمل، أجب على هذه كتابةً (لنفسك)، لأن بقية المنهج يفترض أنك استوعبتها:

  1. في C، لو حذفت كل الأنواع، هل يبقى لديك برنامج يعمل؟ في TypeScript؟ ما الذي يقوله هذا الفرق عن دور النوع في كل لغة؟
  2. قلنا "كل برنامج JS لديه أنواع أصلاً". خذ دالة JS كتبتها سابقاً، واكتب بالعربية عقد كل وسيط ومخرَج. هل كان العقد واضحاً في رأسك؟ هل كان مكتوباً في أي مكان؟
  3. لو كانت الأنواع تُمحى ولا تؤثّر على التشغيل، فما القيمة التي تبقى منها بعد لحظة الترجمة الواحدة؟ (تلميح: القيمة ليست في الناتج، بل في لحظة الفحص نفسها وما تمنعه من الوصول للناتج.)

الخلاصة — وصلٌ في الشجرة

أسّسنا الجذر الذي تتفرّع منه كل عقدة قادمة:

  • TypeScript ليست لغة جديدة بل طبقة برهان فوق JavaScript، وُلدت من مشكلة المقياس لا من نقص في اللغة.
  • كل برنامج لديه أنواع أصلاً؛ TypeScript تجعلك تكتبها وتفحصها بدل تركها ضمنيّة وهشّة.
  • الأنواع تُمحى قبل التشغيل. هذا يخلق عالمين: عالم الأنواع (وقت الترجمة) وعالم القيم (وقت التشغيل). كل غرابة ستراها لاحقاً تسكن في الجدار بينهما.

عقدتان زُرِعتا هنا وستكبران: فكرة أن النوع عقدٌ على شكل قيمة ستتحوّل في الإقليم التالي إلى الفكرة الأدقّ — أن النوع مجموعة من القيم. وفكرة أن الأنواع تُمحى هي الشجرة الموازية التي تنمو بصمت حتى الإقليم ٩.

بذرة غموض

بذرة غموض: قلنا "كل نظام أنواع حاسم لا بد أن يرفض بعض البرامج الصحيحة". هذه ليست عيباً في TypeScript — إنها نظرية رياضية لا مهرب منها. ستفهم لماذا حين نصل للحواف. والأغرب: نظام أنواع TypeScript نفسه قويٌّ لدرجة أنه يستطيع أن يحسب ويتفرّع ويعيد استدعاء نفسه — لكن لا تفكّر بهذا الآن. أوّلاً، العدسة.

التالي: 01-types-are-sets.md

منهج TypeScript — من الجذور · نظرية مجموعات + مُبرهِن نظريات النوع = مجموعة