پیاده سازی تابع کاری (Curry) در جاوا اسکریپت — از صفر تا صد
شاید از دیدن عنوان این مقاله شگفتزده شده باشید و از خود بپرسید تابع کاری دیگر چیست و یا این که آیا ما تابع غیر کاری هم داریم؟ اما منظور ما از «کاری» (Curry) در عنوان این مقاله، اشاره به فردی است که این نوع تابع را نخستین بار ابداع کرده است. البته احتمالاً این توضیح نیز به جای روشنتر شدن موضوع موجب افزایش سردرگمی شما شده است. بنابراین با ما همراه باشید تا ببینیم منظور از تابع کاری چیست و روش پیادهسازی تابع کاری در جاوا اسکریپت به چه صورت است.
اگر با «برنامهنویسی تابعی» (Functional Programming) آشنا باشید، یا اگر یک توسعهدهنده جاوا اسکریپت یا «اسکالا» (Scala) باشید، احتمالاً با اصطلاح تابع کاری آشنا هستید. در ادامه این مقاله با مفهوم دقیق کاری کردن یک تابع، دلیل مفید بودن آنها و شیوه پیادهسازی یک تابع کاری ساده آشنا میشویم.
تابعهای کاری به افتخار ریاضیدان مشهور آمریکایی «هسکل بروکز کاری» (Haskell Brooks Curry) که روی شالوده ریاضیاتی برنامهنویسی تابعی کار کرده است چنین نامگذاری شدهاند.
بر اساس تعریف، «کاری کردن» (Currying) به فرایند تبدیل یک تابع با تعداد پارامتر یا آرگومان بیشتر به تابعی با آرگومانهای کمتر گفته میشود. تابعهای کاری، تابعهایی با مرتبه بالاتر هستند که یک تابع به عنوان ورودی میگیرند و تابعی بازگشت میدهند که آرگومانهای تابع ورودی را میگیرد و یا آن را فراخوانی کرده و نتیجه را بازگشت میدهد (اگر دست کم تعداد آرگومانها ارائه شده باشد) یا تابعی بازگشت میدهد که آرگومانهای باقیمانده را میپذیرد و همین طور تا آخر ادامه میدهد.
به تعداد آرگومانهایی که یک تابع انتظار دارد دریافت کند Arity گفته میشود. در جاوا اسکریپت متد function.length عدد Arity تابع را بازگشت میدهد. بنابراین، به بیان ساده زمانی که عدد Arity آرگومانها به یک تابع کاری شده ارائه نشود، تابع دیگری بازگشت میدهد که آرگومانهای باقیمانده تابع را میگیرد. به مثال زیر توجه کنید:
1function logMessage (backgroundColor, fontColor, message) {
2 let logger = chalk[backgroundColor][fontColor];
3 console.log(logger(message));
4}
ما یک تابع Logger داریم که سه آرگومان میگیرد. این آرگومانها شامل رنگ پسزمینه و رنگ فونت پیامی است که باید لاگ شود و همچنین رشته متنی پیام را شامل میشود. اگر بخواهیم پیامی را لاگ کنیم، به طور معمول میتوانیم به صورت زیر عمل کنیم:
توجه کنید که در هر دو مورد باید bgBlue را ارسال کنیم. این حالت تکراری به نظر میرسد، به خصوص اگر بخواهیم پیامها را با رنگ پسزمینه آبی در جاهای مختلف برنامه لاگ کنیم. در چنین مواردی میتوانیم از یک تابع کاری شده استفاده کنیم. فرض کنید یک تولیدکننده تابع کاری داریم. میتوانیم به صورت زیر عمل کنیم:
چنان که میبینید صرفاً یک بار bgBlue را ارسال میکنیم. اکنون میتوانیم از تابع جدید به نام logWithBlueBackground برای لاگ کردن پیامها با پسزمینه آبی استفاده کنیم. این یک کاربرد ساده کلوژر در جاوا اسکریپت است، اما تکنیک کاریسازی امکانات بیشتری ارائه میکند که در ادامه آن را بررسی میکنیم.
logWithBlueBackground یک تابع کاری نیز محسوب میشود، یعنی میتوانیم تابعهای دیگری با استفاده از این تابع بسازیم.
همچنین میتوانیم کارهایی مانند زیر برای به دست آوردن نتیجه مشابه انجام دهیم:
کاریسازی یک تکنیک قدرتمند است که میتواند در صورت استفاده صحیح، برای نوشتن کد منسجمتر و تمیزتر استفاده شود. بدین ترتیب میتوان بخشهای کوچکی از کد را چنان پیکربندی کرد که وظیفه خاصی انجام دهند و قابلیت استفاده مجدد نیز داشته باشند. اینک به موضوع اصلی این مقاله بازمیگردیم که شیوه پیادهسازی یک تابع کاریشده است. به مثال ساده زیر توجه کنید.
فرض کنید یک تابع ساده داریم که 4 عدد را با هم جمع میکند:
const add = (x, y, z, w) => x + y + z + w;
میخواهیم یک تولیدکننده تابع کاری (curryGenerator) ایجاد کنیم که این تابع را به عنوان آرگومان میگیرد و یک تابع کاریشده بازگشت میدهد.
const curriedFunction = curryGenerator(add); console.log(curriedFunction(1,2,3,4)); // prints 10 console.log(curriedFunction(1)(2)(3)(4)); //prints 10 console.log(curriedFunction(1,2)(3,4)); // prints 10 console.log(curriedFunction(1,2, 3)(4)); // prints 10 ...
اینک سؤال این است که چطور میتوان تابع curryGenerator را پیادهسازی کرد که همه این کارها را انجام دهد؟ پیشنهاد میکنیم ابتدا این موضوع را خودتان بررسی کنید، چون سؤال بسیار جالبی است و در مصاحبههای شغلی جاوا اسکریپت نیز غالباً پرسیده میشود.
در ادامه پیادهسازی بازگشتی تابع curryGenerator را میبینید:
1// Curry function generator implementation
2const curryGenerator = (fn) => {
3 const helper = ({func, args, prevArgs}) => {
4 if (args.length + prevArgs.length >= func.length) {
5 return func(...prevArgs, ...args);
6 }
7 return (...newArgs) => helper({
8 args: newArgs,
9 prevArgs: [...prevArgs, ...args],
10 func
11 });
12 }
13 return (...args) => helper({
14 func: fn,
15 args: args,
16 prevArgs: []
17 });
18}
در کد فوق یک تابع کمکی درون «تولیدکننده کاری» داریم که به صورت بازگشتی فراخوانی میشود. همچنان در مواردی که curriedFunction را فرامیخوانیم، تابع کمکی را نیز اجرا میکنیم. تابع کمکی ابتدا مجموع آرگومانهای ارائه شده و آرگومانهای قبلی را بررسی میکند. اگر این مجموع بزرگتر یا مساوی تعداد ورودیهای تابع باشد، این تابع با آرگومانهای ارائه شده فراخوانی میشود و نتیجه بازگشت مییابد. اما اگر تعداد آرگومانها کافی نباشد، تابع کمکی، تابع دیگری بازگشت میدهد که آرگومانهای کنونی به صورت prevProps به آن داده میشود. این فرایند تا زمانی که تعداد کافی از آرگومانها پیدا شوند، تداوم مییابد.
بدین ترتیب به پایان این مقاله با موضوع تابعهای کاری میرسیم. امیدواریم با مطالعه این راهنما مطالب جدید و مفیدی آموخته باشید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای جاوا اسکریپت
- مجموعه آموزشهای برنامهنویسی
- آموزش جاوا اسکریپت (JavaScript)
- آموزش جاوا اسکریپت — مجموعه مقالات جامع وبلاگ فرادرس
- معرفی جاوا اسکریپت ناهمگام — به زبان ساده
==