المرحلة 0 · الأسبوع 3 · الدرس 0011 · ~35 دقيقة

Currying — سلسلة الحقائب

ليه الدرس ده؟ في تمرين التوقع الأخير بدرس 0010 (makeAdder) كتبت دالة بالشكل ده من غير ما تعرف: دالة بترجع دالة بترجع دالة، كل واحدة باراميتر واحد بس. ده اسمه الرسمي Currying — وهو نفس فكرة "الخانات" اللي عرفتها مع bind في 0007، بس من غير bind خالص، بالـ closures بس.

🔁 مراجعة سريعة قبل ما تبدأ (~5 دقايق) — بالأمثلة، مش بس لينكات:

من 0007 — كارت "الخانات" (partial application بـ bind):

function multiply(a, b) { return a * b; }
const double = multiply.bind(null, 2);
double(5);
double(100);

توقع من الذاكرة: الرقمين طالعين كام؟ (لو نسيت الآلية، ارجع للكارت في 0007 قبل ما تكمل.)

من 0010 — نفس تمرين التوقع اللي حللته (كان curried من غير ما تعرف):

function makeAdder(base) {
  return function (n) { return base + n; };
}
const addFive = makeAdder(5);
addFive(0);

افتكر: ليه base فضلت متاحة جوه الدالة اللي رجعت، حتى بعد ما makeAdder خلصت؟ (لو مش متذكر الآلية، راجع محاكي 0010 الأول.)

١ — الفكرة في 3 جمل

① Currying = تحويل دالة بتاخد كذا argument لـ سلسلة دوال، كل واحدة بتاخد argument واحد بس، وبترجع الدالة اللي بعدها.

② كل دالة في السلسلة شايلة 🎒 حقيبة (closure) فيها كل الـ arguments اللي اتجمعت لحد دلوقتي — لحد ما آخر واحدة تتنادى وتنفّذ الحساب الكامل.

③ الفايدة العملية: تقدر "توقف" عند أي نقطة في السلسلة وتاخد دالة متخصصة تقدر تستخدمها كذا مرة بقيم مختلفة — زي ما شفت بالظبط مع bind في 0007، بس هنا بآلية closures صرفة.

٢ — مثال أساسي: دالة جمع مقسّمة لـ 3 مستويات

function add(a) {
  return function (b) {
    return function (c) {
      return a + b + c;
    };
  };
}

console.log(add(1)(2)(3));

لاحظ الشكل: add(1)(2)(3) — كل قوس بينده على الدالة اللي رجعت من القوس اللي قبله. الناتج الحقيقي:

6

والحاجة الأهم: تقدر توقف عند أي مستوى وتستخدمه كذا مرة:

const addOneTwo = add(1)(2);
console.log(addOneTwo(10));
console.log(addOneTwo(100));
13
103

✓ متحقق بتشغيل Node.js فعلي (2026-08-14). addOneTwo شايلة حقيبة فيها a=1 وb=2 للأبد — كل ما تناديها بـ c مختلف، بتحسب من نفس القيم المحفوظة.

٣ — الفرق بين Currying وpartial application (bind) اللي عرفتها في 0007

الاتنين بيحلّوا مشكلة شبيهة (تجهيز دالة متخصصة بقيم محجوزة مقدمًا)، بس بشكل مختلف تمامًا:

Partial application (bind)Currying
شكل النداءf.bind(null, a) ثم g(b, c) — الباقي مرة واحدةf(a)(b)(c) — كل مستوى argument واحد بس، دايمًا
الآليةexplicit binding (قاعدة 2 من this)closures بس — مفيش this خالص هنا

جرّب الفرق بنفسك — نفس الدالة، بس بعت اتنين arguments مع بعض لمستوى curried واحد:

const addOne = add(1);
console.log(addOne(2, 3));

الناتج الحقيقي: دالة، مش رقم — لأن addOne(2, 3) بس بتستخدم أول argument (2) كـ b، والـ 3 بيتجاهله تمامًا. السلسلة لسه محتاجة نداء تالت منفصل بـ c. Currying الحقيقي مايقبلش تجميع arguments مع بعض في مستوى واحد — لازم واحد لكل مستوى، دايمًا.

✓ متحقق بتشغيل Node.js فعلي (2026-08-14).

٤ — شوفها بعينك: سلسلة الحقائب في المحاكي

لاحظ إزاي كل مستوى بيترك حقيبة جديدة في 🟩، وكل حقيبة جديدة بتفضل مرتبطة بالحقيبة اللي قبلها — سلسلة كاملة من الروابط. توقع قبل كل خطوة!

function add(a) {
  return function (b) {
    return function (c) {
      return a + b + c;
    };
  };
}
const step1 = add(1);
const step2 = step1(2);
const result = step2(3);
دوس «خطوة» عشان نبدأ.
Console:
🟦 Call Stack (مؤقت)
🟩 بيئات باقية (بره الـ stack)

٥ — توقع قبل ما تشوف (3 تمارين)

function multiplyC(a) {
  return function (b) { return a * b; };
}
const timesFour = multiplyC(4);
console.log(timesFour(5).toString());
function add(a) { return b => c => a + b + c; }
console.log(add(1)(2, 3));
function add(a) { return b => c => a + b + c; }
const x = add(10);
const y = add(20);
x(1)(1); y(1)(1);

٦ — اختبر نفسك

س1: التعريف الدقيق لـ currying إيه؟

س2: إيه اللي بيخلّي currying ممكن من الأساس؟

س3: add(1) وadd(2) — الحقيبتين الناتجتين بيتشاركوا في نفس المتغيرات ولا منفصلين؟

س4 (سؤال judgment): امتى currying مفيد فعليًا في شغل حقيقي؟

٧ — التحدي الأخير: صمّم حاسبة خصم curried (من غير AI!)

المطلوب: دالة curried اسمها applyDiscount، بالشكل: applyDiscount(percent)(price) — بترجع السعر بعد الخصم.

بعد ما تكتبها، اعمل منها نسخة متخصصة زي const tenPercentOff = applyDiscount(10);، واستخدمها على 3 أسعار مختلفة — تأكد إن كل نتيجة صح.

🎁 بونص لو حابب: اعمل نسخة تانية const twentyPercentOff = applyDiscount(20); وأثبت إنها مستقلة تمامًا عن tenPercentOff (شغّل الاتنين على نفس السعر وقارن).

٨ — المصدر الأساسي للتعمق