آنچه در این صفحه می خوانید:
- معرفی برنامه نویسی تابعی (Functional Programming)
- مفاهیم برنامه نویسی تابعی (Functional Programming)
- مزایای برنامه نویسی تابعی (Functional Programming)
- ویژگی های برنامه نویسی تابعی (Functional Programming)
معرفی برنامه نویسی تابعی (Functional Programming)
در علوم رایانه، برنامه نویسی تابعی یک الگوی برنامه نویسی است - سبکی برای ساختن ساختار و عناصر برنامه های رایانه ای - که محاسبات را به عنوان ارزیابی تابع های ریاضی انجام می دهد و از تغییر حالت های داده و متغیر جلوگیری می کند. در واقع یک الگوی برنامه نویسی اعلانی است که برنامه نویسی به جای statements با declarations یا expressions انجام می شود. در کد تابعی، مقدار خروجی تابع فقط به آرگومان های آن بستگی دارد، بنابراین فراخوانی تابع با همان مقدار برای آرگومان همیشه نتیجه مشابه را تولید می کند. این برخلاف برنامه نویسی دستوری است که علاوه بر استدلال های تابع، یک حالت برنامه گلوبال می تواند بر ارزش نتیجه تابع تأثیر بگذارد. یکی از انگیزه های اصلی برای توسعه برنامه نویسی تابعی، آسان تر کردن برنامه با حذف تغییرات در حالت وابسته به ورودی های تابع نیست که به آنها عوارض جانبی گفته می شود.
برنامه نویسی تابعی ریشه در محاسبات لامبدا دارد، سیستم رسمی که در دهه 1930 برای بررسی محاسبات، Entscheidungsproblem، تعریف تابع، کاربرد تابع و بازگشت به کار گرفته شد. بسیاری از زبان های برنامه نویسی تابعی را می توان به عنوان شرح در حساب lambda مشاهده کرد. یکی دیگر از الگوی شناخته شده برنامه نویسی، برنامه نویسی منطق، مبتنی بر روابط است.
در مقابل، برنامه نویسی دستوری state را با عبارات موجود در سورس کد تغییر می دهد که ساده ترین نمونه آن assignment است. برنامه نویسی Imperative دارای زیر برنامه ها است، اما اینها تابع های ریاضی نیستند. آنها می توانند عوارض جانبی داشته باشند که ممکن است حالت برنامه را تغییر داده و امکاناتی را بدون مقادیر بازگشتی فراهم کند. به همین دلیل، آنها فاقد شفافیت مرجع هستند، یعنی یک زبان می تواند بسته به وضعیت برنامه در حال اجرا، در زمان های مختلف منجر به مقادیر مختلف شود.
زبان های برنامه نویسی تابعی بیشتر در دانشگاه ها به جای پیاده سازی صنعت مورد تاکید قرار گرفته است. با این حال، زبان های برنامه نویسی که از برنامه نویسی تابعی در صنعت پشتیبانی می کنند از جمله لیسپ (Lisp)، Scheme، کلوژر (Clojure)، Wolfram Language، Racket، ارلنگ (Erlang)، OCaml، هسکل (Haskell) و اف شارپ (#F) می باشند. جاوااسکریپت، یکی از پراکنده ترین زبان های جهان، علاوه بر پارادایم های ضروری و شی گرا، دارای ویژگی های زبان تابعی نیز است. برنامه نویسی تابعی همچنین برای برخی از زبان هایی که موفقیت در حوزه های خاص مانند آر (R) در آمار، J، K و Q در تجزیه و تحلیل مالی و XQuery / XSLT برای اکس ام ال (XML) دارند موفقیت مهم است. زبان های اعلامی خاص دامنه مانند اس کیوال (SQL) و Lex / Yacc از برخی عناصر برنامه نویسی تابعی به خصوص در عدم پشتیبانی از مقادیر قابل تغییر استفاده می کنند.
برنامه نویسی به سبک تابعی می تواند به زبان هایی انجام شود که در طور خاص برای برنامه نویسی های تابعی طراحی نشده باشند، مانند پرل، پی اچ پی، سی پلاس پلاس و کاتلین. یک مورد ترکیبی از مورد اسکالا است، غالباً به سبک تابعی نوشته می شود، اما وجود عوارض جانبی و حالت جهش یافته آن را در منطقه خاکستری بین زبان های دستوری و تابعی قرار می دهد.
برنامه نویسی تابعی یک پارادایم است که بر نتایج محاسبات و نه بر انجام action تمرکز می کند. در واقع هنگامی که شما یک تابع را فراخوانی می کنید، تنها اثر قابل توجهی که تابع دارد، معمولا برای محاسبه مقدار و بازگشت آن است. البته، در پشت صحنه تابع از زمان CPU استفاده می کند و حافظه را می نویسد. اما از نظر برنامه نویسان، اثر اصلی مقدار بازگشتی است. آبجکت ها در یک زبان برنامه نویسی تابعی اغلب تغییرناپذیر هستند. به جای تغییر آبجکت شما یک آبجکت جدید را اختصاص می دهید. برنامه ریزی تابعی مستلزم آن است که توابع first-class باشند، به این معنی که آنها مانند هر مقدار دیگری رفتار می شوند و می توانند به عنوان argument به توابع دیگر منتقل شوند یا به عنوان یک نتیجه از یک تابع بازگردانده شوند. first-class نیز بدین معنی است که امکان تعریف و دستکاری توابع از داخل توابع دیگر وجود دارد. توجه ویژه باید به توابع که متغیرهای محلی را از محدوده خود ارجاع می دهند، داده شود.
مفاهیم برنامه نویسی تابعی (Functional Programming)
تعدادی از مفاهیم و پارادایم ها مخصوص برنامه نویسی تابعی و عموماً برای برنامه نویسی دستوری (از جمله برنامه نویسی شی گرا) بیگانه هستند. با این حال، زبان های برنامه نویسی اغلب از چندین الگوی برنامه نویسی پذیرایی می کنند، بنابراین برنامه نویسان با استفاده از زبان های "اکثر دستوری" ممکن است از برخی از این مفاهیم استفاده کرده اند.
توابع درجه یک و مرتبه بالاتر
توابع مرتبه بالاتر توابعی هستند که می توانند توابع دیگری را به عنوان آرگومان به کار گیرند یا آنها را به عنوان نتیجه بازگردانند. در حساب، مثالی از تابع مرتبه بالاتر، عملگر دیفرانسیل d / d x display \ displaystyle d / dx} d / dx است که مشتق تابع f {\ displaystyle f} f را برمی گرداند.
توابع مرتبه بالاتر ارتباط نزدیکی با توابع first-class دارند، زیرا توابع مرتبه بالاتر و توابع درجه یک هر دو توابع را به عنوان آرگومان و نتایج سایر توابع اجازه می دهند. تمایز بین این دو ظریف است: "مرتبه بالاتر" مفهوم ریاضی از توابع را که بر روی توابع دیگر کار می کنند، توصیف می کند، در حالی که "first-class" اصطلاح علم کامپیوتر است که اشخاص زبان برنامه نویسی را توصیف می کند که هیچ گونه محدودیتی در استفاده از آنها ندارند (بدین ترتیب توابع first-class می توانند در هر جای برنامه ظاهر شوند که سایر موجودات first-class مانند اعداد بتوانند، از جمله به عنوان استدلال برای سایر توابع و به عنوان مقادیر بازگشت آنها).
توابع مرتبه بالاتر کاربرد جزئی یا پردازش را امکان پذیر می کند، تکنیکی است که یک بار عملکردها را به طور همزمان به آرگومان های خود اعمال می کند، و هر برنامه یک تابع جدید را می پذیرد که آرگومان بعدی را می پذیرد. این اجازه می دهد تا برنامه نویس به طور خلاصه بیان کند، به عنوان مثال، تابع جانشین بعنوان اپراتور اضافی که جزئی از آن در قسمت طبیعی شماره یک اعمال می شود.
توابع خالص
تابع های خالص (یا اصطلاحات) هیچ گونه عوارض جانبی (حافظه یا I / O) ندارند. این بدان معنی است که توابع خالص دارای چندین خاصیت مفید هستند که بسیاری از آنها می توانند برای بهینه سازی کد استفاده شوند:
- اگر نتیجه یک عبارت خالص استفاده نشود، می تواند بدون تأثیرگذاری بر عبارات دیگر حذف شود.
- اگر یک تابع خالص با آرگومان هایی خوانده شود که عوارض جانبی ایجاد نمی کنند، نتیجه آن با توجه به آن لیست استدلال (که بعضا شفافیت مرجع نامیده می شود) ثابت است، یعنی با فراخوانی دوباره تابع خالص با همان آرگومان ها نتیجه مشابه را برمی گرداند. (این می تواند بهینه سازی حافظه پنهانی مانند یادداشت را فعال کند.)
- اگر هیچ وابستگی به داده ها بین دو عبارت خالص وجود نداشته باشد، ترتیب آنها قابل برگشت است، یا می توان آنها را به صورت موازی انجام داد و آنها نمی توانند با یکدیگر تداخل داشته باشند (به عبارتی دیگر، ارزیابی هرگونه بیان خالص بی خطر است).
- اگر کل زبان عوارض جانبی نداشته باشد، می توان از هر استراتژی ارزیابی استفاده کرد. این به کامپایلر این امکان را می دهد که مجدداً مرتب سازی کند یا ترکیبی از ارزیابی عبارات در برنامه باشد (برای مثال، استفاده از جنگل زدایی).
در حالی که اکثر کامپایلرها برای زبان های برنامه نویسی ضروری تابع های خالص را تشخیص می دهند و حذف های متداول را برای فراخوانی های با تابع خالص انجام می دهند، همیشه نمی توانند این کار را برای کتابخانه های از پیش تهیه شده انجام دهند، که معمولاً این اطلاعات را در معرض نمایش قرار نمی دهند، بنابراین از بهینه سازی هایی که شامل آن تابع های خارجی است، جلوگیری می کنند. برخی از کامپایلرها، مانند GCC، کلید واژه اضافی را برای برنامه نویس اضافه می کند تا تابع های خارجی را به صورت خالص علامت گذاری کند، تا چنین بهینه سازی هایی را فعال کند. Fortran 95 همچنین اجازه می دهد تا توابع خالص تعیین شوند. C++ 11 کلمه کلیدی constexpr را با معانی مشابه اضافه کرد.
بازگشت
تکرار (حلقه زدن) در زبان های تابعی معمولاً از طریق بازگشت صورت می گیرد. تابع های بازگشتی خود را فراخوانی می کنند، اجازه می دهند تا یک عمل تکرار شود تا این که به مورد اصلی برسد. اگرچه برخی از بازگشت ها به حفظ پشته احتیاج دارند، بازگشت یك دم توسط یك کامپایلر به همان كدی كه برای اجرای تکرار در زبان های ضروری استفاده می شود قابل شناسایی و بهینه سازی است. استاندارد زبان Scheme برای شناسایی و بهینه سازی بازگشت دم به پیاده سازی نیاز دارد. بهینه سازی بازگشت دم می تواند با تبدیل برنامه به یک شیوه ادامه گذر در هنگام تدوین، از جمله رویکردهای دیگر اجرا شود.
الگوهای معمول بازگشت می تواند با استفاده از توابع مرتبه بالاتر انتزاع شود، با فسادها و دگرگونی ها (یا "برابر" و "آشکار شدن") بارزترین نمونه ها است. چنین طرح های بازگشتی نقشی مشابه ساختارهای کنترل داخلی مانند حلقه ها به زبان های ضروری دارند.
اکثر زبان های برنامه نویسی تابعی با هدف کلی امکان بازگشت مجدد بدون محدودیت را دارند و Turing کامل است، که مسئله توقف را غیر قابل اثبات می کند، می تواند باعث عدم صداقت استدلال معادلات شود و به طور کلی نیاز به وارد کردن ناسازگاری در منطق بیان شده توسط سیستم نوع زبان دارد. برخی از زبان های خاص مانند Coq فقط بازگشتی مبتنی بر مجوز را دارند و به شدت عادی می شوند (محاسبات غیرمستقیم فقط با جریان های نامتناهی از مقادیر موسوم به codata بیان می شود). در نتیجه، این زبان ها به صورت Turing کامل نیستند و بیان تابع های خاص در آنها غیرممکن است، اما آنها می توانند ضمن جلوگیری از مشکلات ایجاد شده توسط بازگشت نامحدود، کلاس گسترده ای از محاسبات جالب را بیان کنند. برنامه نویسی تابعی محدود به بازگشت مبتنی بر چند محدودیت دیگر، برنامه نویسی تابعی کل نامیده می شود.
ارزیابی دقیق و غیر دقیق
مفاهیمی که به چگونگی پردازش آرگومان های تابع هنگام ارزیابی یک عبارت می پردازند، می توانند زبان های کارکردی را طبقه بندی کنند که آیا آنها از ارزیابی دقیق (مشتاق) یا غیر دقیق (lazy) استفاده می کنند. تفاوت فنی در معنای معنایی عبارات حاوی محاسبات ناکام یا واگرا است. تحت ارزیابی دقیق، ارزیابی هر اصطلاح حاوی زیرمجموعهای ناکام است. به عنوان مثال، عبارت:
print length([2+1, 3*2, 1/0, 5-4])
به دلیل تقسیم صفر در عنصر سوم لیست، در ارزیابی دقیق انجام نمی شود. تحت ارزیابی تنبل، تابع طول مقدار 4 (یعنی تعداد موارد موجود در لیست) را برمی گرداند، زیرا ارزیابی آن تلاش نمی کند تا اصطلاحات تشکیل دهنده لیست را ارزیابی کند. به طور خلاصه، ارزیابی دقیق همیشه به طور کامل آرگومان های تابع را قبل از استناد به تابع ارزیابی می کند. ارزیابی تنبل آرگومان های تابع را ارزیابی نمی کند مگر اینکه مقادیر آنها برای ارزیابی تابع فراخوانی مورد نیاز باشد.
استراتژی اجرای معمول برای ارزیابی lazy در زبان های تابعی، کاهش نمودار است. ارزیابی تنبل به طور پیش فرض در چندین زبان تابعی خالص از جمله میراندا، پاک و هاسکل استفاده می شود.
هیوز 1984 برای ارزیابی lazy به عنوان مکانیسم برای بهبود مدولار برنامه از طریق جداسازی نگرانی ها، با سهولت اجرای مستقل تولید کنندگان و مصرف کنندگان جریان داده ها، استدلال می کند. Launchbury 1993 مشكلاتي را كه ارزيابي تنبلي به ويژه در تحليل نيازهاي نگهداري برنامه ارائه مي دهد، توصيف مي كند و معاني عملياتي را براي كمك به چنين تحليلي ارائه مي دهد. هارپر 2009، ارزیابی دقیق و تنبل را به همان زبان ارائه می دهد و از سیستم نوع زبان برای تمایز آنها استفاده می کند.
انواع سیستم ها
به خصوص از زمان توسعه استنباط نوع Hindley-Milner در دهه 1970، زبان های برنامه نویسی تابعی تمایل به استفاده از حساب لامبدا تایپ شده دارند، همه برنامه های نامعتبر را در زمان تلفیق رد می کنند و خطاهای مثبت کاذب را به خطر می اندازند، برخلاف حساب حساب لامبای بدون نسخه، که همه را می پذیرد. برنامه های معتبر در زمان تدوین و خطاهای منفی کاذب، که در Lisp و انواع آن استفاده می شود (مانند Scheme)، خطرات منفی را تهدید می کند، اگرچه وقتی اطلاعات کافی برای رد برنامه های معتبر هستند، تمام برنامه های نامعتبر را در زمان اجرا رد می کنند. استفاده از داده های جبری، دستکاری در ساختار داده های پیچیده را راحت می کند. وجود چک نوع تایپ کامپایل قوی در صورت عدم وجود سایر روش های اطمینان مانند توسعه تست محور، باعث می شود که برنامه ها قابل اطمینان تر باشند، در حالی که استنتاج نوع برنامه نویس را در اکثر موارد از نیاز به اعلام دستی انواع به کامپایلر رها می کند.
برخی از زبان های تابعی پژوهش محور مانند Coq، Agda، Cayenne و Epigram براساس تئوری نوع شهودی هستند که به انواع مختلف آن ها بستگی دارد. به این نوع ها انواع وابسته گفته می شود. این سیستم های نوع استنباط قابل قبولی ندارند و درک و برنامه ریزی با آنها دشوار است. اما انواع وابسته می توانند گزاره های دلخواه را با منطق محمول بیان کنند. از طریق ایزومورفیسم کاری- هوارد، برنامه های به خوبی تایپ شده در این زبان ها به ابزاری برای نوشتن اثبات ریاضی رسمی تبدیل می شوند که از طریق آن یک کامپایلر می تواند کد تأیید شده را تولید کند. در حالی که این زبانها عمدتاً به تحقیقات دانشگاهی (از جمله در ریاضیات رسمی) علاقه مند هستند، آنها در مهندسی نیز شروع به استفاده می کنند. کامپکتتر کامپایلر برای زیر مجموعه از زبان برنامه نویسی C است که به Coq نوشته شده و به طور رسمی تأیید شده است.
یک نوع محدود از انواع وابسته به نام انواع داده های جبری عمومی (GADT) می تواند به شکلی اجرا شود که برخی از مزایای برنامه نویسی وابسته را تایپ کند ضمن اینکه از بیشتر ناراحتی های آن جلوگیری می کند. GADT در کامپایلر گلاسکو هاسکل، در OCaml (از نسخه 4.00) و در Scala (به عنوان "کلاس های موردی") موجود است، و به عنوان اضافات به زبان های دیگر از جمله جاوا و سی شارپ پیشنهاد شده است.
ساختارهای داده
ساختار داده های خالص تابعی اغلب به روشی متفاوت از همتایان ضروری آنها نشان داده می شوند. به عنوان مثال، آرایه با زمان دسترسی ثابت و به روز رسانی یک جزء اصلی اکثر زبان های ضروری است و بسیاری از ساختارهای داده ضروری مانند جدول هش و پشته های باینری مبتنی بر آرایه ها هستند. آرایه ها را می توان با نقشه ها یا لیست های دسترسی تصادفی جایگزین کرد، که پذیرش اجرای کاملاً تابعی را دارند، اما زمان دسترسی و بروزرسانی لگاریتمی دارند. ساختار داده های خالص تابعی پایداری دارند، خاصیت نگه داشتن نسخه های قبلی ساختار داده غیر اصلاح شده. در Clojure، ساختار داده مداوم به عنوان گزینه های جایگزین برای همتایان ضروری خود استفاده می شود. برای مثال بردارهای مداوم از درختان برای به روزرسانی جزئی استفاده می کنند. فراخوانی با روش درج باعث ایجاد برخی گره ها می شود.
مزایای برنامه نویسی تابعی (Functional Programming)
برنامه نویسی تابعی مزایای زیر را ارائه می دهد:
- بدون اشکالات کد: برنامه نویسی تابعی از حالت پشتیبانی نمی کند، بنابراین هیچ نتیجه ای از عوارض جانبی به وجود نمی آید و می توانیم کدهای بدون خطا بنویسیم.
- برنامه نویسی موازی کارآمد: زبان های برنامه نویسی تابعی حالت قابل تغییر ندارند، بنابراین هیچ مسئله تغییر حالت وجود ندارد. می توان "توابع" را برای کار موازی به عنوان "دستورالعمل" برنامه ریزی کرد. این کدها از قابلیت استفاده مجدد و قابلیت آزمایش آسان استفاده می کنند.
- کارآیی: برنامه های تابعی از واحدهای مستقلی تشکیل شده است که می توانند همزمان اجرا شوند. در نتیجه، چنین برنامه هایی کارایی بیشتری دارند.
- پشتیبانی از توابع تو در تو: برنامه نویسی تابعی از توابع Nested پشتیبانی می کند.
- ارزیابی Lazy: برنامه نویسی تابعی از سازه های تابعی Lazy مانند لیست های Lazy، نقشه های Lazy و غیره پشتیبانی می کند.
به عنوان یک روند نزولی، برنامه نویسی تابعی به فضای حافظه زیادی احتیاج دارد. از آنجایی که حالت ندارد، شما باید هر بار اشیاء جدیدی را برای انجام اعمال ایجاد کنید. برنامه نویسی تابعی در شرایطی استفاده می شود که ما مجبوریم عملیات های مختلف زیادی را در همان مجموعه داده انجام دهیم.
- لیسپ (Lisp) برای کاربردهای هوش مصنوعی مانند یادگیری ماشین، پردازش زبان طبیعی، مدل سازی گفتار و بینایی رایانه ای و غیره مورد استفاده قرار می گیرد.
- مترجمان Lisp تعبیه شده قابلیت برنامه نویسی را به برخی از سیستم ها مانند Emacs اضافه می کنند.
ویژگی های برنامه نویسی تابعی (Functional Programming)
- دارای توابع Higher-order
- دارای قابلیت Purity
- داده های غیر قابل تغییر
- ارزیابی lazy و اجتناب از محاسبات غیر ضروری
- Recursion یا بازگشت
- دارای توابع first-class