ساید‌افکت را درست درک کنیم!

September 11th, 2020 · 4 min read

امان از دست کلمات و اصطلاحات قلبمه و سملبه‌ی 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.

-- David A. Spuler and et al.

نگران نباشید! این تعاریف برای دوستداران منبع ذکر شده. تمامی تعاریف دارند به یک مفهوم ساده اشاره می‌کنند. کلمه side-effect یعنی شما یک چیزی را در یک جایی از این جهان فاسد تغییر بدید! به شکلی که تغییر ایجاد شده قابل رویت یا تاثیرگذار باشه!

می‌خواید متوجه بشید که آیا یک عملیات side-effect داره یا خیر؟ دو شرط پایین را به خاطر بسپارید.

اولا: Mutation

جدی عرض میکنم! شما باید واقعا یک تغییر ایجاد کنید! بدون تغییر نمی‌تونید ادعای داشتن side-effect کنید.

به مثال‌های زیر توجه کنید:

  • نوشتن یک فایل روی دیسک
  • پاک کردن user
  • نوشتن یک بلاگ پست جدید
  • آغاز جنگ جهانی سوم
  • خودکشی

در هر کدام از موارد فوق شما یک تغییری در جهان اطراف ایجاد کردید، پس اولین شرط لازم به‌وجود آوردن یک side-effect یعنی mutation را دارید.

دوما: Observable

به مثال زیر توجه کنید:

1function f() {
2 let x = 0
3
4 ++x
5 ++x
6
7 x = 1
8
9 return x
10}

در فانکشن f ما سه مرتبه مقدار x را تغییر دادیم (mutation) ولی مقدار x در خارج از فانکشن f قابل رویت نیست. یعنی تغییرات ایجاد شده هیچ تاثیری در جهان ایجاد نکردند.

حالا دلیل انتخاب کلمه effect را درک می کنید. هر side-effect باید تاثیری بر سایر موجودیت‌ها اعمال کنه.

Idempotency (A.K.A. Determinism)

فانکشن + را در نظر بگیرید. اهمیتی نداره که آن را چندبار صدا کنید یا در چه زمانی از روز صدا کنید. مادامی که ورودی یکسانی به فانکشن + بدید خروجی یکسانی خواهید گرفت.

12 + 2 = 4
2
3-- 2000 years later
4
52 + 2 = 4

فانکشن idempotent یا deterministic همواره به ازای یک ورودی یکسان یک خروجی یکسان به ما خواهد داد.

در مقابل آن فانکشن non-idempotent یا non-deterministic قرار گرفته که امکان داره به ازای ورودی یکسان خروجی غیر‌یکسان به ما بده.

1Math.random() // 0.33882779821654285
2Math.random() // 0.8528583771665288
3Math.random() // 0.76382461906862

رابطه side-effect و idempotency

لازمه که دقت کنید دو مفهوم side-effect و idempotency هیچ ارتباطی به هم ندارند. یعنی از روی یکی نمی‌تونیم دیگری را اثبات کنیم.

relation of side-effect and idempotency.

تصویر بالا تفاوت این دو مفهوم و دلیل عدم ارتباطشان را به شما نشان میده. چیزی که باعث تاثیر روی رفتار فانکشن ما میشه و idempotency را نقض می‌کنه با side cause نشان داده شده. تاثیری که فانشکن ما بر جهان میذاره را با side-effect نمایش داده. پس از شما خواهش میکنم برای این دو مفهوم تفاوت قائل بشید.

بر اساس idempotency و side-effect می‌تونیم فانکشن‌ها را به چهار دسته تقسیم کنیم:

Side-effect-freeIdempotent
Pure
Impure - side-effectful
Impure - non-idempotent
Impure - side-effectful + non-idempotent

Pure Function (A.K.A. Referential Transparent)

pure function and referential transparency.

