functional programming یا برنامه نویسی تابعگرا در جاوا اسکریپت چیست

functional programming یا برنامه نویسی تابعگرا در جاوا اسکریپت چیست

functional programming یا برنامه نویسی تابعگرا در جاوا اسکریپت چیستWhat is Functional Programming?

functional programming یا برنامه نویسی تابعگرا در جاوا اسکریپت چیست

توسط : admin
برنامه نویسی تابعگرا به یک موضوع واقعاً داغ در جهان JavaScript تبدیل شده است. فقط چند سال پیش ، معدود برنامه نویسان JavaScript حتی می دانستند که برنامه نویسی تابعگرا چیست ، اما در هر کد برنامه بزرگی که در 3 سال گذشته دیده ام ،تا حد زیادی از ایده های برنامه نویسی تابعگرا استفاده شده است.

 

Functional programming (با اختصار FP) فرآیند ساختن نرم افزار با ترکیب توابع خالص ، جلوگیری از حالت مشترک ، داده های قابل تغییر و اثرات جانبی است. برنامه نویسی تابعگرا بیشتر از آنچه دستوری باشد ، مفهومی است و وضعیت برنامه از طریق توابع خالص جریان می یابد. این روش کاملا متضاد با برنامه نویسی شی گرا  است، جایی که وضعیت برنامه معمولاً با متد های موجود در اشیاء به اشتراک گذاشته می شود.

برنامه نویسی تابعگرا یک الگوی برنامه نویسی است ، به این معنی که روشی برای تفکر در مورد ساخت نرم افزار است که براساس برخی اصول اساسی و تعیین کننده ساخته شده است (در بالا ذکر شده است). نمونه های دیگر پارادایم های برنامه نویسی شامل برنامه نویسی شی گرا و برنامه نویسی رویه ای است.

کدنویسی تابعگرا تمایل به آزمایش دقیق تر ، قابل پیش بینی تر و ساده تر از کد دستوری یا شی گرا بودن دارد ، اما اگر با آن و الگوهای متداول مرتبط با آن نا آشنا باشید  ، کدنویسی تابعی نیز می تواند بسیار پیچیده تر به نظر برسد و درک ادبیات مرتبط می تواند برای تازه واردان غیرقابل نفوذ  و سخت باشد.

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

 

اجازه ندهید که کلمات جدید شما را بترساند. این موضوع بسیار ساده تر از آن است که به نظر می رسد

 

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

  • توابع خالص(Pure functions)
  • ترکیب عملکرد (Function composition)
  • از حالت مشترک جلوگیری کنید(Avoid shared state)
  • از حالت تغییر خودداری کنید(Avoid mutating state)
  • از اثرات جانبی خودداری کنید(Avoid side effects)

به عبارت دیگر ، اگر می خواهید بدانید برنامه نویسی تابعگرا در عمل به چه معناست ، باید با درک این مفاهیم اصلی شروع کنید.

یک تابع خالص تابعی است که:

با توجه به ورودی های یکسان ، همیشه خروجی یکسان داشته  و اثرات جانبی نداشته باشد .

توابع خالص(Pure functions) دارای بسیاری از ویژگی های مهم در برنامه نویسی تابعگرا ، از جمله شفافیت ارجاع است (شما می توانید بدون تغییر معنی در برنامه ، فراخوانی یک تابع را ، با مقدار بدست آمده از آن ، جایگزین کنید).

 

