برنامه‌نویسی تابعی در جاوااسکریپت

Functional Programming
Functional Programming

این روزها به وفور برنامه‌نویس‌های جاوااسکریپت از برنامه‌نویسی تابعی یا Functional Programming استفاده می‌کنند، حتی شاید شما هم جزو اون دسته از برنامه‌نویس‌ها باشید اما آیا تا به حال شده از خودتون بپرسید که چی باعث می‌شه که از برنامه‌نویسی فانکشنال استفاده کنم؟ تاحالا براتون سوال پیش اومده اصلا مزایای این نوع برنامه‌نویسی چیه؟ چه قابلیت‌هایی داره که شاید هنوز ازش خبر ندارم؟ آیا اصلا به درد من میخوره؟

اگه هنوز جواب سوالات بالا رو پیدا نکردی، من تو این مقاله تا حد امکان سعی کردم پاسخ این سوالات رو بدم.

برنامه‌نویسی تابعی یا Functional Programming چیست؟

قبل از این که برنامه‌نویسی تابعی به این صورت مورد توجه قرار بگیره، برنامه‌نویس‌ها از برنامه‌نویسی شی‌گرا یا Object Oriented Programming استفاده می‌کردند. اون‌ها برای حساب کتاب‌های ساده و یکسری کامپوننت‌های معمولی از توابع استفاده می‌کردند اما رفته رفته با گذر زمان برنامه‌نویسی تابعی جای تازه‌ای رو در برنامه‌هایی که توسعه داده می‌شد پیدا کردند. در واقع در جاوااسکریپت از زمانی که فریم‌ورک‌ها و لایبرری‌ها معرفی شدند، برنامه‌نویسی تابعی بیشتر مورد توجه قرار گرفت و توسط توسعه‌دهنده‌ها بیشتر از قبل استفاده می‌شد. برنامه‌نویسی تابعی یه‌جورایی باید ممنون این فریم‌ورک‌ها و لایبرری‌ها مثل انگیولار، ویو و از همه بیشتر ری‌اکت باشه که با قابلیت‌های جدیدش بقیه رو تشویق به استفاده از برنامه‌نویسی فانکشنال میکنه.

اما خود برنامه‌نویسی فانکشنال چیه؟

برنامه‌نویسی تابعی در واقع یک نوع الگوی برنامه‌نویسی هست که در اون شما بیشتر پروژه‌اتون رو با استفاده از توابع برنامه‌نویسی توسعه میدید؛ این توابع یکسری آرگومان ورودی دریافت میکنن و خروجی‌هایی رو بر اساس این ورودی‌ها برمی‌گردونند.

توجه کنید که برنامه‌نویسی تابعی یک الگو از چندین الگویی است که جاوااسکریپت دارد؛ این که شما از توابع در ساختار کد خود استفاده کنید به این معنی نیست که شما از برنامه‌نویسی فانکشنال استفاده کرده‌اید.

قابلیت‌های فانکشنال پروگرمینگ چیست؟

First-Class Function

از خوبی‌های برنامه‌نویسی تابعی یکی همین First-Class Function بودن اون هست، بدین معنی که شما همیشه می‌تونید بدون هیچ محدودیتی از توابع دیگه داخل توابع دیگه استفاده کنید؛ می‌تونید یک تابع رو به عنوان آرگومان ورودی به تابع دیگه‌ای بدید، از یک تابع اون رو برگردونید و حتی اون رو به یک متغییر نسبت بدید!

این قابلیت به معرفی Higher-Order Functions (HoFs) کمک میکنه.

در واقع HoFsها توابعی هستند که یک تابع دیگه رو به عنوان ورودی دریافت میکنند؛ البته میتونن که در کنار این تابع، آرگومان‌های دیگه‌ای رو هم دریافت کنند. در آخر خروجی به عنوان یک تابع دیگه return میشه.

به این مثال توجه کنید:

const add = (x, y) => x + y

const log = fn => (...args) => {
    return fn(...args)
}

const logAdd = log(add)

