ترکیب توابع کوچک برای ساخت منطق بزرگ‌تر: Function Composition

مقدمه

در درس قبل با Pure Functions آشنا شدیم؛ توابعی قابل پیش‌بینی که هیچ اثر جانبی تولید نمی‌کنند. یکی از دلایل اصلی ارزشمند بودن توابع خالص این است که می‌توان چند تابع کوچک و خالص را با یکدیگر ترکیب کرد تا منطق پیچیده‌تری ساخته شود؛ بدون نگرانی از تداخل یا اثرات جانبی پنهان. به این تکنیک، Function Composition (ترکیب توابع) می‌گویند.

ایده‌ی اصلی Function Composition

Function Composition یعنی خروجی یک تابع، مستقیماً به‌عنوان ورودی تابع بعدی استفاده شود؛ به این ترتیب، چند تابع ساده به یک زنجیره‌ی پردازشی تبدیل می‌شوند. اگر f و g دو تابع باشند، ترکیب آن‌ها یعنی چیزی شبیه f(g(x)): ابتدا x به g داده می‌شود، سپس نتیجه‌ی آن به f داده می‌شود.

یک مثال ساده بدون هیچ ابزار خاصی

function double(x) {
  return x * 2;
}

function increment(x) {
  return x + 1;
}

let result = increment(double(5));
console.log(result); // 11 — ابتدا 5 دوبرابر شد (10)، سپس یک واحد اضافه شد (11)

در این مثال، خروجی double(5) مستقیماً به‌عنوان ورودی increment استفاده شده است. این ساده‌ترین شکل ممکن از Function Composition است، هرچند هنوز چیزی به نام «ترکیب» به‌صورت رسمی نساخته‌ایم.

مشکل: زنجیره‌ی تودرتو با افزایش تعداد توابع

وقتی تعداد توابع بیشتر شود، این روش تودرتو نوشتن به‌سرعت ناخوانا می‌شود:

function double(x) { return x * 2; }
function increment(x) { return x + 1; }
function square(x) { return x * x; }

let result = square(increment(double(5)));
// خواندن این خط از داخل به بیرون است، که با ترتیب اجرای واقعی مطابقت ندارد

مشکل اینجاست که ترتیب خواندن این عبارت (از بیرون به داخل: square، سپس increment، سپس double) دقیقاً برعکس ترتیب اجرای واقعی آن است (از داخل به بیرون: ابتدا double، سپس increment، سپس square). هرچه توابع بیشتر شوند، این ناخوانایی بیشتر می‌شود.

ساخت یک تابع compose

راه‌حل رایج، نوشتن یک تابع کمکی است که چند تابع را می‌گیرد و یک تابع جدید و واحد برمی‌گرداند که آن‌ها را به‌ترتیب اجرا می‌کند:

ساخت تابع کمکی compose
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

compose(f, g) یک تابع جدید برمی‌گرداند که وقتی با آرگومان x فراخوانی شود، ابتدا g(x) را اجرا می‌کند و سپس نتیجه را به f می‌دهد. در این مثال، compose(double, increment) یعنی: ابتدا increment اجرا شود، سپس نتیجه به double داده شود. به همین دلیل incrementThenDouble(5) برابر 12 است: ابتدا 5 + 1 = 6، سپس 6 * 2 = 12.

این تابع compose خودش یک نمونه‌ی مستقیم از Higher-Order Function است که در فصل قبل دیدیم: هم توابع را به‌عنوان آرگومان می‌پذیرد و هم یک تابع جدید برمی‌گرداند.

ترکیب چند تابع با استفاده از reduce

برای ترکیب بیش از دو تابع، می‌توان از متد reduce که در فصل آرایه دیدیم استفاده کرد تا یک compose عمومی‌تر بسازیم:

ترکیب چند تابع با reduce
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل رفتار جاوااسکریپت

در این نسخه، compose با استفاده از Rest Parameter (که در فصل توابع مبانی دیدیم)، تعداد نامحدودی تابع می‌پذیرد. متد reduceRight دقیقاً مثل reduce عمل می‌کند، با این تفاوت که پیمایش را از انتهای آرایه به ابتدای آن انجام می‌دهد؛ این یعنی توابع از راست به چپ اجرا می‌شوند: ابتدا double (آخرین تابع در لیست)، سپس increment، و در نهایت square. برای combined(5): ابتدا double(5) = 10، سپس increment(10) = 11، و در نهایت square(11) = 121.

این ترتیب اجرا (راست به چپ) قراردادی رایج در پیاده‌سازی compose است که از نماد ریاضی f(g(x)) الهام گرفته شده؛ برخی کتابخانه‌ها نسخه‌ی معکوس آن، به نام pipe، را نیز ارائه می‌دهند که توابع را از چپ به راست اجرا می‌کند و معمولاً خواناتر تلقی می‌شود.

چرا Function Composition به Pure Functions وابسته است؟

Function Composition زمانی قابل‌اعتماد و قابل‌پیش‌بینی است که توابع ترکیب‌شونده، خالص باشند. اگر یکی از توابع داخل زنجیره Side Effect داشته باشد (مثلاً یک متغیر Global را تغییر دهد)، رفتار کل زنجیره دیگر فقط به ورودی اولیه بستگی نخواهد داشت و ممکن است بسته به تعداد دفعات اجرا، نتایج متفاوتی تولید کند. به همین دلیل، Function Composition معمولاً به‌عنوان یکی از تکنیک‌های اصلی برنامه‌نویسی تابعی، همیشه در کنار اصل Pure Function مطرح می‌شود.

جدول جمع‌بندی

مفهومتوضیح
Function Compositionترکیب چند تابع به‌صورتی که خروجی یکی، ورودی دیگری شود
composeالگوی رایج که توابع را از راست به چپ اجرا می‌کند
pipeنسخه‌ی معکوس compose که توابع را از چپ به راست اجرا می‌کند
پیش‌نیاز مهمتوابع ترکیب‌شونده باید ترجیحاً خالص (Pure) باشند

ارتباط با سایر مفاهیم

Function Composition مستقیماً بر پایه‌ی سه مفهومی بنا شده که پیش‌تر در همین فصل دیدیم: First-Class Functions (که امکان پاس دادن و بازگرداندن تابع را می‌دهد)، Higher-Order Functions (که خودِ compose نمونه‌ای از آن است)، و Pure Functions (که ترکیب توابع را قابل‌اعتماد می‌کند). در درس بعدی، با IIFE آشنا می‌شویم که گاهی برای اجرای فوری یک تابع ترکیبی یا مقداردهی اولیه استفاده می‌شود.

جمع‌بندی

Function Composition تکنیکی است که در آن، خروجی یک تابع مستقیماً ورودی تابع بعدی می‌شود تا از چند تابع کوچک، یک زنجیره‌ی پردازشی واحد ساخته شود. تابع کمکی compose این کار را با ترکیب چند تابع در قالب یک تابع جدید انجام می‌دهد و معمولاً توابع را از راست به چپ اجرا می‌کند. این تکنیک زمانی بهترین نتیجه را می‌دهد که تمام توابع ترکیب‌شونده، خالص (Pure) باشند.