توابع در زبان برنامه نویسی دارت (Dart)
توابع یا function در زبان برنامه نویسی دارت (Dart) برای فریم ورک فلاتر Flutter ، موضوعی است که در این لحظه از تجاری اپ ما قصد داریم به آن بپردازیم. در این پست به کلیات تابع در زبان برنامه نویسی دارت Dart، پارامترهای توابع، توابع به عنوان اشیا first-class، توابع ناشناس (Anonymous functions)، Scope ها، مفهوم Lexical Closure و بازگشت مقادیر یا Return values در زبان برنامه نویسی دارت می پردازیم.
Functions یا توابع در تمامی زبان های برنامه نویسی یک معنا و مفهوم را دارد. می توان گفت توابع مانند کارخانه هایی هستند که ورودی هایی دریافت می کنند (و یا حتی می توانند دریافت نکنند) و درون خود عملیاتی انجام می دهند. و گاها پردازش های خاصی برروی ورودی ها اعمال می کنند و در نهایت خروجی را تحویل می دهند (حتی می توانند خروجی خاصی هم نداشته باشند).
در پست پیشین از سلسله آموزش های فلاتر در تجاری اپ گفتیم که اولین و اصلی ترین تابع درون Dart تابع main است که تمامی کد های ما باید درون این تابع قرار بگیرند.
main() {
}
به عبارت دیگر هر برنامه جهت اجرا شدن یک تابع main دارد، که به عنوان نقطه شروع برنامه عمل می کند. تابع main از نوع void است و یک آرگومان اختیاری <List<String دارد.
یک مثال ساده از function:
ما می خواهیم درون تابع main یک تابع دیگر تعریف کنیم. برای مثال قصد داریم تابعی که وظیفه جمع کردن دو مقدار را دارد به این صورت پیاده کنیم.
main() {
sum(){
print(3 + 4);
}
}
نکته: زبان برنامه نویسی Dart یک زبان تماما شی گراست. توابع در زبان برنامه نویسی Dart به نوعی هم شئ هستند و هم نوع دارند. به این معنا که توابع در زبان برنامه نویسی Dart می توانند به یک متغیر اختصاص داده شوند و یا به عنوان آرگومان به تابع دیگری پاس داده شوند.
نکته حائز اهمیت در زبان برنامه نویسی Dart و در بخش توابع این است که شما می توانید توابع تک خطی را با علامت => به صورت کوتاه تر نیز بنویسید.
Sum() => print(3+4);
در تابع sum ما از دستور print استفاد کردیم و انتظار داریم مجموع دو عدد 3 و 4 را برای ما نمایش دهد. و طبیعتا هم این عملیات به درستی انجام می شود. اما درصوتی که ما تابع sum را فراخوانی کنیم. چرا؟ خب ما تابعی مشخص کرده ایم که دو مقدار را جمع می کند و در نهایت print می کند اما جایی این تابع را جهت اجرا فراخوانی نکرده ایم. پس کد فوق را به این صورت تغییر می دهیم.
main() {
sum(){
print(3 + 4);
}
sum();
}
حال اگر در Editor مخصوص Dart دکمه run را بزنید خروجی که عدد 7 است را خواهید دید.
توجه: شما هم می توانید برای تمرین های ساده با زبان برنامه نویسی Dart به Editor آنلاین وب سایت اصلی زبان برنامه نویسی دارت مراجعه کنید.
اکنون تصور کنید که می خواهیم این اعداد 3 و 4 را به عنوان پارامتر به متد خود ارسال کنیم.
main() {
sum(firstVal , secondVal ){
print(firstVal+secondVal);
}
sum(2, 3);
}
هر تابع در زبان دارت یا Dart می تواند هر تعداد پارامتر الزامی یا اختیاری داشته باشد.
پارامترهای هر تابع می تواند به صورت اختیاری و اجباری تعریف شود. برای تعریف پارامترها به صورت اختیاری، تابع را به صورت زیر تعریف می کنیم.
sum({int firstVal=0 , int secondVal=0 })
همانطور که مشاهده می کنید پارامتر ها درون { } قرار می گیرند و مقدار اولیه دارند. حال برای ارسال مقدار زمان فراخوانی این تابع تنها کافی است به صورت زیر عمل کنیم.
prarmName: value
یا
paramName = value
برای مثال:
sum(firstVal: 2 , secondVal: 3);
حتی می توان تنها یک آرگومان را ارسال کرد چرا که پارامتر ها به صورت اختیاری تعریف کرده ایم.
void main() {
sum({int firstVal=0 , int secondVal=0 }) => print(firstVal+secondVal);
sum(firstVal: 3);
}
نکته دیگر در مورد پارامتر های تابع در زبان برنامه نویسی Dart، اجباری کردن یک یا چند پارامتر می باشد. اگر ما بخواهیم یکی از این دو پارامتر را ( که هر دو به صورت اختیاری تعریف کرده ایم) اجباری کنیم تنها کافی است از کلمه کلیدی required به صورت زیر استفاده کنیم.
sum({int firstVal=0 ,required int secondVal })
حال زمان فراخوانی این تابع حتی اگر پارامتر firstVal را هم ارسال نکنیم اما باید پارامتر secondVal ارسال شود.
sum(secondVal: 3);
پس در نتیجه نام گذاری پارامتر ها یک امر اختیاری است. مگر زمانی که پارامتر تابع را به صورت مشخص، اختیاری یا اجباری تعریف کرده باشیم.
تا به حال در تابع sum ما تنها نام پارامتر را مشخص کردیم و هیچ data type خاصی برای آن ها در نظر نگرفته ایم. به همین خاطر می توانستیم هر نوع مقداری را به آن پاس دهیم. اما اگر بخواهیم آرگومان خود را محدود به یک data type خاص کنیم باید نوع پارامتر را دقیقا مشخص کنیم.
main() {
sum(String firstVal ,String secondVal ) => print(firstVal+secondVal);
sum("tejariapp", ".com");
}
همانطور که می بینید ما مستقیما مشخص کرده ایم که پارامتر های ما باید از نوع String باشد و نوع دیگری را قبول نمی کند. حال اگر ما به این تابع پارامتری از نوع دیگر مثلا int پاس دهیم با خطای زیر مواجه خواهیم شد
The argument type ‘int’ can’t be assigned to the parameter type ‘String’
همانطور که پیش تر اشاره شد ما در زبان برنامه نویسی دارت می توانیم توابع را به عنوان پارامتر به تابع دیگری پاس دهیم. برای مثال:
void main() {
sum({int firstVal=0 , int secondVal=0 }) => print(firstVal+secondVal);
int populateFirstVal(int someVal) => someVal + 1;
sum(firstVal: populateFirstVal(4));
}
خروجی تابع sum که print خواهد شد عدد 5 است.
ما تابع populateFirstVal را به عنوان آرگومان به تابع sum ارسال کردیم که خروجی این تابع عدد 5 خواهد بود (زیرا عدد 1 به اضافه 4 می شود). و در نهایت خروجی sum عدد 5 است. چرا که ما پارامتر secondVal را پاس نداده ایم و از مقدار اولیه آن که 0 است استفاده می شود.
یک تابع می تواند نام گذاری شود مانند main و یا توابعی که تا به حال دیدید یا ایجاد کردید. اما توابع در زبان برنامه نویسی دارت می توانند بی نام باشند که در این صورت به آنها توابع ناشناس (Anonymous functions) می گویند.
توابع ناشناس همانند توابع معمولی می توانند انواع پارامتر ها را بگیرند و یا بدون پارامتر باشند. به صورت lambda و یا حتی closure تعریف شوند.
شما می توانید توابع ناشناس را به variable ها اختصاص دهید. برای مثال می توانید توابع ناشناس ( Anonymous functions ) را به یک متغیر از جنس List اختصاص دهید و آیتمی را به آن اضافه یا از آن حذف کنید.
const list = ['tejariapp.com', 'flutter', 'tutorials'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
همچنین می توانیم مثال فوق را کوتاه تر کنیم زیرا دستور اجرایی ما تک خطی است.
void main() {
const list = ['tejariapp.com', 'flutter', 'tutorials'];
list.forEach((item) => print('${list.indexOf(item)}: $item'));
}
محدوده تعریف هر متغیر یا تابع و غیره در زبان برنامه نویسی Dart استاتیک است و به مکان تعریف آن بستگی دارد. مانند اکثر زبان های برنامه نویسی دیگر برای مثال اگر شما متغیری درون یک تابع خاص تعریف کنید، دیگر دسترسی به آن متغیر خارج از آن تابع نخواهید داشت.
مثال:
bool topLevel = true;
void main() {
var insideMain = true;
void myFunction() {
var insideFunction = true;
void nestedFunction() {
var insideNestedFunction = true;
}
}
دسترسی هر متغیر را از نام متغیر تعریف شده می توان دریافت. variable یا متغیر topLevel در توابع nestedFunction()، myFunction() و main() قابل دسترس است. متغیر insideMain در تابعی که در آن تعریف کردیم و توابع myFunction() و nestedFunction() قابل دسترس است. متغیر insideFunction در تابعی که در آن تعریف شده و در تابع nestedFunction() قابل دسترس است. همچنین متغیر insideNestedFunction نیز تنها در تابع nestedFunction() قابل دسترس است.
Closure یک تابع است که به variable های درون scope خود دسترسی دارد حتی زمانی که خود تابع در خارج از scope اصلی خود تعریف شده است.
مثال:
Function makeAdder(int addBy) {
return (int i) => addBy + i;
}
void main() {
var add = makeAdder(2);
print(add(3));
}
ما یک تابع makeAdder داریم که یک پارامتر ورودی addBy دارد اما این تنها ورودی تابع makeAdder نیست. درواقع با اختصاص دادن تابع makeAdder به متغیر add ما متغیر add را تبدیل به یک Function کرده ایم که یک پارامتر int نیز دارد.
در ادامه ما به add عدد 3 را پاس داده ایم و پس از اجرا در خروجی عدد 5 را خواهیم دید. چرا که makeAdder مقدار i که برابر با 3 است به اضافه addBy که برابر با 2 می باشد، کرده است.
در زبان برنامه نویسی دارت توابع می توانند هر نوع خروجی داشته باشند که در صورت داشتن نوع خروجی خاص، تابع حتما باید یک مقدار از نوع خروجی مشخص شده، return کند اما در صورتی که خروجی void بود، تابع null برمی گرداند و نیاز به بازگشت مقدار خاصی نیست.
به تابع زیر دقت کنید:
sum({int firstVal=3 , int secondVal=0 }) => print(firstVal+secondVal);
برای تابع sum هیچ نوع خروجی تعریف نشده است پس در نتیجه خروجی از نوع void است.
int sum({int firstVal=3 , int secondVal=0 }) => firstVal+secondVal;
اما هم اکنون sum خروجی از نوع int دارد و مقداری هم که return می کند از نوع int است.
دیگر مقالات در زمینه آموزش فلاتر یا Flutter و زبان برنامه نویسی Dart