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);
٥ — توقع قبل ما تشوف (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 (شغّل الاتنين على نفس السعر وقارن).
٨ — المصدر الأساسي للتعمق
- javascript.info — Currying — الشرح المرجعي مع دالة curry() عامة (تستخدم أدوات هنشرحها لاحقًا).
- MDN — Closures — نفس مرجع 0010، السند الأساسي لـ currying.
- ورقة المراجعة: reference/js-execution-model.html