مفهوم Currying در جاوااسکریپت فرآیندی در برنامه نویسی فانکشنال است که در آن میتوانیم یک تابع با چندین آرگومان را به دنبالهای از توابع تودرتو تبدیل کنیم. به این ترتیب آن تابع، یک تابع جدیدی را return میکند که منتظر آرگومان بعدی میباشد.
به عبارت دیگر، یک تابع به جای اینکه همه آرگومانها را در یک زمان دریافت کند، آرگومان اول را میگیرد و تابعی را برمیگرداند که آرگومان دوم را دریافت میکند، سپس تابع جدیدی را برمیگرداند که آرگومان سوم را میگیرد و به همین ترتیب ادامه پیدا میکند تا زمانی که همه آرگومانها دریافت شوند. مثلا وقتی که تابع sum(1,2,3)
را به sum(1)(2)(3)
تبدیل میکنیم.
به تعداد آرگومانهایی که یک تابع میگیرد arity
نیز گفته میشود.
function sum(a, b) { // do something } function _sum(a, b, c) { // do something }
تابع sum
دو آرگومان (تابع ۲-arity) و _sum
سه آرگومان (تابع ۳-arity) دریافت میکند.
توابع Curried با زنجیرهای از closureها و با تعریف و return همزمان توابع داخلی آنها ساخته میشوند.
Currying به ما کمک میکند تا از استفاده مجدد و دوباره یک متغیر مشابه جلوگیری کنیم. این مفهوم همچنین به ایجاد یک تابع higher order کمک میکند. ما در مقاله توابع Higher Order در جاوااسکریپت این توابع را به شکل مورد بررسی قرار دادهایم.
Currying یک تابع با چندین آرگومان را به یک دنباله از توابع تبدیل میکند که هر کدام یک آرگومان واحد میگیرند. به عنوان مثال:
function sum(a, b, c) { return a + b + c; } sum(1,2,3); // 6
همانطور که می بینیم، در این مثال تابع هر سه آرگومان را باهم و در یک زمان دریافت میکند. در ادامه ورژنی از تابع را ایجاد میکنیم تا ببینیم چگونه میتوانیم یک تابع را در یک سری فراخوانی، فراخوانی کرده و همان نتیجه را بدست بیاوریم:
function sum(a) { return (b) => { return (c) => { return a + b + c } } } console.log(sum(1)(2)(3)) // 6
برای درک بهتر میتوانیم این مجموع (۱) (۲) (۳) را از هم جدا کنیم:
const sum1 = sum(1); const sum2 = sum1(2); const result = sum2(3); console.log(result); // 6
در ادامه نحوه کارکردن آن را بررسی میکنیم.
ما عدد ۱ را به تابع sum
ارسال کردیم:
let sum1 = sum(1);
تابع زیر را برمیگرداند:
return (b) => { return (c) => { return a + b + c } }
اکنون sum1
تعریف تابع بالا را دارد که آرگومان b
را میگیرد.
ما تابع sum1
را با تخصیص دادن مقدار ۲ فراخوانی میکنیم:
let sum2 = sum1(2);
sum1
تابع سوم را برمیگرداند:
return (c) => { return a + b + c }
حالا تابع return شده در متغیر sum2
ذخیره میشود.
sum2
اکنون به صورت زیر خواهد بود:
sum2 = (c) => { return a + b + c }
وقتی sum2
با پارامتر ۳ فراخوانی شود:
const result = sum2(3);
محاسبه را با پارامترهای قبلی انجام میدهد: a = 1
،b = 2
و مقدار ۶ را برمیگرداند.
console.log(result); // 6
آخرین تابع فقط متغیرc
را میپذیرد اما این عملیات را با متغیرهای دیگری که Scope تابع آنها return شده است انجام میدهد. با این وجود به دلیل Closure درست کار میکند.
اکنون ممکن است برخی به این موضوع فکر کنند که تعداد توابع تودرتو یک تابع Curried به تعداد آرگومانهایی که دریافت میکند بستگی دارد. بله، این موضوع آن را تبدیل به Curry میکند.
همان مثال sum
را در نظر میگیریم:
function sum(a) { return (b, c) => { return a * b * c } }
میتواند به شکل زیر فراخوانی شود:
let x = sum(10); x(3,12); x(20,12); x(20,13); // OR sum(10)(3,12); sum(10)(20,12); sum(10)(20,13);
تابع فوق انتظار دارد ۳ آرگومان دریافت کند و ۲ تابع تودرتو داشته باشد، برخلاف ورژن قبلی که انتظار دریافت ۳ آرگومان وجود داشت و ۳ تابع تودرتو ایجاد شد.
این ورژن Curry نیست. ما فقط یک Partial Application از تابع sum
انجام دادیم.
Currying و Partial Application به دلیل closure با هم مرتبط هستند، اما مفاهیم متفاوتی دارند.
Partial Application یک تابع را به تابع دیگری با arity کوچکتر تبدیل میکند.
function sum1(x, y, z) { return sum2(x,y,z) } // to function sum1(x) { return (y,z) => { return sum2(x,y,z) } }
این مثال برای Currying به صورت زیر خواهد بود:
function sum1(x) { return (y) = > { return (z) = > { return sum2(x,y,z) } } }
Currying با توجه به تعداد آرگومانهای تابع، توابع تودرتو ایجاد میکند. هر تابع یک آرگومان دریافت میکند. اگر آرگومانی وجود نداشته باشد، Curryingای نیز وجود نخواهد داشت.
برای توسعه تابعی که یک تابع را میگیرد و تابع Curried برمیگرداند به صورت زیر عمل میکنیم:
function currying(fn, ...args) { return (..._arg) => { return fn(...args, ..._arg); } }
تابع بالا یک تابع (fn)
که میخواهیم آن را اجرا کنیم و تعداد متغیر پارامترها (…args)
را دریافت میکند. عملگر rest برای جمعآوری تعداد پارامترها بعد از fn
در …args
مورد استفاده قرار میگیرد.
در مرحله بعد، تابعی را return میکنیم که بقیه پارامترها را نیز به صورت …_args
جمعآوری میکند. این تابع، تابع اصلی fn
را با استفاده از عملگر spread به عنوان پارامتر به …args
و …_args
ارسال میکند، سپس مقدار به کاربر return میشود.
اکنون میتوانیم از تابع بالا برای ایجاد تابع curry استفاده کنیم:
function sum(a,b,c) { return a + b + c } let add = currying(sum,10); add(20,90); // 120 add(70,60); // 140
Closure مفهوم Currying را در جاوااسکریپت امکانپذیر میکند. اگر با مفهوم Scope و closure آشنا نیستید مطالعه مقاله Scope و Closure در جاوااسکریپت میتواند بسیار مفید باشد.
در این مقاله سعی کردیم با مفهوم Currying در جاوااسکریپت آشنا شویم و نحوه عملکرد آن را دقیقتر یاد بگیریم.
دیدگاهها: