در این مقاله از مجموعه آموزشهای پیاستور میخواهیم درمورد برنامه نویسی تابعی صحبت کنیم که یک الگوی برنامهنویسی است که بر اساس تابعهای ریاضی بنا شده است. جالب است بدانید که در این سبک برنامهنویسی، تابعها شهروندان درجهیک محسوب میشوند، یعنی میتوان آنها را به عنوان ورودی به توابع دیگر ارسال کرد یا به عنوان خروجی از توابع بازگرداند.
مقدمه
برنامه نویسی تابعی (Functional Programming) یکی از سبکهای برنامه نویسی است که به جای استفاده از دستورات کنترلی و تغییر وضعیتها، بر روی استفاده از توابع خالص و اجتناب از تغییر دادهها تمرکز دارد. این شیوه برنامه نویسی به توسعهدهندگان این امکان را میدهد که کدی نوشتاری و قابل فهمتر، بدون خطاهای ناشی از تغییرات وضعیت، ایجاد کنند. برنامه نویسی تابعی به کمک ویژگیهایی مانند توابع اولویتدار، ترکیب توابع و دادههای غیرقابل تغییر، به سادگی میتواند به حل مشکلات پیچیده کمک کند. در زبانهای مختلفی همچون جاوا اسکریپت، پایتون، هاسکل و … از برنامه نویسی تابعی پشتیبانی میشود.
در این مقاله، قصد داریم تا به بررسی مفاهیم اصلی برنامه نویسی تابعی در زبان جاوا اسکریپت بپردازیم. با توجه به ویژگیهای منحصر به فردی که برنامه نویسی تابعی ارائه میدهد، این رویکرد میتواند به طور چشمگیری کدهای ما را تمیزتر، خواناتر و انعطافپذیرتر کند. به همین دلیل، در این مقاله از مفهوم توابع خالص تا استفاده از مفاهیم پیچیدهتری همچون ترکیب توابع، کاهش و نقشهگذاری، با مثالهای عملی، به شما کمک خواهیم کرد تا مهارتهای برنامه نویسی تابعی خود را در زبان جاوا اسکریپت تقویت کنید.
برنامه نویسی تابعی چیست؟
برنامه نویسی تابعی یک پارادایم برنامه نویسی است که بر اساس استفاده از توابع خالص و اجتناب از تغییرات وضعیت و دادهها طراحی شده است. در این سبک برنامه نویسی، به جای استفاده از دستورات کنترلی و تغییر وضعیتهای متغیرها، برنامهنویس روی تعریف توابعی تمرکز میکند که ورودیهای مشخصی را دریافت کرده و خروجیهای ثابت و پیشبینیشدهای تولید میکنند.
این توابع هیچگونه اثر جانبی ندارند و تنها به ورودی خود وابستهاند. برنامه نویسی تابعی همچنین به استفاده از مفاهیمی مانند ترکیب توابع، بازگشت (Recursion) و دادههای غیرقابل تغییر (Immutability) تاکید دارد. این رویکرد باعث میشود کدهای نوشتهشده سادهتر، قابل پیشبینیتر و عاری از خطاهای ناشی از تغییر وضعیتها شوند.
جاوا اسکریپت (JavaScript) به عنوان یک زبان برنامه نویسی چندپارادایمی (Multi-paradigm) میتواند از ویژگیهای برنامه نویسی تابعی نیز بهرهبرداری کند. این یعنی شما میتوانید در جاوا اسکریپت از توابع به روشهای مختلف استفاده کنید، همانطور که در برنامه نویسی تابعی مشاهده میشود.
اصول پایهای برنامه نویسی تابعی
اصول پایهای برنامه نویسی تابعی شامل استفاده از توابع خالص، دادههای غیرقابل تغییر، و توابع اولویتدار است که هدف آنها نوشتن کدی قابل فهم، تستشده و بدون عوارض جانبی است. این اصول به ما کمک میکنند تا برنامههای مقیاسپذیر و قابل نگهداری بسازیم.
1- توابع خالص یا محض (Pure Functions)
توابع خالص در برنامه نویسی تابعی، توابعی هستند که برای یک ورودی مشخص همیشه همان خروجی را تولید میکنند و هیچگونه تغییر یا اثر جانبی در سیستم ایجاد نمیکنند. این بدان معناست که توابع خالص تنها به ورودیهایی که دریافت میکنند وابستهاند و هیچگونه تاثیری بر دادهها یا وضعیتهای خارجی نمیگذارند.
این ویژگیها موجب میشود که توابع خالص بسیار قابل پیشبینی و تستشونده باشند، چرا که برای ورودیهای ثابت، خروجیهای ثابت خواهند داشت. یکی از مزایای بزرگ توابع خالص، قابلیت موازیسازی و کش (memoization) آنها است، زیرا هیچگونه تغییرات جانبیای ندارند که باعث بههمریختگی اجرای برنامه شود.
یک مثال از تابع خالص:
const add = (a, b) => a + b; console.log(add(2, 3)); // 5
در این مثال، تابع add برای هر ورودی مشخص، همان خروجی را همیشه باز میگرداند و هیچگونه تغییری در وضعیت خارجی یا متغیرهای دیگر ایجاد نمیکند.
یک مثال از تابع غیرخالص:
let count = 0;
const increment = () => {
count++;
return count;
};
console.log(increment()); // 1
console.log(increment()); // 2
در این مثال، تابع increment وضعیت خارجی (متغیر count) را تغییر میدهد و نتیجه تابع به ورودیها وابسته نیست، بلکه به وضعیت خارجی سیستم نیز وابسته است.
2- امپیوتر (Immutability)
امپیوتر یا دادههای غیرقابل تغییر به این معنی است که پس از ایجاد یک شیء یا متغیر، نمیتوان آن را تغییر داد. در برنامه نویسی تابعی، این اصل به جلوگیری از تغییرات غیرقابل پیشبینی در دادهها و حفظ وضعیت ثابت در طول اجرای برنامه کمک میکند. هنگامی که دادهها غیرقابل تغییر باشند، دیگر نیازی به نگرانی دربارهی وضعیتهای مختلف و تغییرات آنها در بخشهای مختلف برنامه نخواهیم داشت.
در جاوا اسکریپت میتوان از روشهایی مانند استفاده از const برای تعریف متغیرهای غیرقابل تغییر و استفاده از روشهای جدید برای ایجاد کپی از آرایهها و اشیاء (مانند Spread Operator یا Object.assign) برای حفظ امپیوتر استفاده کرد.
مثال برای آرایه:
const arr = [1, 2, 3]; const newArr = [...arr, 4]; // استفاده از Spread Operator برای ایجاد آرایهای جدید console.log(arr); // [1, 2, 3] console.log(newArr); // [1, 2, 3, 4]
در اینجا، arr تغییر نمیکند و به جای آن یک آرایه جدید ایجاد میشود که شامل دادههای جدید است.
مثال برای شیء:
const obj = {a: 1, b: 2};
const newObj = {...obj, a: 3}; // ایجاد شیء جدید با تغییرات
console.log(obj); // {a: 1, b: 2}
console.log(newObj); // {a: 3, b: 2}
3- توابع با مرتبه بالاتر (Higher-order Functions)
توابع با مرتبه بالا، توابعی هستند که یا توابع دیگری را به عنوان آرگومان میپذیرند، یا توابع جدیدی را به عنوان خروجی تولید میکنند. این ویژگی در برنامه نویسی تابعی بسیار پرکاربرد است و به انعطافپذیری کد کمک میکند.
در زیر، یک تابع با مرتبه بالاتر که تابعی به عنوان آرگومان میگیرد و آن را اجرا میکند آورده شده است:
const greet = (name, formatter) => {
console.log(formatter(name));
};
const uppercase = (str) => str.toUpperCase();
greet('ali', uppercase); // ALI
در اینجا، تابع greet یک تابع دیگر (uppercase) را به عنوان آرگومان میگیرد و آن را برای پردازش نام اجرا میکند.
توابعی مانند map(), filter(), و reduce() در جاوا اسکریپت نمونههایی از توابع با مرتبه بالا هستند که به برنامهنویس این امکان را میدهند تا عملکردهای پیچیده را با استفاده از توابع سادهتر ترکیب کنند.
مثال از map:
const numbers = [1, 2, 3]; const doubled = numbers.map(num => num * 2); console.log(doubled); // [2, 4, 6]
در این مثال، map یک تابع با مرتبه بالا است که برای هر عنصر از آرایه، تابعی را اجرا میکند و آرایه جدیدی تولید میکند.
4- توابع اولویتدار (First-Class Functions)
توابع اولویتدار به این معناست که توابع میتوانند مانند سایر اشیاء (متغیرها) در برنامه نویسی استفاده شوند. این به این معنی است که توابع میتوانند به متغیرها نسبت داده شوند، به عنوان آرگومان به توابع دیگر ارسال شوند و از توابع دیگر بازگردانده شوند.
در جاوا اسکریپت، توابع میتوانند به متغیرها اختصاص داده شوند، از توابع دیگر به عنوان ورودی دریافت شوند یا حتی به عنوان خروجی بازگردانده شوند.
مثال از اختصاص تابع به متغیر:
const add = (a, b) => a + b; const sum = add; console.log(sum(2, 3)); // 5
در اینجا، تابع add به متغیر sum اختصاص داده شده و به راحتی میتوان از آن استفاده کرد.
مثال از ارسال تابع به عنوان آرگومان:
const withLog = (fn) => {
return (...args) => {
console.log(`calling ${fn.name}`);
return fn(...args);
};
};
const add = (a, b) => a + b;
const addWithLogging = withLog(add);
addWithLogging(3, 4); // calling add, 7
در این مثال، تابع withLog یک تابع را به عنوان آرگومان میگیرد و آن را قبل از اجرا با لاگ کردن نامش میپیچد.
این ویژگیها موجب میشوند که توابع در برنامه نویسی تابعی به راحتی با یکدیگر ترکیب شده و کدهای پیچیدهتر و انعطافپذیرتری تولید شود.
روشهای معمول در برنامه نویسی تابعی
در برنامه نویسی تابعی، چندین روش و الگوی متداول وجود دارند که به بهبود ساختار کد و افزایش کارایی کمک میکنند. این روشها شامل ترکیب توابع، کوری و بازگشت هستند که در این بخش به شرح آنها خواهیم پرداخت.
1- ترکیب توابع (Function Composition)
ترکیب توابع یکی از ویژگیهای مهم در برنامه نویسی تابعی است که به ما اجازه میدهد توابع کوچکتر را برای ساخت توابع پیچیدهتر ترکیب کنیم. این کار نه تنها کد را تمیزتر و قابل فهمتر میکند، بلکه باعث میشود کد قابل نگهداریتر و قابل تست باشد.
ترکیب توابع فرآیند عبور خروجی یک تابع به عنوان ورودی به تابع دیگر است. به این ترتیب، میتوانیم چندین عملیات مختلف را در یک خط ترکیب کرده و یک کد کوتاه و تمیز بنویسیم.
مثال:
const add = x => x + 2; const multiply = x => x * 3; const addThenMultiply = x => multiply(add(x)); console.log(addThenMultiply(3)); // 15
در این مثال، ابتدا 3 به 2 اضافه میشود و سپس نتیجهی حاصل در 3 ضرب میشود. این همان ترکیب توابع است.
2- کوری (Currying)
کوری به فرایند تبدیل یک تابع با چندین آرگومان به یک سری از توابع که هر کدام تنها یک آرگومان میگیرند، اطلاق میشود. کوری مزایای زیادی دارد، از جمله امکان استفاده مجدد از توابع و بهبود عملکرد از طریق ذخیرهسازی نتایج میانه.
در برنامه نویسی تابعی، کوری به شما این امکان را میدهد که توابع را به صورت جزئی فراخوانی کرده و از آنها در زمانهای مختلف استفاده کنید. این به ویژه برای توابعی که نیاز به پردازش مجدد یک سری دادهها دارند، مفید است.
مثال:
const add = a => b => a + b; const add5 = add(5); console.log(add5(10)); // 15
در اینجا، تابع add به صورت کوری بازنویسی شده است که به ما اجازه میدهد ابتدا عدد 5 را فیکس کرده و بعدا به آن عدد دیگری را اضافه کنیم.
3- بازگشت (Recursion)
در برنامه نویسی تابعی، بازگشت به جای استفاده از حلقهها، برای انجام کارهای تکراری استفاده میشود. بازگشت زمانی به کار میرود که بخواهیم یک تابع را به طور مکرر فراخوانی کنیم تا به نتیجه نهایی برسیم. این روش برای حل مسائل پیچیدهای که نیاز به تقسیم مشکل به بخشهای کوچکتر دارند، بسیار مناسب است.
در بازگشت، تابع خود را فراخوانی میکند تا زمانی که به شرایط پایه (Base Case) برسد. این ویژگی باعث میشود که کد سادهتر و به صورت مفهومی قویتر باشد.
مثال:
const factorial = n => (n === 0 ? 1 : n * factorial(n - 1)); console.log(factorial(5)); // 120
در این مثال، تابع factorial از بازگشت برای محاسبه فاکتوریل عدد 5 استفاده کرده است. تابع خود را تا زمانی که به مقدار پایه (0) برسد، فراخوانی میکند.
متدهای تابعی موجود در جاوا اسکریپت
جاوا اسکریپت ابزارهایی قدرتمند برای برنامه نویسی تابعی فراهم میکند که با استفاده از آنها میتوانیم کدهای تمیز و مؤثر بنویسیم. برخی از این ابزارها شامل متدهای آرایهای مانند map، filter و reduce هستند که به طور گسترده در برنامه نویسی تابعی استفاده میشوند. همچنین، استفاده از کتابخانههایی مانند Lodash و Ramda میتواند توسعه برنامههای تابعی را سادهتر و سریعتر کند.
روشهای آرایهای (Array Methods)
در جاوا اسکریپت، متدهای آرایهای به ما این امکان را میدهند که به راحتی دادهها را پردازش کرده و بدون تغییر در دادههای اصلی، عملیات مختلفی روی آنها انجام دهیم. این متدها به طور خاص برای برنامه نویسی تابعی طراحی شدهاند و به ما این امکان را میدهند که دادهها را به صورت غیرمستقیم و بدون تغییر وضعیت مدیریت کنیم.
1- متد map
متد map برای ایجاد یک آرایه جدید از آرایهی اصلی با اعمال یک تابع به هر یک از عناصر آن استفاده میشود. این متد به ما این امکان را میدهد که به راحتی تمام عناصر یک آرایه را به یک مقدار جدید تبدیل کنیم.
مثال:
const numbers = [1, 2, 3, 4]; const doubled = numbers.map(num => num * 2); console.log(doubled); // [2, 4, 6, 8]
2- متد filter
متد filter برای فیلتر کردن دادهها در آرایه به کار میرود. این متد تابعی را به هر یک از عناصر آرایه اعمال میکند و فقط عناصری که مقدار بازگشتی آنها true است را در آرایه جدید قرار میدهد.
مثال:
const numbers = [1, 2, 3, 4, 5]; const evenNumbers = numbers.filter(num => num % 2 === 0); console.log(evenNumbers); // [2, 4]
در اینجا، فقط اعداد زوج از آرایه اصلی انتخاب و در آرایه جدید قرار میگیرند.
3- متد reduce
متد reduce برای کاهش آرایه به یک مقدار منفرد استفاده میشود. این متد یک تابع را روی هر عنصر آرایه اعمال میکند و نتیجه را به صورت تدریجی به مقدار نهایی میرساند.
مثال:
const numbers = [1, 2, 3, 4]; const sum = numbers.reduce((acc, num) => acc + num, 0); console.log(sum); // 10
در این مثال، reduce تمام مقادیر آرایه را جمع کرده و حاصل را برمیگرداند.
استفاده از const برای حفظ عدم تغییر دادهها
در برنامه نویسی تابعی، یکی از اصول اصلی عدم تغییر دادهها (Immutability) است. برای اطمینان از عدم تغییر دادهها، میتوانیم از کلمه کلیدی const برای اعلان متغیرها استفاده کنیم. این کار تضمین میکند که متغیرها قابل تغییر نباشند و از ایجاد وضعیتهای غیرقابل پیشبینی جلوگیری میکند.
مثال:
const numbers = [1, 2, 3, 4]; const newNumbers = [...numbers, 5]; // استفاده از spread operator برای ایجاد آرایه جدید console.log(numbers); // [1, 2, 3, 4] console.log(newNumbers); // [1, 2, 3, 4, 5]
در این مثال، آرایهی numbers تغییر نمیکند و یک آرایه جدید با اضافه کردن عدد 5 به آن ایجاد میشود.
کتابخانههای مفید برای برنامه نویسی تابعی
در دنیای برنامه نویسی تابعی، استفاده از کتابخانههای مفید میتواند به نوشتن کدهای تمیزتر و سریعتر کمک کند. دو کتابخانه محبوب برای برنامه نویسی تابعی در جاوا اسکریپت، Lodash و Ramda هستند. این کتابخانهها مجموعهای از توابع تابعی را فراهم میکنند که میتوانند در پروژههای جاوا اسکریپت به راحتی استفاده شوند.
1- کتابخانه Lodash
Lodash یکی از کتابخانههای پرکاربرد در جاوا اسکریپت است که امکانات بسیاری برای کار با دادهها و توابع تابعی فراهم میکند. از ویژگیهای آن میتوان به توابعی برای کار با آرایهها، اشیاء و رشتهها اشاره کرد.
مثال:
const _ = require('lodash');
const numbers = [1, 2, 3, 4, 5];
const sum = _.reduce(numbers, (acc, num) => acc + num, 0);
console.log(sum); // 15
در اینجا از reduce کتابخانه Lodash برای محاسبه مجموع استفاده شده است.
2- کتابخانه Ramda
Ramda یک کتابخانه تابعی برای جاوا اسکریپت است که به طور خاص برای برنامهنویسانی که به دنبال یک تجربه برنامه نویسی تابعی ناب هستند طراحی شده است. این کتابخانه ویژگیهایی مانند کوری، ترکیب توابع و توابع با مرتبه بالا را به راحتی در اختیار قرار میدهد.
مثال:
const R = require('ramda');
const add = (a, b) => a + b;
const add5 = R.partialRight(add, [5]);
console.log(add5(10)); // 15
در اینجا از تابع partialRight در Ramda برای ایجاد تابعی جدید که مقدار 5 را به طور خودکار به تابع add اضافه میکند استفاده شده است.
استفاده از این کتابخانهها میتواند روند نوشتن کدهای تابعی را تسهیل کرده و زمان توسعه را کاهش دهد.
تفاوت برنامه نویسی شیگرا و برنامه نویسی تابعی
برنامه نویسی شیگرا (OOP) و برنامه نویسی تابعی (FP) دو رویکرد متفاوت در طراحی نرمافزار هستند که هرکدام ویژگیها و شیوههای خاص خود را دارند.
در برنامه نویسی شیگرا، تمرکز بر روی اشیاء است که شامل دادهها (ویژگیها) و رفتارها (متدها) میشوند. این دادهها میتوانند تغییر کنند و وضعیت شیء از طریق متدها بهروزرسانی شود. در این رویکرد، مدولاریتـی از طریق کلاسها و اشیاء بهدست میآید و بازاستفادهپذیری کد از طریق وراثت و چندریختی انجام میشود. دادهها و رفتارها درون اشیاء ترکیب میشوند و تغییرات وضعیت از طریق متدها صورت میگیرد.
در برنامه نویسی تابعی، تمرکز بیشتر بر روی توابع خالص است که دادهها را تغییر نمیدهند و از توابع با مرتبه بالاتر برای ساخت مدولهای کد استفاده میشود. دادهها بهصورت غیرقابل تغییر (ایمونتابل) مدیریت میشوند و وضعیت در توابع با ورودیها و خروجیهای مشخص تغییر میکند. بازاستفادهپذیری کد در این رویکرد از طریق ارسال توابع بهعنوان آرگومان یا بازگشت آنها بهعنوان خروجی از توابع دیگر صورت میگیرد. در این روش، کد بیشتر بر اساس ترکیب توابع ساده به یکدیگر ساخته میشود.
در نهایت، هرکدام از این دو رویکرد مزایای خاص خود را دارند و بسته به نیاز پروژه، ممکن است یکی از آنها بهتر عمل کند.
نتیجه گیری
در نهایت، برنامه نویسی تابعی بهعنوان یک پارادایم قدرتمند و مؤثر برای توسعه نرمافزار، مزایای زیادی از جمله کد تمیزتر، قابلیت تستپذیری بالا، و پشتیبانی از پردازش موازی را ارائه میدهد. با این حال، این روش نیازمند یادگیری و آشنایی با مفاهیم خاصی چون توابع خالص، دادههای غیرقابل تغییر، و تکنیکهایی مانند کوری و ترکیب توابع است که ممکن است برای برخی توسعهدهندگان چالشبرانگیز باشد. در انتخاب برنامه نویسی تابعی برای پروژهها، باید نیازهای خاص پروژه و مزایا و معایب این روش را به دقت بررسی کرد تا بهترین نتیجه حاصل شود.