در خط دوم تابع log به عنوان ورودی تابعی رو دریافت میکنه که در خط آخر می‌بینید اون تابع add هستش و برای جمع دو عدد تعریف شده و به متغیر logAdd نسبت داده شده. در اینجا ابتدا تابع log به عنوان وارث اجرا میشه و با ریترن کردن فرزند خودش تابع add رو اجرا میکنه.

برای کسانی که برنامه‌نویسی React انجام میدن، این نوع توابع از اهمیت بالایی برخوردار هستند؛ درک این نوع توابع به درک Higher-Order Components در ری‌اکت بسیار کمک خواهد کرد.



Purity (به معنی خالص)

بحث مهم دیگه‌ای که در برنامه‌نویسی تابعی مطرح هست، توابع pure هستند.

اما اول بیاید بررسی کنیم که به چه توابعی توابع pure گفته میشه؟

به تابعی pure گفته میشود که آن تابع هیچ تاثیری بر روی عوامل خارجی که داخل خود تابع تعریف نشده‌اند نگذراد.

برای مثال تابعی که در زیر تعریف می‌کنیم یک تابع pure است:

const add = (x, y) => x + y

این تابع میتونه چندین بار اجرا بشه و در هر بار اجرا خروجی که به ما خواهد داد، جمع دو عددی هست که ما به اون تابع می‌دیم؛ دلیلش هم اینه که ما هیچ متغییری رو خارج از تابع تعریف نکردیم که بر روی تابع تاثیر بگذاره.

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

let x = 0
 const add = y => (x = x + y)

بیاید برنامه‌رو امتحان کنیم، به صورت زیر تابع رو فراخوانی کنید (کنسول مرورگر رو باز کنید و همینجا امتحان کنید)، خواهید دید که خروجی اول همون خروجی نیست که تو فراخوانی دوم می‌گیرید:

add(1) // returned 1
add(1) // returned 2

ما توابع یکسان با ورودی یکسان رو فراخوانی کردیم اما خروجی یکسان نیست. دلیل هم متغیر x=0 هست که ما به صورت global تعریف کردیم.

Immutability

ما فهمیدیم که چطور توابعی تعریف کنیم که pure باشند اما اگر بخوایم روی متغیر‌هایی که تعریف شده از قبل به صورت global تغییر ایجاد کنیم باید چه کاری انجام بدیم؟
در برنامه‌نویسی تابعی به جای تغییر دادن یک متغیر به صورت لوکال در دل تابع، یک متغیر جدید ساخته میشه و متغیر اصلی (درصورتی که در اصل متغیر تغیری ایجاد نشه) با تغییر در تابع ریترن میشه. این روش کار با داده‌ها، immutability نام دارد.

به مثال توجه کنید:

const add3 = arr => arr.push(3)
const myArr = [1,2]
add3(myArr); // [1,2,3]
add3(myArr); // [1,2,3,3]

در این مثال مفهموم Immutability رعایت نشده؛ چرا که در خروجی مشخص هست که مقدار متغیر global ما تغییر کرده و در فراخوانی دوم، خروجی مقداری نابرابر با خروجی در فراخوانی اول هست با این که همان تابع دوبار فراخوانی شده.

با تغییر دادن push در کد بالا و جایگزین کردن با concat، متغیر اصلی تغییری نمیکنه و خروجی ما در هر فراخوانی متغیر جدید هست:

const add3 = arr => arr.concat(3)
const myArr = [1, 2]
const result1 = add3(myArr) // [1, 2, 3] 
const result2 = add3(myArr) // [1, 2, 3]

حالا با هربار فراخوانی تابع، میشه متوجه شد که مقدار متغیر myArr، تغییری نمیکنه.

Currying

یک تکنیک متداول و پرکاربرد دیگه در برنامه‌نویسی تابعی، currying است.

درواقع Currying تبدیل کردن یک تابع با چند متغیر ورودی به تابعی با یک متغیر ورودی و return کردن تابعی دیگر است.

به صورت ریاضی تابع f(x,y)=x+y رو در نظر بگیرید.

برای currying کردن این تابع به صورت زیر عمل می‌کنیم:

f(g(x)) = x + g(x)
g(x) = y

