Kodokon kodokon.com

الأنماط الوظيفية: currying، التركيب، اللاتغيرية

هيكل كودك باستخدام currying وتركيب الدوال والبيانات اللامتغيرة لتكسب قابلية التنبؤ والاختبار.

9 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

تعامل JavaScript الدوال كـ قيم من الدرجة الأولى، مما يفتح الباب للأنماط الوظيفية. يحول currying دالة ذات n وسائط إلى سلسلة من دوال أحادية الوسيط: f(a, b, c) يصبح f(a)(b)(c). لا تخلط بينه وبين التطبيق الجزئي (partial application)، الذي يثبت بعض الوسائط في خطوة واحدة (fn.bind(null, a)). يتألق currying في إنشاء دوال متخصصة من دوال عامة.

JAVASCRIPT
const curry = (fn) => {
  const arity = fn.length;
  return function curried(...args) {
    if (args.length >= arity) return fn(...args);
    return (...rest) => curried(...args, ...rest);
  };
};

const add3 = (a, b, c) => a + b + c;
const addCurried = curry(add3);

console.log(addCurried(1)(2)(3)); // 6
console.log(addCurried(1, 2)(3)); // 6
curry عام قائم على عدد وسائط الدالة.

دقة في المواصفة: fn.length يعد فقط الوسائط الموجودة قبل أول وسيط افتراضي أو وسيط باقٍ (rest). (a, b = 1) => {} طوله 1، و(...args) => {} طوله 0. لذا يجب إعطاء curry القائم على عدد الوسائط دوال ذات وسائط بسيطة. التركيب (composition) هو الخطوة التالية: تجميع دوال صغيرة نقية في خطوط أنابيب قابلة للقراءة.

JAVASCRIPT
const pipe = (...fns) => (input) =>
  fns.reduce((acc, fn) => fn(acc), input);

const trim = (s) => s.trim();
const lower = (s) => s.toLowerCase();
const dashes = (s) => s.replace(/\s+/g, "-");

const toSlug = pipe(trim, lower, dashes);
console.log(toSlug("  Hello World  "));
// "hello-world"
pipe يطبق الدوال من اليسار إلى اليمين.

pipe(f, g, h)(x) يحسب h(g(f(x))): تقرؤه بترتيب التنفيذ. المتغير compose يطبق من اليمين إلى اليسار، مثل التدوين الرياضي. لا تكون هذه الأنابيب قيّمة إلا إذا كانت الدوال نقية - دون آثار جانبية، دون تبعيات خفية - وهو ما يستدعي بيانات لامتغيرة: بدلا من تغيير كائن، تنتج نسخة جديدة منه، وعندها تكفي مساواة المراجع لكشف التغيير (قلب مصالحة React).

JAVASCRIPT
const state = Object.freeze({
  user: { name: "Ada" },
  tags: ["js"]
});

const next = {
  ...state,
  tags: [...state.tags, "fp"]
};

const deepCopy = structuredClone(state);
deepCopy.user.name = "Grace"; // independent copy
نسخة جديدة عبر النسخ بدلا من التغيير.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. ما الفرق بين currying والتطبيق الجزئي؟
    • لا فرق، هما مترادفان
    • currying يحول f(a, b, c) إلى f(a)(b)(c)؛ التطبيق الجزئي يثبت بعض الوسائط في استدعاء واحد
    • التطبيق الجزئي يعمل فقط مع bind، وcurrying فقط مع الدوال السهمية
    • currying ينطبق فقط على الدوال ثنائية الوسائط
  2. بماذا يكافئ pipe(f, g, h)(x)؟
    • f(g(h(x)))
    • h(g(f(x)))
    • g(f(h(x)))
    • f(x) + g(x) + h(x)
  3. بعد const s = Object.freeze({ a: { b: 1 } })، هل يعمل الإسناد s.a.b = 2؟
    • لا، freeze يجمد شجرة الكائن بأكملها
    • لا، يرمى TypeError حتى خارج الوضع الصارم
    • نعم، لأن freeze سطحي: الكائن المتداخل s.a غير مجمد
    • نعم، لكن فقط في الوضع غير الصارم