برنامه نویسی

معرفی و مروری بر مباحث برنامه نویسی فانکشنال

معرفی و مروری بر مباحث Functional Programming

اگر در دنیای توسعه نرم افزار و برنامه نویسی باشید, احتمالا در کنار برنامه نویسی شئ گرا, اسم برنامه نویسی Functional به گوشتون خورده. اینکه برنامه نویسی فانکشنال چی هست؟ چه فلسفه ای داره؟ چه مباحثی داره؟ و در آخر مزایا و معایب اون چیه؟ صحبت خواهیم کرد.

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

برنامه نویسی فانکشنال یک الگو یا paradigm برنامه نویسی است که از declarative programming نشات میگیرد.

مدل برنامه نویسی اساسا به دو روش می باشد:

  • imperative programming : که تمرکز بر این دارد که یک برنامه تا خود زیر لایه های واحد های کد مانند توابع چگونه کاری را انجام دهند و مهمتر از آن برنامه نویسی که از آن استفاده میکند نیاز است که بداند آن واحد از کد “چگونه” کاری که قرار است انجام دهد را انجام میدهد.
  • declarative programming : تفاوت عمده این مدل با مدل imperative در این است که تمرکز بر روی یک واحد بصورت “چه کاری انجام میدهد” است تا “چه کاری را چگونه انجام میدهد”.

از این دو مدل برنامه نویسی دو الگو یا paradigm نشات گرفته شده اند که بسیار متداول هستند:

  • از imperative programming برنامه نویسی شئ گرا Object-Oriented Programming
  • از declarative programming برنامه نویسی فانکشنال Functional Programming

برنامه نویسی فانکشنال اساسا از مدل ها و مفاهیم ریاضیاتی بهره برده است, در حالی که برنامه نویسی شئ گرا از ساختار کلی جهان و اشیا بهره برده است.

اگر قبلا برنامه نویسی غیر فانکشنال مانند شئ گرا یا مانند زبان های برنامه نویسی C بلدید, بهتر است موقع یادگیری یک زبان برنامه نویسی فانکشنال مفاهیم زبانی که بلدید را کنار بگذارید. چرا که بسیار متفاوت هستند و شبیه دانستن آنها درک و یادگیری برنامه نویسی فانکشنال را برای شما سخت خواهد کرد.

تفاوتهای برنامه نویسی فانکشنال و شئ گرا

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

  • برنامه نویسی شئ گرا برای برنامه هایی که نیازمند تعریف ساختارهای داده ای پیچیده به تعداد زیاد هستند بهتر و راحتتر هستند, در حالی که برنامه نویسی فانکشنال برای انجام عملیات بر روی نوع داده های ساده و ساختار های داده ای ساده مانند لیست ها, گراف ها, اعداد و یا حتی تنها المنت های html راحتتر و اکثز اوقات بهینه تر هستند.
  • برنامه نویسی فانکشنال برای اشتراک گذاری اساسی داده ها خیلی مناسب نیستند. منظور اشتراک گذاری داده ها بین توابع مختلف است بخصوص زمانی که توابع باید با یک دیگر در تعامل باشند و عملیات های sync صورت بگیرد.

مفاهیم و قوانین در برنامه نویسی فانکشنال

تابع خالص یا pure function

یک تابع خالص تابعی است که مانند توابع ریاضی با آرگومان های ثابت, و نه متغییر, همیشه خروجی ثابتی نیز دارد. توابع خالص به این منظور هیچ “تاثیر جانبی” ای ندارد که در مورد تاثیرات جانبی در ادامه صحبت خواهیم کرد.

نمونه کد یک تابع خالص:

void func(unsigned int n, unsigned int p) {
if (p == 0) return 1;

return n * func(n, p - 1);
}

نمونه کد ناخالص:

int global_value = 8;

void func(int n) {
return n * global_value;
}

تاثیرات جانبی

تاثیرات جانبی شامل تمام متغییرها, و نه ثابت ها!, می باشند که در خارج از scope یا محدوده داخلی یک واحد از کد باشند. در یک تابع متغییرهای داخلی یعنی متغییرهایی که در خود تابع تعریف میشوند و آرگومان های آن تابع.

اشاره گرها, متغییر های عمومی یا همان global و ورودی و خروجی که شامل سوکت شبکه, دیتابیس, رابط کاربری و غیره میشوند از نمونه های تاثیر گذاران جانبی هستند.

یک تابع خالص علاوه بر اینکه نباید تاثیر جانبی بپذیرد یعنی از خارج از محدوده متغییر های خود متغییری بخواند, بلکه همینطور نباید تاثیر جانبی بگذارد, یعنی نباید به خارج از خود بطور مستقیم بنویسد و باید تنها از طریق باز گردانی مقدار به خارج از خودش مقداری را ارائه دهد.

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

توابع Higher-Order Functions

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

