امان از دست کلمات و اصطلاحات قلبمه و سملبهی functional programming! هرچند این کلمات یادگیری را دشوار میکنند ولی فوائدی هم دارند.
دقت کردید هر کسی از کلمه femenism یک تعریف داره؟ بعضی از کلمات قلمبه و سلمبه در functinal programming هم دچار همچین عاقبتی شدند.
دلایل مختلفی باعث این موضوع شده و یکی از آنها به نظر من تلاش تولید کنندههای محتوا برای ساده کردن مفاهیم بوده. این ساده کردن در بعضی موارد منجر به انتقال ناقص مفهوم و حتی انتقال نادرست آن شده. یکی از آنها مفهومیه با نام side-effect. حدس میزنم که تا به حال حداقل یکبار در این رابطه مطالعه کردید! با کمال تاسف باید بگم که غالب تعاریف و مطالبی که شما با آنها برخورد کردید کاملا غلط هستند. نه از دید من! بلکه از دید ریاضیات!
اگر علاقهمند به دیدن ویدئو هستید توصیه میکنم فانکشنال تراپی زیر را حتما تماشا کنید.
باغبان گر پنج روزی صحبت گل بایدشبر جفای خار هجران صبر بلبل بایدش
Side-effect
اول یک نگاهی به تعریف Wikipedia کنیم:
In computer science, an operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, ...
-- Wikipedia
یک مقدار پیچیده شد! سادهترش را ببینیم:
That is to say has an observable effect besides returning a value (the main effect) to the invoker of the operation.
-- Wikipedia
شاید بد نباشه که به اولین تعریفی که از این کلمه شده هم بپردازیم:
The term Side effect refers to the modification of the nonlocal environment.
نگران نباشید! این تعاریف برای دوستداران منبع ذکر شده. تمامی تعاریف دارند به یک مفهوم ساده اشاره میکنند. کلمه side-effect یعنی شما یک چیزی را در یک جایی از این جهان فاسد تغییر بدید! به شکلی که تغییر ایجاد شده قابل رویت یا تاثیرگذار باشه!
میخواید متوجه بشید که آیا یک عملیات side-effect داره یا خیر؟ دو شرط پایین را به خاطر بسپارید.
اولا: Mutation
جدی عرض میکنم! شما باید واقعا یک تغییر ایجاد کنید! بدون تغییر نمیتونید ادعای داشتن side-effect کنید.
به مثالهای زیر توجه کنید:
- نوشتن یک فایل روی دیسک
- پاک کردن user
- نوشتن یک بلاگ پست جدید
- آغاز جنگ جهانی سوم
- خودکشی
در هر کدام از موارد فوق شما یک تغییری در جهان اطراف ایجاد کردید، پس اولین شرط لازم بهوجود آوردن یک side-effect یعنی mutation را دارید.
دوما: Observable
به مثال زیر توجه کنید:
1function f() {2 let x = 034 ++x5 ++x67 x = 189 return x10}
در فانکشن f ما سه مرتبه مقدار x را تغییر دادیم (mutation) ولی مقدار x در خارج از فانکشن f قابل رویت نیست. یعنی تغییرات ایجاد شده هیچ تاثیری در جهان ایجاد نکردند.
حالا دلیل انتخاب کلمه effect را درک می کنید. هر side-effect باید تاثیری بر سایر موجودیتها اعمال کنه.
Idempotency (A.K.A. Determinism)
فانکشن
+
را در نظر بگیرید. اهمیتی نداره که آن را چندبار صدا کنید یا در چه زمانی از روز صدا کنید.
مادامی که ورودی یکسانی به فانکشن
+
بدید خروجی یکسانی خواهید گرفت.
12 + 2 = 423-- 2000 years later452 + 2 = 4
فانکشن idempotent یا deterministic همواره به ازای یک ورودی یکسان یک خروجی یکسان به ما خواهد داد.
در مقابل آن فانکشن non-idempotent یا non-deterministic قرار گرفته که امکان داره به ازای ورودی یکسان خروجی غیریکسان به ما بده.
1Math.random() // 0.338827798216542852Math.random() // 0.85285837716652883Math.random() // 0.76382461906862
رابطه side-effect و idempotency
لازمه که دقت کنید دو مفهوم side-effect و idempotency هیچ ارتباطی به هم ندارند. یعنی از روی یکی نمیتونیم دیگری را اثبات کنیم.
تصویر بالا تفاوت این دو مفهوم و دلیل عدم ارتباطشان را به شما نشان میده. چیزی که باعث تاثیر روی رفتار فانکشن ما میشه و idempotency را نقض میکنه با side cause نشان داده شده. تاثیری که فانشکن ما بر جهان میذاره را با side-effect نمایش داده. پس از شما خواهش میکنم برای این دو مفهوم تفاوت قائل بشید.
بر اساس idempotency و side-effect میتونیم فانکشنها را به چهار دسته تقسیم کنیم:
Side-effect-free | Idempotent | |
---|---|---|
Pure | ✅ | ✅ |
Impure - side-effectful | ❌ | ✅ |
Impure - non-idempotent | ✅ | ❌ |
Impure - side-effectful + non-idempotent | ❌ | ❌ |
Pure Function (A.K.A. Referential Transparent)
فانکشنهایی که idempotent هستند و side-effectای ندارند را pure خطاب میکنیم. این فانکشنها مثل فانشکنهای ریاضی عمل میکنند و برای ما بسیار ارزشمندند. چرا؟ چون بسیار خوانا هستند، تست کردنشان فوقالعاده ساده است و امکان نداره که رفتارهای عجیب و غریب ازشان ببینیم.
Impure Function (A.K.A. Referential Opaque)
اگر فانکشن ما idempotent نباشه و/یا side-effect داشته باشه جزو impureها قرار میگیره. در عمل ما اهمیتی نمیدیم که چرا یک فانکشن impure شده و همه را داخل یک دسته قرار میدیم. در واقع فانکشن اسم درستی برای اینها نیست و باید با عنوان procedure صداشون کنیم. از procudure ها انتظار هرگونه رفتار عجیب و غریب و وحشیانهای را داشته باشید.
Non-idempotent + Side-effect-free
1let x = 023function getX() {4 return x5}67function now() {8 return Date.now()9}1011function random() {12 return Math.random()13}
اگر فکر میکنید که هر کدام از سه فانکشن بالا side-effect دارند باید بهتون بگم که در دام تعاریف نادرست قرار گرفتید. اولین شرط side-effect انجام mutation بود. (اگر این جمله را قبول ندارید یعنی شما تعریف خودتان از side-effect را ساختید و با کمال تاسف تعریف شما از side-effect برای هیچکس الی خودتان ارزشی نداره.)
در هر کدام از فانکشنهای بالا ما تغییری ایجاد نکردیم. تمام این فانکشنها non-idempotent هستند ولی ابدا side-effect ندارند.
1function fetchUser(id) {2 return API.getUser(id) // GET /users/:id - endpoint doesn't perform any side-effect3}
نظرتان درباره فانکشن
fetchUser
چیه؟
فکر میکنید
side-effect
داره؟
با توجه به اینکه میدونیم در سمت
endpoint
هیچ
side-effect
ای اجرا نمیشه
این فانشکن هم همچنان هیچ
side-effect
ای نداره و صرفا
non-idempotent
به حساب میاد.
پس اگر صرفا خواندن از منابع تغییرپذیر صورت بگیره شما side-effect نخواهید داشت بلکه دچار نقض idempotency شدهاید.
These terms have definitions.
-- Mohammad Hasani
Idempotent + Side-effectful
1function f() {2 API.startWW3()34 return 15}
حدس بزنید با صدا زدن این فانکشن چه اتفاقی رخ خواهد داد؟! پر واضح که side-effect خواهد داشت ولی همانطور که میبینید این فانکشن همواره یک خروجی برمیگردونه و این یعنی idempotent هست.
Non-idempotent + Side-effectful
1function f(data) {2 return API.postUser(data) // POST /users/:id - register new user3}
توضیح واضحات خواهد بود اگر کلامی بگم.
تعاریف نادرست رایج از side-effect
همانطور که اشاره کردم تعاریف نادرست زیادی از side-effect ارائه شده. بیاید بررسی کنیم که این تعاریف نادرست چی هستند؟
Any I/O is a side-effect.-- Eric Elliott
داخل بحث Non-idempotent + Side-effect-free نشان دادیم که هر I/O ای الزاما side-effect نیست.
Triggering any external process is a side-effect.-- Eric Elliott
باز هم داخل Non-idempotent + Side-effect-free نشان دادیم که اینطور نیست.
Retrieving the value from outside the function scope is side-effect.-- Igor Wojda
جمله بالا چیزی جز چرند نیست. در درجه اول باید اشاره کنم خواندن immutable value از خارج از scope نه حامل side-effect است و نه تضادی با idempotency خواهد داشت.
1const x = 'Hello World'23// Pure Function4function f() {5 return x6}
اشتباه دوم این جمله را خودتان دیگه میتونید متوجه بشید! حتی اگر یک mutable value را از خارج scope بخونید باز هم دچار side-effect نشدید بلکه idempotency را نقض خواهید کرد و به دنبال آن purity را از دست خواهید داد.
ختم کلام
اگر دقت کنید تمامی اشتباهات بالا که بهش اشاره شده یک الگوی ثابت دارند و آن چیزی نیست جز خلط شدن سه مفهوم side-effect و idempotency و pure function. امیدوارم به تفاوت این سه مفهوم پی برده باشید و کمک کنید تا سایر افراد در آینده دچار این تناقض و کجفهمی نشده و یا اگر شدند آنها را اصطلاح کنید.
به خاطر داشته باشید که هیچ انسانی معصوم نیست و من هم از این قاعده مستثنی نیستم. لذا برای پیدا کردن مفاهیم درست به انسانها ارجاع نکنید. تجربه به من ثابت کرده که ترکیب ریاضیات و منطق شما را از چنین مخمصههایی به راحتی نجات خواهند داد.