فانکشن‌هایی که idempotent هستند و side-effectای ندارند را pure خطاب می‌کنیم. این فانکشن‌ها مثل فانشکن‌های ریاضی عمل میکنند و برای ما بسیار ارزشمندند. چرا؟ چون بسیار خوانا هستند، تست کردنشان فوق‌العاده ساده است و امکان نداره که رفتار‌های عجیب و غریب ازشان ببینیم.

Impure Function (A.K.A. Referential Opaque)

impure function and referential opacity.

اگر فانکشن ما idempotent نباشه و/یا side-effect داشته باشه جزو impureها قرار میگیره. در عمل ما اهمیتی نمیدیم که چرا یک فانکشن impure شده و همه را داخل یک دسته قرار میدیم. در واقع فانکشن اسم درستی برای اینها نیست و باید با عنوان procedure صداشون کنیم. از procudure ها انتظار هرگونه رفتار عجیب و غریب و وحشیانه‌ای را داشته باشید.

Non-idempotent + Side-effect-free

non-idempotent function.
1let x = 0
2
3function getX() {
4 return x
5}
6
7function now() {
8 return Date.now()
9}
10
11function 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-effect
3}

نظرتان درباره فانکشن fetchUser چیه؟ فکر می‌کنید side-effect داره؟ با توجه به اینکه می‌دونیم در سمت endpoint هیچ side-effect ای اجرا نمیشه این فانشکن هم همچنان هیچ side-effect ای نداره و صرفا non-idempotent به حساب میاد.

پس اگر صرفا خواندن از منابع تغییر‌پذیر صورت بگیره شما side-effect نخواهید داشت بلکه دچار نقض idempotency شده‌اید.

These terms have definitions.

-- Mohammad Hasani

Idempotent + Side-effectful

side-effectful function.
1function f() {
2 API.startWW3()
3
4 return 1
5}

حدس بزنید با صدا زدن این فانکشن چه اتفاقی رخ خواهد داد؟! پر واضح که side-effect خواهد داشت ولی همانطور که میبینید این فانکشن همواره یک خروجی برمیگردونه و این یعنی idempotent هست.

Non-idempotent + Side-effectful

non-idempotent side-effectful function.
1function f(data) {
2 return API.postUser(data) // POST /users/:id - register new user
3}

توضیح واضحات خواهد بود اگر کلامی بگم.

تعاریف نادرست رایج از 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'
2
3// Pure Function
4function f() {
5 return x
6}

اشتباه دوم این جمله را خودتان دیگه می‌تونید متوجه بشید! حتی اگر یک mutable value را از خارج scope بخونید باز هم دچار side-effect نشدید بلکه idempotency را نقض خواهید کرد و به دنبال آن purity را از دست خواهید داد.

ختم کلام

اگر دقت کنید تمامی اشتباهات بالا که بهش اشاره شده یک الگوی ثابت دارند و آن چیزی نیست جز خلط شدن سه مفهوم side-effect و idempotency و pure function. امیدوارم به تفاوت این سه مفهوم پی برده باشید و کمک کنید تا سایر افراد در آینده دچار این تناقض و کج‌فهمی نشده و یا اگر شدند آنها را اصطلاح کنید.

به خاطر داشته باشید که هیچ انسانی معصوم نیست و من هم از این قاعده مستثنی نیستم. لذا برای پیدا کردن مفاهیم درست به انسان‌ها ارجاع نکنید. تجربه به من ثابت کرده که ترکیب ریاضیات و منطق شما را از چنین مخمصه‌هایی به راحتی نجات خواهند داد.

خبرنامه

با عضویت در خبرنامه، می‌تونی از آخرین مطالب من از طریق ایمیل مطلع بشی و همواره امکان لغو عضویت را خواهی داشت.

پیشنهاد مطالعه

تایپ‌های ناشایسته

این پست اولین بخش از سری پست‌های «طریقت یک FP…

اگر If/Else را Abstract کنیم چی؟

می‌خوام شما را با اصطلاحی آشنا کنم تحت عنوان ontogeny recapitulates phylogeny…