حالا اگه x=3 و y=2 باشه، جواب f(x,y) با f(g(x)) برابر هستش اما فرق اینجاست که ما در تابع اول دو پارامتر x,y رو به تابع دادیم اما در تابع دوم فقط پارامتر x رو دادیم و به ما تابع g(3) رو برگردوند که g(3)=2 بود.
حالا با با جایگذاری می‌بینیم که f(g(3) = x + g(3) میشه f(2)=3+2=5.
همون تابع f(x,y) فقط اینبار با یک متغیر.

برگردیم به برنامه‌نویسی؛ با تابع add که بالاتر تعریف کردیم کار می‌کنیم.

تابع add دارای دو متغیر ورودی بود:

const add = (x, y) => x + y

میتونیم تابع add رو به صورت زیر تعریف کنیم:

const add = x => y => x + y

حالا از روش بالا میتونیم به صورت زیر استفاده کنیم و می‌بینید که چقدر کاربردی و بدرد بخوره این نوع تعریف تابع:

const add1 = add(1) 
add1(2); // 3 
add1(3); // 4

Composition

آخرین مبحث ما یکی از مهم‌ترین قسمت‌های برنامه‌نویسی تابعی هستش.
این مبحث به ما میگه که توابع میتونن باهم دیگه ترکیب بشن و تابعی جدید با قابلیت‌های جدید رو بسازن.

به مثال توجه کنید:

const add = (x, y) => x + y 
const square = x => x * x

ما در اینجا دو تابع مختلف رو داریم که میتونن باهم ترکیب بشن و یک تابع با قابلیت جمع دو عدد و سپس به توان ۲ رسوندن عدد حاصل از تابع جمع رو داشته باشیم:

const addAndSquare = (x, y) => square(add(x, y))

این کاربرد ساده‌ای از الگوی Composition بود که قطعا در پروژه‌های بزرگ با قابلیت‌های جالب‌تر اون آشنا خواهید شد.



مزایای برنامه‌نویسی تابعی یا Functional Programming

سرراست، مختصر و مفید

یکی از دلایل این که برنامه‌نویسی فانکشنال مورد توجه قرار گرفته، سرراست بودن در نوشتار و خواندن کدها و همچنین دیباگ کردن اون‌هاست. در حقیقت کار کردن با برنامه‌نویسی تابعی در نگهداری کد و ساختار بسیار به ما کمک میکنه.

تعاریف Pure و Immutable بودن در برنامه‌نویسی تابعی

از مهم‌ترین چیزهایی که در برنامه‌نویسی تابعی مورد توجه قرار میگیره Immutable و Pure بودن هست. این قابلیت به دیباگ کردن کد‌ها به قدر قابل توجهی کمک میکنه. فرض کنید در پروژه‌ای قرار به تغییر یک متغیر باشد، اگر توابع شما Pure نباشن باز هم تغییر دادن این متغیر کار ساده‌ایه؟ عملا غیر قابل تغییر میشه.
همین موضوع باعث میشه که تو از دیباگ کردن هم به مشکلات زیادی بر بخوریم و عملا نگهداری کد بسیار سخت میشه.



نتیجه‌گیری

تقریبا برنامه‌نویسی تابعی داره تبدیل به یک استاندارد برای کدهای جاوااسکریپت و نگهداری از اون‌ها میشه.

تقریبا بیشتر لایبرری‌ها و فریم‌ورک‌ها به سمت برنامه‌نویسی فانکشنال در حرکت هستن و بسیاری‌شون مثل ری‌اکت قدم‌ها بزرگی مثل React Hooks برداشتن و توسعه دهنده‌های این اکوسیستم رو تشویق به استفاده از برنامه‌نویسی فانکشنال میکنند.



از این که تا انتهای این مقاله رو مطالعه کردید ممنونم.

این اولین مقاله‌ی من تو حوزه‌ی کاری خودم بود، سعی کردم از دانش و کتاب‌هایی که داشتم مطالبی رو جمع‌آوری کنم و اینجا یادداشت کنم؛ اگر اشکالی داشت به بزرگی خودتون ببخشید و ممنون میشم همینجا تو کامنت‌ها بهم بگید تا اصلاح کنم.