ترکیب عملکرد (Function composition) فرآیند ترکیب دو یا چند تابع به منظور تولید یک تابع جدید یا انجام برخی محاسبات است. به عنوان مثال ، ترکیب f. g (نقطه به معنی "تشکیل شده با") برابر با ((f (g (x در JavaScript است. درک ترکیب تابع یک گام مهم برای درک چگونگی ساخت نرم افزار با استفاده از برنامه نویسی تابعگرا است.

 

حالت مشترک(shared state)

حالت اشتراکی ، هر متغیر ، شی یا فضای حافظه ای است که در یک دامنه مشترک وجود دارد ، یا به عنوان خاصیت یک شی در بین محدوده منتقل می شود. دامنه مشترک می تواند دامنه سراسری یا محدوده بسته شده را شامل شود. اغلب ، در برنامه نویسی شی گرا ، اشیاء با اضافه کردن خواص به اشیاء دیگر بین اسکوپ ها به اشتراک گذاشته می شوند.

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

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

تصور کنید تغییراتی در اطلاعات یک شی به نام user دارید که نیاز است ذخیره شود. تابع ()saveUser شما درخواستی را برای API روی سرور ارسال می کند. در حالی که این اتفاق می افتد ، کاربر تصویر پروفایل خود را با ()updateAvatar تغییر می دهد و یک درخواست ()saveUser دیگر ایجاد می کند.در زمان ذخیره ، سرور باید شی کاربر دریافت شده را با آنچه که در حافظه از قبل ذخیره شده است جایگزین کند تا بتواند با تغییراتی که در سرور یا در پاسخ به سایر درخواست های API رخ می دهد ، همگام شود.

متأسفانه ، پاسخ درخواست دوم یعنی تابع ()updateAvatar قبل از اولین درخواست دریافت می شود ، بنابراین هنگامی که پاسخ اولین  (که اکنون منسوخ شده) بازگشت می شود ، عکس پروفایل جدید که با تابع ()updateAvatar بروز شده بود ، در حافظه پاک می شود و  با عکس موجود در درخواست اول که پس از ()updateAvatar اجرا شده ، جایگزین می شود. این یک نمونه از شرایط مسابقه است - یک اشکال بسیار رایج که در حالت مشترک وجود دارد.

 

یکی دیگر از مشکلات رایج در ارتباط با حالت مشترک این است که تغییر ترتیب توابع نامیده می شود ، باعث آبشار خرابی شود زیرا توابعی که در حالت مشترک به کار می روند وابسته به زمان هستند:

// ترتیب فراخوانی توابع  در حالت مشترک
// نتیجه فراخوانی توابع را تغییر می دهد.
const x = {
  val: 2
};

const x1 = () => x.val += 1;

const x2 = () => x.val *= 2;

x1();
x2();

console.log(x.val); // 6

// این مثال دقیقاً معادل مورد فوق است ، جز ...
const y = {
  val: 2
};

const y1 = () => y.val += 1;

const y2 = () => y.val *= 2;

// ... ترتیب فراخوانی توابع معکوس می شود ...
y2();
y1();

// ... که مقدار حاصل را تغییر می دهد:
console.log(y.val); // 5

 

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

 

const x = {
  val: 2
};

const x1 = x => Object.assign({}, x, { val: x.val + 1});

const x2 = x => Object.assign({}, x, { val: x.val * 2});

console.log(x1(x2(x)).val); // 5


const y = {
  val: 2
};

// از آنجا که هیچ وابستگی به متغیرهای بیرونی وجود ندارد ،
// ما به توابع مختلفی احتیاج نداریم
// متغیرها.

// این قسمت مخصوصا خالی مانده است


// از آنجا که توابع اشتراک ندارند ، می توانید اینها را صدا کنید
// هر زمان که بخواهید ، به هر ترتیب عمل می کند ،
// بدون تغییر نتیجه سایر فراخوانی های تابع.
x2(y);
x1(y);

console.log(x1(x2(y)).val); // 5

 

در مثال بالا ، از ()Object.assign استفاده کردیم و به عنوان اولین پارامتر یک آبجکت خالی قرار دادیم تا به جای اینکه در جای خود تغییر یابد ، خواص x را کپی کند. این حالت ، می تواند معادل ساختن یک شیء جدید از ابتدا ، بدون ()Object.assign  باشد ، اما این یک الگوی رایج در جاوا اسکریپت برای ایجاد نسخه هایی از حالت موجود به جای استفاده از متغیرها است ، که در ابتدای مثال نشان دادیم.

اگر در این مثال به خروجی () console.log نگاهی بیندازید ، باید به چیزی که قبلاً به آن اشاره کردم توجه کنید: ترکیب عملکرد. از گذشته به یاد بیاورید ، ترکیب عملکرد به این صورت است: ((f (g (x. در این حالت ()f  و ()g  را با ()x1  و ()x2  برای ترکیب جایگزین می کنیم: x1. x2

البته اگر ترتیب ترکیب را تغییر دهید ، خروجی تغییر خواهد کرد. ترتیب عملیات هنوز هم مهم است. ((f (g (x همیشه برابر با ((g (f (x نیست ، اما آنچه دیگر مهم نیست بی تاثیر بودن فراخوانی های خارج از تابع بر روی خروجی اصلی ما است منظور( ;(x1(y  و ;(x2(y  ) ، که این یک مسئله بزرگ است. با توابع ناخالص ، درک کامل عملکردی که انجام می شود غیرممکن است مگر اینکه کل تاریخچه متغیرهایی را که تابع استفاده می کند یا روی آن تأثیر می گذارد ، بدانید.


تغییر ناپذیری

یک شی تغییر ناپذیر شیئی است که پس از ایجاد نمی تواند اصلاح شود. در مقابل ، یک شیء قابل تغییر هر شیء است که می تواند پس از ایجاد آن اصلاح شود.

تغییر ناپذیری یک مفهوم اصلی برنامه نویسی تابعگرا است زیرا بدون آن ، جریان داده ها در برنامه شما از بین می رود. تاریخچه وضعیت ، از بین رفته و می تواند به نرم افزار شما  اشکالات عجیب و غریب  وارد شود.

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

اشیاء غیرقابل تغییر به هیچ وجه قابل تغییر نیستند. با انجماد عمیق یا (deep freezing) شی می توانید یک مقدار واقعاً غیرقابل تغییر ایجاد کنید. جاوا اسکریپت روشی دارد که یک شیء را در عمق سطح اول منجمد می کند:

const a = Object.freeze({
  foo: 'Hello',
  bar: 'world',
  baz: '!'
});

a.foo = 'Goodbye';
// Error: Cannot assign to read only property 'foo' of object Object

اما اشیاء منجمد شده فقط از نظر سطح غیرقابل تغییر هستند. به عنوان مثال ، شیء زیر قابل تغییر است:

const a = Object.freeze({
  foo: { greeting: 'Hello' },
  bar: 'world',
  baz: '!'
});

a.foo.greeting = 'Goodbye';

console.log(`${ a.foo.greeting }, ${ a.bar }${a.baz}`);

 

همانطور که مشاهده می کنید ، ویژگیهای ابتدایی سطح بالای یک جسم فریز شده نمی توانند تغییر کنند ، اما هنوز هم می توان هر خاصیتی را که یک شیء باشد (از جمله آرایه ها و غیره ...) تغییر داد ، بنابراین حتی اشیاء فریز شده غیرقابل تغییر هستند مگر اینکه در درخت کل شیء پیشروی کرده  و  هر خاصیت شی را مسدود کنید.

در بسیاری از زبانهای برنامه نویسی تابعگرا ، ساختارهای داده غیرقابل تغییر به نام ساختارهای داده trie (تلفظ "درخت") وجود دارند که بطور مؤثر فریز شده اند ، به این معنی که بدون توجه به سطح خاصیت ، در سلسله مراتب شیء ، هیچ خاصیتی نمی تواند تغییر کند.

در Tries سعی می شود از اشتراک گذاری ساختاری برای به اشتراک گذاشتن مکانهای ارجاع حافظه برای همه قسمتهای شی که پس از کپی کردن یک شیء توسط یک عملگر ، که از حافظه کمتری استفاده می کند ، بدون تغییر باشد استفاده کند و پیشرفت های چشمگیر تابع را برای برخی از انواع عملیات امکان پذیر می کند.

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

چندین کتابخانه در جاوا اسکریپت وجود دارد که از مزیت tries استفاده می کنند ، از جمله Immutable.js و Mori.

من هر دو را تجربه کرده ام و تمایل دارم از Immutable.js در پروژه های بزرگ استفاده کنم که به مقدار قابل توجهی حالت تغییرناپذیری احتیاج دارند.

 

اثرات جانبی

یک اثر جانبی شامل هر تغییر حالت در برنامه است که غیر از مقدار برگشتی ، قابل مشاهده است. اثرات جانبی شامل:

  • تغییر هر متغیر خارجی یا خاصیت شی (به عنوان مثال ، یک متغیر گلوبال یا متغیر در اسکوپ دامنه عملکرد والد)
  • درج در کنسول لاگ
  • نوشتن روی صفحه
  • نوشتن در یک فایل
  • نوشتن در شبکه
  • اجرای هر فرایند بیرونی
  • فراخوانی سایر توابع با اثرات جانبی

 

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

هاکل و سایر زبانهای تابعگرا معمولاً اثرات جانبی توابع خالص را با استفاده از قطعه ها جدا کرده و کپسوله می کنند. موضوع کپسوله کردن به اندازه کافی عمیق است که می توانید در مورد آن کتاب بنویسید ، بنابراین بعداً در مورد آن صحبت خواهیم کرد.

آنچه شما در حال حاضر باید بدانید این است که اقدامات جانبی اثر باید از بقیه بخش های نرم افزار شما جدا شوند. اگر اثرات جانبی خود را از بقیه منطق برنامه جدا نگه دارید ، نرم افزار شما برای گسترش ، refactor ، اشکال زدایی ، آزمایش و نگهداشت بسیار ساده تر خواهد بود.

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

 

قابلیت استفاده مجدد از طریق توابع مرتبه بالاتر

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

در برنامه نویسی تابعگرا ، هر نوع داده میتواند وارد بازی شود. مانند ()map که می تواند بر روی اشیاء ، رشته ها ، اعداد یا هر نوع داده دیگر عمل کند زیرا تابعی را به عنوان یک آرگومان می گیرد که یک نوع داده است. FP با استفاده از توابع مرتبه بالاتر این ترفند ها را پیاده میکند.

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

تابع مرتبه بالاتر هر تابعی است که یک تابع را به عنوان یک آرگومان دریافت میکند ،و یک تابع یا هر دو را برمی گرداند. توابع مرتبه بالاتر اغلب مورد استفاده قرار می گیرد برای:

  • کپسوله کردن یک عمل ، اثرات ، یا کنترل جریان ناهمزمان با استفاده از کالبک ها ، وعده ها ، قطعه کدها و غیره.
  • برنامه هایی ایجاد کنید که می توانند در انواع مختلفی از داده ها عمل کنند
  • بخشی از تابع را برای آرگومان های آن اعمال کنید یا یک تابع  را به منظور استفاده مجدد یا ترکیب عملکرد ایجاد کنید
  • لیستی از توابع را تهیه کرده و ترکیب برخی از توابع ورودی را برگردانید

 

کانتینرها ، عمل کننده ها ، لیست ها و استریم ها

functors  کار نگاشت روی اشیا را انجام میدهد. به عبارت دیگر ، ظرفی است که دارای رابط کاربری است و می تواند برای اعمال یک تابع در مقادیر داخل آن استفاده شود. وقتی کلمه functor را می بینید ، باید به "mappable" فکر کنید.

پیش از این یاد گرفتیم که  ابزار ()map می تواند در انواع مختلف داده ها عمل کند. این کار را توسط عملیات نگاشت برای کار با functor API انجام می دهد. عملیات مهم کنترل جریان مورد استفاده توسط ()map  از این رابط استفاده می کند. در این مورد ()Array.prototype.map ، ظرف ما ، یک آرایه است ، اما سایر ساختارهای داده نیز تا زمانی که mapping API را تهیه میکنند می توانند functors باشند.

بیایید بررسی کنیم که چگونه ()Array.prototype.map  به شما اجازه می دهد نوع داده را از ابزار نگاشت برای انتزاع نقشه استفاده کنید تا ()map با هر نوع داده قابل استفاده باشد. ما یک نگاشت ساده ()double ایجاد خواهیم کرد که به راحتی مقادیر منتقل شده را با 2 ضرب می کند:

const double = n => n * 2;
const doubleMap = numbers => numbers.map(double);
console.log(doubleMap([2, 3, 4])); // [ 4, 6, 8 ]

اگر بخواهیم در یک بازی بر روی اهداف عمل کنیم تا تعداد امتیازات آنها را دو برابر کنیم باید چکار کنیم؟ تنها کاری که باید انجام دهیم این است که یک تغییر نامحسوس را در تابع  ()double که به map پاس میدهیم ، ایجاد کنیم ، و همه چیز هنوز هم کار می کند:

const double = n => n.points * 2;

const doubleMap = numbers => numbers.map(double);

console.log(doubleMap([
  { name: 'ball', points: 2 },
  { name: 'coin', points: 3 },
  { name: 'candy', points: 4}
])); // [ 4, 6, 8 ]

به منظور استفاده از توابع برای دستکاری هر تعداد از انواع مختلف داده ، مفهوم استفاده از انتزاعاتی از قبیل functors و توابع مرتبه بالاتر در برنامه نویسی تابعگرا دارای اهمیت است.خواهید دید یک مفهوم مشابه را با نوع های مختلف ، اعمال خواهید کرد.

 

در حال حاضر تنها چیزی که باید درک کنید اینست که آرایه ها و functors ها تنها روشی نیستند که مفهوم کانتینر و مقادیر موجود در کانتینر در آنها اعمال می شود. به عنوان مثال ، یک آرایه فقط لیستی از آیتم ها است. لیستی که در طول زمان بیان شده است ، یک stream است ، بنابراین می توانید همان نوع ابزارهای کاربردی را برای پردازش جریان وقایع ورودی استفاده کنید ، چیزی که هنگام شروع ساختن نرم افزار واقعی با FP ، مشاهده خواهید کرد.

 

اعلان در مقابل دستور

برنامه نویسی تابعگرا یک الگوی اعلامی است ، به این معنی که منطق برنامه بدون توصیف صریح کنترل جریان بیان می شود.

برنامه های دستوری برای توصیف مراحل خاص استفاده شده برای دستیابی به نتایج مطلوب ، خطوط کد را بیان می کنند ، کنترل جریان: نحوه انجام کارها است.

برنامه های اعلامی فرایند کنترل جریان را انتزاع می کنند ، و در عوض خطوط کد را برای توصیف جریان داده صرف می کنند: چه کاری باید انجام شود. چگونه انتزاع می شود.

به عنوان مثال ،  نگاشت  دستوری ، آرایه ای از اعداد را گرفته و آرایه جدیدی را با هر عدد ضرب در 2 برمی گرداند:

const doubleMap = numbers => {
  const doubled = [];
  for (let i = 0; i < numbers.length; i++) {
    doubled.push(numbers[i] * 2);
  }
  return doubled;
};

console.log(doubleMap([2, 3, 4])); // [4, 6, 8]

نگاشت اعلانی همان کار را انجام می دهد ، اما کنترل جریان را با استفاده از ابزار کاربردی ()Array.prototype.map که به شما امکان می دهد واضح تر جریان داده را بیان کنید :

const doubleMap = numbers => numbers.map(n => n * 2);

console.log(doubleMap([2, 3, 4])); // [4, 6, 8]

کد دستوری اغلب از عبارات استفاده می کند. کد اعلانی یک قطعه کد است که برخی از اقدامات را انجام می دهد. نمونه هایی از عبارات متداول شامل ، if ، switch ، throw  و غیره ... هستند.

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

اینها همه نمونه عبارات هستند:

2 * 2
doubleMap([2, 3, 4])
Math.max(4, 3, 2)

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

 

نتیجه گیری

برنامه نویسی تابعگرا :

  • توابع خالص به جای حالت مشترک و اثرات جانبی
  • تغییر ناپذیری نسبت به داده های قابل تغییر
  • استفاده از ترکیب عملکرد بیش از کنترل جریان دستوری
  • بسیاری از ابزارهای عمومی و قابل استفاده مجدد که از توابع مرتبه بالاتر استفاده می کنند به جای متد هایی که فقط روی داده های جمع شده آنها کار می کنند ، در بسیاری از انواع داده ها عمل می کنند
  • اعلامی و نه کد دستوری (چه کاری انجام دهیم ، نه اینکه چگونه آن را انجام دهیم)
  • عبارات بیان شده
  • ارجحیت ظروف و توابع مرتبه بالاتر نسبت به ad-hoc های چند ریختی

 

نظرات :

در عرض چند دقیقه برای ایجاد حساب

کاربری خود اقدام کنید


اکنون حساب کاربری خود را ایجاد کنید!


ایجاد حساب کاربری

با ثبت نام در نیلوتک از آخرین بروز رسانی های آموزش ها و مقالات سایت مطلع شوید