بعنوان مثال اگر بخواهیم مقادیر درون یک لیست را یکبار جذر بگیریم و یکبار به توان برسانیم, در حالت عادی نیاز است که دو تابع که هردو از همه لحاظ شبیه یکدیگر هستند اما تنها در یک قسمت بخصوص فرق دارند یعنی عملیاتی که بر روی هر مقدار انجام می دهیم, بنویسیم که این امر خسته کننده و بعضی اوقات تعداد کارها بسیار زیاد هستند که دوباره نویسی یک تابع خیلی آزاردهنده میشود و اصل DRY یا Don’t Repeat Yourself را نقض میکند.

در این حالت ها میتوانیم از Higher-Order Functions استفاده نماییم. در این توابع شما می توانید یک تابع دیگر را بعنوان آرگومان بدهید و تابعی که آن آرگومان را دریافت کرده در جایی از عملیات های خود از آن استفاده کند.

در اکثر زباهای شئ گرا نیز میبینیم که این عمل با استفاده از lambda ها بجای آدرسدهی یک تابع نامدار قابل انجام است.

ساده ترین مثال برای Higher-Order Functions همان انجام یک عملیات بر روی تمام المان های یک تابع و بازگردانی یک لیست از المان های جدید است. در اکثر زبان ها تابعی پیشفرض توسط کتابخانه استاندارد آن زبان با نام map ارائه میشود. در خیلی زبانهای دیگر از نامهای دیگری نیز استفاده میشود.

نمونه کد به زبان جاوا اسکریپت:

function arr_map(func, array) {
if (array.length <= 0)
return [];

var new_element = func(array.pop());
return [new_element].concat(arr_map(func, array));
}

var array = [1, 2, 3, 4, 5];
var new_array = arr_map(
function(n) { return n * n; },
array
);

توجه داشته باشید که توابع Higher-Order می توانند خالص باشند و ناخالص شدن آنها بستگی به نحوه استفاده شما از آنها دارد. اگر تابعی که بعنوان آرگومان به آنها میدهید ناخالص باشد, این باعث میشود که آن قسمت که شما تابع را صدا کرده اید ناخالص شود. چرا که تابع زمانی خالص است که تمام توابعی که صدا میکند نیز خالص باشند. بطور مثال اگر ما تابعی را به map بدهیم که از یک متغییر عمومی استفاده میکند, آنوقت خروجی ما ناخالص است.

در نمونه کد erlang تابع map به ذات خالص و تابع print_dict_style ناخالص است, چرا که تابع map نه تاثیر جانبی میپذیرد و نه تاثیر جانبی میگذارد در حالی که تابع print_dict_style بر روی خروجی استاندارد مینویسد یعنی تاثیر جانبی میگذارد.

Referential Transparency

وقتی در برنامه یک عبارت, یا همان دستور در برنامه نویسی imperative, همانند توابع خالص باشد, می توان آن را با جواب مشخص آن جایگزین کرد بدون آنکه در جای دیگری از برنامه نیاز به تغییری باشد, آن عبارت دارای خاصیت Referential Transparency می باشد. مانند:

function successor(n)
{
return n + 1;
}

if (successor(x) == successor(y)) // referential transparency here
{
// do something
}

قسمتی که در کامنت نوشته ایم را میتوانیم به شکل زیر نیز بنویسیم:

// successor(x) == successor(y) equals to (x + 1) == (y + 1)
// as in mathematical equations, we can remove +1 parts:
// x == y
// so we can write that expression as follow:
if (x == y) {
// do something
}

این کار میتواند تاثیر بسزایی در efficiency و performance برنامه داشته باشد.

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

  • یک تابع خالص میتواند از چند آرگومان مختلف خروجی یکسان داشته باشد, بلکه تنها نمیتواند از یک ست آرگومان ثابت چند خروجی داشته باشد. بطور خلاصه جواب یک تابع خالص با آرگومان های ثابت را باید بتوان با خروجی آن عوض کرد.
  • تمام توابعی که یک تابع خالص صدا میزند نیز باید خالص باشند, در غیر این صورت آن تابع نیز خود ناخالص است.
  • سعی کنید توابع ناخالص کمتری داشته باشید و از توابع خالص در آنها استفاده کنید.
  • از Higher-Order Functions برای خالص سازی نسبی استفاده کنید و تا جای ممکن کد ها را در توابع خالص قرار دهید.
  • اگر از دیگر زبانها که اصولا فانکشنال نیستند مانند C یا JavaScript استفاده میکنید, سعی کنید با استفاده از تکنیک های برنامه نویسی فانکشنال کارایی برنامه خود را بالاتر ببرید.
  • توابع خالص برای تست واحدی بسیار راحتتر هستند و هر چقدر توابع ناخالص کمتری داشته باشید, تست نویسی برای شما بسیار راحتتر خواهد شد.
  • در برنامه نویسی شئ گرا میتوانید از Dependency Injection که شبیه Higher-Order ها هستند استفاده کنید که هم تست پذیری راحتتر و هم انعطاف پذیری بالایی را برای شما فراهم میکند.

امیدوارم از این پست بهره برده باشید. با تشکر.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *


دکمه بازگشت به بالا