توابع خالص و اثرات جانبی: Pure Functions و Side Effects

مقدمه

در دو درس قبل، با First-Class Functions، Higher-Order Functions و Callback آشنا شدیم؛ مفاهیمی که بیشتر به «چگونگی جابه‌جا کردن توابع» مربوط می‌شدند. در این درس، به یک ویژگی متفاوت می‌پردازیم که به «چگونگی رفتار داخلی یک تابع» مربوط است: خالص بودن (Pure) یا داشتن اثرات جانبی (Side Effects). این مفهوم پایه‌ی بسیاری از اصول برنامه‌نویسی تابعی و کدنویسی قابل‌اعتماد است.

Pure Function چیست؟

یک تابع، خالص (Pure) نامیده می‌شود اگر دو ویژگی زیر را همزمان داشته باشد:

  • برای ورودی‌های یکسان، همیشه دقیقاً همان خروجی را برمی‌گرداند.
  • هیچ تأثیری روی هیچ‌چیزی خارج از خودش نمی‌گذارد (بدون Side Effect).
function add(a, b) {
  return a + b;
}

console.log(add(2, 3)); // همیشه 5
console.log(add(2, 3)); // همیشه 5، هر چند بار که فراخوانی شود

تابع add یک تابع خالص است: با ورودی‌های 2 و 3، همیشه دقیقاً 5 برمی‌گرداند و هیچ متغیر یا داده‌ای خارج از خودش را تغییر نمی‌دهد.

Side Effect چیست؟

Side Effect (اثر جانبی) به هر تغییری گفته می‌شود که یک تابع، فراتر از محاسبه و بازگرداندن مقدار خروجی خودش، روی دنیای بیرون از خودش ایجاد می‌کند. نمونه‌های رایج Side Effect عبارت‌اند از:

  • تغییر یک متغیر خارج از تابع (مثل یک متغیر Global)
  • تغییر مستقیم یک آرگومان از نوع Reference Type (مثل تغییر یک Object یا Array ورودی)
  • چاپ چیزی در Console
  • ارسال درخواست به سرور
  • تغییر محتوای صفحه (DOM)
  • خواندن یا نوشتن در localStorage

مقایسه‌ی یک تابع خالص و ناخالص

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

تحلیل کد

addImpure با فراخوانی دوم، خروجی متفاوتی نسبت به فراخوانی اول تولید می‌کند (10 سپس 20)؛ با اینکه هر دو بار دقیقاً همان آرگومان 10 پاس داده شده است. دلیل این رفتار این است که این تابع به یک متغیر Global بیرون از خودش (total) وابسته است و آن را تغییر می‌دهد؛ این دقیقاً یک Side Effect است. اما addPure با ورودی‌های یکسان (0 و 10)، همیشه همان خروجی 10 را برمی‌گرداند و هیچ متغیر خارجی را تغییر نمی‌دهد؛ به همین دلیل یک تابع خالص است.

Side Effect در تغییر مستقیم آرگومان

یکی از رایج‌ترین انواع Side Effect، تغییر مستقیم یک Object یا Array که به‌عنوان آرگومان پاس داده شده است:

تغییر مستقیم آرگومان به‌عنوان یک Side Effect
خروجی کنسول اینجا نمایش داده می‌شود...

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

همان‌طور که در فصل انواع داده دیدیم، آرایه یک Reference Type است؛ به همین دلیل، وقتی cart داخل تابع مستقیماً با push تغییر می‌کند، این تغییر روی همان آرایه‌ی اصلی (myCart) هم اعمال می‌شود؛ چون هر دو به یک شیء در حافظه اشاره دارند. این یعنی myCart بعد از فراخوانی تابع، بدون اینکه مستقیماً myCart.push نوشته باشیم، تغییر کرده است؛ این دقیقاً همان نوع Side Effect است که می‌تواند در کدهای بزرگ، منبع باگ‌های سخت‌ردیابی شود.

نسخه‌ی خالص همین تابع، به‌جای تغییر مستقیم آرایه‌ی ورودی، یک آرایه‌ی جدید برمی‌گرداند:

function addItemPure(cart, item) {
  return [...cart, item];
}

let myCart = ["کتاب"];
let newCart = addItemPure(myCart, "دفتر");

console.log(myCart);   // ["کتاب"] — بدون تغییر
console.log(newCart);  // ["کتاب", "دفتر"]

همان‌طور که در فصل آرایه‌ها دیدیم، Spread Operator برای ساخت یک آرایه‌ی جدید به‌جای تغییر مستقیم آرایه‌ی اصلی، دقیقاً همین هدف را دنبال می‌کند.

آیا هر Side Effect بد است؟

نه؛ Side Effect ذاتاً بد نیست و اصلاً برنامه‌ای که هیچ Side Effect ای نداشته باشد، عملاً بی‌فایده خواهد بود؛ چاپ کردن در Console، ارسال داده به سرور، یا نمایش چیزی در صفحه، همگی Side Effect هستند و دقیقاً همان چیزهایی‌اند که یک برنامه‌ی واقعی باید انجام دهد. هدف اصول برنامه‌نویسی تابعی این نیست که Side Effect را کاملاً حذف کنیم، بلکه این است که آن‌ها را مدیریت‌شده و قابل‌پیش‌بینی نگه داریم؛ یعنی منطق اصلی محاسبات را در توابع خالص بنویسیم، و Side Effect ها را در بخش‌های مشخص و محدودی از برنامه متمرکز کنیم.

چرا Pure Function ارزشمند است؟

توابع خالص چند مزیت عملی مهم دارند:

  • قابل پیش‌بینی بودن: چون خروجی فقط به ورودی وابسته است، رفتار تابع همیشه یکسان و قابل استدلال است.
  • سادگی تست: برای تست یک تابع خالص، فقط کافی است ورودی و خروجی موردانتظار را بررسی کنیم؛ بدون نیاز به شبیه‌سازی وضعیت‌های پیچیده‌ی خارجی.
  • عدم تداخل: چون هیچ داده‌ی خارجی را تغییر نمی‌دهند، فراخوانی یک تابع خالص هرگز رفتار بخش دیگری از برنامه را به‌طور غیرمنتظره تغییر نمی‌دهد.

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

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

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

مفهوم Pure Function مستقیماً به رفتار Reference Types که در فصل انواع داده بررسی کردیم مرتبط است؛ چون بسیاری از Side Effect های ناخواسته، دقیقاً از تغییر مستقیم یک Object یا Array ورودی ناشی می‌شوند. در درس بعدی همین فصل، با Function Composition آشنا می‌شویم که معمولاً بر پایه‌ی ترکیب چند تابع خالص با یکدیگر ساخته می‌شود.

جمع‌بندی

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