پارادایم‌های برنامه‌نویسی

این مقاله ترجمه مقاله دیگری از من در Medium هستآیا تا حالا در مورد «declarative programming» یا «imperative programming» شنیدید ؟ احتمالا تا حالا تو کدهاتون از این نوع استایل‌های برنامه نویسی استفاده کردید بدون اینکه حتی اسمشونو می‌دونستید !!!حتی اگه تا حالا این در این موارد چیزی نشنیده باشید احتمالا از اونا استفاده کردید بدون اینکه تا حالا در موردشون چیزی شنیده باشید. اما اگر شنیدید تا حالا فکر کردید که اینا چی هستن ؟پارادایم های برنامه نویسی چی هستندپارادایم‌های برنامه نویسی یک سری قاعده و متدولوژی هایی هستند که ساختار و پیاده‌سازی برنامه شما رو مشخص می‌کنند. همینطوری ما می‌تونیم زبان های ‌برنامه نویسی رو بر اساس این قواعد دسته بندی کنیم.به طور کلی دو نوع عاقده داریم :Imperative (دستوری)Declarative (اعلامی یا اخباری)Imperative programming در مقابل Declarative programmingدر برنامه‌نویسی به روش Imperative ما به مراحل مورد نیاز برای انجام یک کار اهمیت می‌دیم و در مقابل در برنامه‌نویسی Declarative ما به نتیجه نهایی بدون اهمیت دادن به مراحل مورد نیاز توجه می‌کنیمبه این دو کد که با کاتلین نوشته شده‌اند نگاه کنید ، می‌خوایم مجموع اعداد زوج درون یک آرایه رو به این دو شیوه حساب کنیم : https://gist.github.com/sasssass/09e9d95256a7a6e3c4e6049c4c62c331 https://gist.github.com/sasssass/13935b3a083ffdc6ecb61caac6e462a2 اولی به شیوه Imperative و دومی به شیوه Declarative نوشته شده . ما در کد اول برای محاسبه مقدار جمع اعداد زوج تمامی مراحل رو برای سیتسم شرح دادیم ولی در کد دوم بیشتر سعی کردیم از توابع خود کاتلین استفاده کنیم (و نمی‌دونیم پیاده سازی اونا چطوریه) و بیشتر به خروجی کار توجه داشتیم تا مراحل تولید اون خروجی !به طور کلی اگه بخوام بگم Imperative روی چطور تمرکز داره و declarative روی چی !به عنوان مثال اگه زبان SQL رو در نظر بگیرید این زبان یک زبان Declarative محسوب میشه ، چرا ؟ چون ما صرفا می‌گیم از فلان table بهمان row هایی رو با بیسار ویژگی به من نشون بده ! ما فقط به سیستم می‌گیم چی می‌خوایم و به SQL توضیحی نمی‌دیم که این مقادیر رو چطور باید برای ما محاسبه کنه !Imperative Paradigmsما انواع مختلفی از این نوع پارادایم (روش) ها داریم اما سه تای معروف اون این موارد هستند :ProceduralObject-Oriented Programming (OOP)StructuredProcedural Programmingما در برنامه نویسی procedural یک مجموعه ای از procedure ها (روش ها) داریم که در واقع همون توابع ما می‌تونن باشند و هر کدوم وظیفه معنی رو انجام می‌دن و برای رسیدن به خروجی مورد نظر باید یک سلسله از این روش‌ها رو پشت سر هم انجام بدیم .زبان‌های زیادی هستند که می‌تونن از این نوع باشند مثل سی، جاوا، پاسکال و ... ولی اینجا یک مثال از پاسکال برای شما می‌زنم . دلیل اون هم اینه که در پاسکال ما علاوه بر تابع (function) یک رویه‌ (procedure) هم داریم تو پاسکال فرق این دو تا اینه که procedure خروجی نداره ولی function داره ! فرض کنید همون برنامه بالا رو برای محاسبه جمع اعداد زوج بخوایم بنویسیم : https://gist.github.com/sasssass/130db72b2098ecf21fdca863ab99ca33 همون طور که می‌بینید در برنامه نویسی procedural در پاسکال ما یک procedure به اسم CalculateSumOfEvenNumbers تعریف کردیم و برای رسیدن به خروجی مورد نظر اون رو اجرا می‌کنیم (چون برنامه ساده است فقط یک procedure داریم)مسلما تا حالا صد تا کد به این روش زدید و فقط اسمشو نمی‌دونستید :)Structured Programmingتو برنامه نویسی Structured ما به یک ساختمار منظم از control flow اهمیت می‌دیم .می‌تونیم این برنامه نویسی رو در سه اصل خلاصه کنیم :سلسله مراتب : کد از بالا به پایین بر اساس یک سلسله مراتب به اجرا در میادانتخاب : همون شرایط کنترلی ما مثل if/else هستندحلقه : حلقه‌های for و while و do while و ... که باعث می‌شن یک کد رو صد بار ننویسیمدر این پاردایم ما سعی می‌کنیم مساله رو به مسایل کوچک تر تقسیم کنیم ، خیلی از زبان‌ها هستند که تو این پارادایم رو شامل می‌شن مثل سی ، جاوا ، کاتلین و ... (همون زبان‌های لیست قبل مثلا)سوالی که الان پیش میاد اینه که تفاوت procedural و structured چیه ؟ به نظر یکی میان ! مساله بیشتر اینه که برنامه نویسی structured در واقع نوعی برنامه نویسی procedural هست که از قابلیت‌های بیشتری بهره می‌بره ، ما در برنامه نویسی structured بیشتر به ماژولار کردن و استفاده از control flow های پیشرفته اهمیت می‌دیم .بیایید برنامه محاسبه فاکتوریل رو در زبان کاتلین به این دو شیوه کنار هم داشته باشیم : https://gist.github.com/sasssass/cceec4c15b1256b73c1b08131e62a651 همون طور که می‌بینید در تابع اول ما یک حلقه while داریم که با کمک اون فاکتوریل رو محاسبه می‌کنیم و تو تابع دوم از control flow های بیشتری استفاده کردیم . اگر عدد صفر یا یک باشه نیازی نیست که حلقه رو بررسی کنیم و مستقیم خروجی رو یک بر می‌گردونیم . همون طور خروجی رو مستقیم return می‌کنیم و مقدار خروجی رو تو یه متغیر نمی‌ریزیم تا دوباره اون رو return کنیم (ساختار یافته تر کد می‌زنیم)کل مبحث رو می‌تونم تو این عکس خلاصه کنم :به نظر من تفاوت این دو پاردایم در واقع صفر و یکی نیست بلکه یک طیفه ، ما می‌تونیم بیشتر کد رو ساختار یافته بنویسیم و اون رو structured در نظر بگیریم و کمتر procedural !Object-Oriented Programming (OOP)بعضی از زبان‌های برنامه نویسی مثل C از تایپی به نام structure پشتیبانی می‌کنند و بعضی از تایپی به نام class/object . تفاوت این دو در اینه که در structure ما یک مجموعه متغیر رو تحت عنوان یک ساختار جدید در نظر می‌گیریم ولی در object ما علاوه بر این مجموعه متغیر (ویژگی‌ها) یک سری توابع (رفتار‌ها) رو هم اضافه می‌کنیم .در مورد ما اشیا موجود در دنیای واقعی رو مدل سازی می‌کنیم . مثلا کلاس «پرنده» یک نوع داره (کلاغ، قناری و ...) و یک سری تابع داره (پرواز کردن و راه رفتن) و خود پرنده نوعی «حیوان» هست که این کلاس «حیوان» دیگه تابع پرواز رو نداره و فقط راه رفتن رو داره .به این مدل از برنامه نویسی ، برنامه نویسی شی گرا می‌گن و بر ۴ رکن استواره :وراثت یا inheritance : یک پرنده یک نوع حیوان هست ، ما می‌تونیم دو شی داشته باشیم و رابطه والد و فرزند بین اون‌ها برقرار کنیم ، کلاس فرزند همه‌ی ویژگی‌ها و رفتار‌های کلاس والد رو به ارث می‌بره (و می‌تونه اونا رو تغییر یا اصلاح کنه) و همینطور می‌تونه ویژگی‌ها و رفتار‌های اضافه و خاص خودش رو داشته باشهچندریختی یا polymorphism : برای یک رفتار می‌تونیم چنین پیاده سازی داشته باشیم . این پیاده سازی ها به دو نوع داینامیک (run-time) و استاتیک (compile-time) تقسیم بندی می‌شن ، مثلا ما می‌تونیم دو تابع با یک اسم یکسان و ورودی‌های مختلف داشته باشیم (استاتیک) یا می‌تونیم یک تابع رو که در کلاس والد بوده در کلاس فرزند override کنیم (داینامیک) https://gist.github.com/sasssass/73b99f6bba3feec6650a11cf37d2071a در مثالی که می‌بینید ما یک شی از جنس والد داریم اما اون رو با نوع فرزند مقدار دهی می‌کنیم و چندریختی موجود نوع داینامیک هست و دلیل اون اینه که سیستم در حین اجرا تصمیم می‌گیره از کدوم تابع استفاده کنه (که اینجا از تابع کلاس فرزند استفاده خواهد کرد) . https://gist.github.com/sasssass/e714e527f135f8f1ccc761f88a974644 در این یکی مثال نوع چندریختی از جنس استاتیک هست چون سیستم در زمان compile به این نتیجه خواهد رسید که از در وقتی به خط شماره ۱۳ یا ۱۴ رسید از کدوم تابع باید استفاده کنه .کپسوله سازی یا encapsulation :پنهان کردن اطلاعات و مشخصه‌های یک شی از یک شی دیگه و محدود کردن دسترسی به این مشخصه‌ها به جز با توابع خاص (مثلا getter و setter) https://gist.github.com/sasssass/2051e95cc73525ad23a6d19326a73457 دلیلی که برای این کار داریم در این مثال به خوبی نمایان هست ، ما نمی‌خوایم متغیر number مقادیر منفی داشته باشه پس این محدودیت رو در setter ایجاد کردیم ، همین طور شاید بخوایم وقتی کسی به number دسترسی پیدا می‌کنه یک کار اضافه (مثل log کردن یا ...) انجام بشهانتزاع یا abstraction :انتزاع در واقع مربوط به وراثت هست ، بعضی وقتا ما می‌خوایم پیچیدگی رو در اشیا کمتر کنیم ، مثلا به مثلا پرنده بر‌می‌گردیم ، هر پرنده یک نوع حیوان هست و در این انتزاع حیوان یک سری مشخصه‌ها و رفتار‌ها تعبیه شده (مثل راه رفتن) و دیگه لزومی نداره در کلاس پرنده پیاده سازی اون‌ها رو تکرار کنیم (مگر اینکه به دلیلی بخوایم override انجام بدیم) و همچین یک سری رفتارهای انتزاعی مثل غذا خوردن (که می‌تونه برای پرنده دونه باشه و برای شیر گوشت) رو داریم که در کلاس والد صرفا تعریف اون‌ها رو میاریم و کلاس فرزند رو مجبور می‌کنیم که پیاده سازی متناسب خودش رو برای اون تابع انجام بده . کلاس حیوان تو این مثال یک کلاس انتزاعی میشه پس ما هیچوقت نمی‌تونیم یک شی از حیوان داشته بشیم که با خود کلاس حیوان مقداردهی بشه ، بلکه می‌تونیم یک شی از کلاس حیوان داشته باشیم که با کلاس پرنده مقداردهی بشه . https://gist.github.com/sasssass/c925d87bf91f745dbe022d031757cddb Declarative programmingما انواع مختلفی از برنامه نویسی Declarative داریم ولی ۳ تای معروف اون این موارد هستند :Functional ProgrammingReacting ProgrammingLogic ProgrammingFunctional Programmingدر برنامه نویسی functional ما به توابع ریاضی اهمیت می‌دیم (مثل f(x) = y) و المان های کلیدی مثل این دو مورد رو داریم تغییر ناپذیری :‌ ورودی یک تابع هیچگاه تغییر نمی‌کنه و از متغیر‌های global هم استفاده نباید بکنیم. برای ساخت ورودی جدید باید خروجی یک تابع رو به داخل تابع دیگه پاس بدیم.برنامه نویسی بازگشتی : از حلقه استفاده نمی‌کنیم و به جاش از توابع بازگشتی استفاده می‌کنیم ، اگه این مورد رو کنار تغییر ناپذیری بذارید می‌بینید که منطقی به نظر میاد . ما نمی‌خوایم متغیری مثل sum داشته باشیم که مرتبا تغییرش بدیم بلکه این sum رو می‌تونیم از راه بازگشتی حساب کنیم https://gist.github.com/sasssass/259082d81329a0463f2c23b9b88e8dc8 اینجا یک مثال به زبان کاتلین داریم که همون برنامه محاسبه جمع اعداد زوج هست و از توابع بازگشتی استفاده کردیم .باید مراقب باشید کی و کجا از تابع بازگشتی استفاده کنید چون این نوع توابع می‌تونن مقدار زیادی حافظه رو مصرف کنند و برنامه شما رو به سمت crash سوق بدن :)توابع با اولیت بالاتر : شما می‌تونید یک تابع رو به عنوان ورودی یک تابع دیگه پاس بدید ، f(y) = z + 2 و y = x²Reacting Programmingبرنامه نویسی reactive بیشتر به eventها و جریان داده asynchronous (این لغات بهتره هیچوقت ترجمه نشن) اهمیت میده . هر زمان یک event داشته باشیم ، یک جریان داده جدید هم داریم و به این تغییرات واکنش یا react می‌کنیم .این نوع برنامه نویسی بر اساس دیزان پترن observable ساخته میشه . ما یک سری observable داریم که یک جریان داده رو انتشار یا emit می‌کنند و یک سری observer داریم که این observableها رو subscribe می‌کنند تا بتونن نتیجه رو پردازش کنند .همه چیز به صورت asynchronous اتفاق میفته ، یعنی ما میتونیم چندین observable داشته باشیم و این observable ها همدیگه رو block نمی‌کنند .معمولا وقتی از این شیوه استفاده می‌کنیم از ابزار reactive extension بهره می‌بریم که مجموعه کاملی از توابع کمکی رو در اختیار ما قرار می‌دن و برای انواع زبان هم library دارن . مثلا یک توسعه دهنده جاوا از RxJava و یک توسعه دهنده ++C از RxCpp استفاده می‌کنه .در واقع Rx مجموعه ای از extensionها و انواع مختلفی از observable رو برای استفاده ما قرار می‌ده که ترکیبی از برنامه نویسی reacting و functional به حساب میاد ، ما میتونیم چندین extension رو برای تغییر داده‌هامون استفاده کنیم (مثل فیلتر یا ترکیب داده‌ها و ...)به عنوان مثال کد زیر برای محاسبه مجموع اعداد زوج با این روش و با استفاده از Rx نوشته شده : https://gist.github.com/sasssass/debdb1086d9f94e126aa65df60f651d8 Logic Programmingبرنامه نویسی logic بر اساس formal logic صورت می‌گیره ولی این formal logic چی هست ؟ ما به طور کلی formal logic و informal logic داریم . در formal logic ما یک سری سمبل و در informal logic از زبان انسان استفاده می‌کنیم (فارسی و انگلیسی و ...) ، در formal logic ما قواعد مشخصی داریم که در نهایت ما رو به یکی از دو گزاره true یا false هدایت می‌کنند ولی در informal logic ما به کیفیت اهمیت می‌دیم نه درستی و غلطی !مثلا اگه از شما بپرسن هوا امروز سرده شما در informal logic می‌تونی بگی یکم سرده یا خیلی سرده ولی در formal logic هوا یا سرد هست یا سرد نیست ! در واقع همون منطق بولی !ما در برنامه نویسی به شیوه logic قواعد و گزاره‌های مطلق و الگوهایی رو داریم و خروجی رو بر اساس اونا بدست میاریم. معروفی ترین زبان برنامه نویسی به این روش Prolog هست . یک مثال با هم ببینیم : https://gist.github.com/sasssass/8f62fa42a15d5ee4226766797d06e3a7 توی این مثال ما یک سری گزاره داریم ، john والد mary هست ، john والد lisa هست و الی آخر . یک سری گزاره دیگر هم داریم که john مرد هست ، mary زن هست و ... . حالا یک سری قوانین داریم که اگر شخصی والد شخص دیگه ای بود و مرد بود پس اون father به حساب میاد و اگر شخصی والد شخص دیگه ای بود ود زن بود اون شخص mother به حساب میاد .مثلا اگر father(john, mary) به سیستم داده بشه خروجی true هست .آدرس کانال تلگرامی ما : لینکمن رو در لینکدین ، اینستاگرام و یوتیوب دنبال کنید !!!