5- کاتلین از ابتدا : بررسی عمیق‌تر توابع

توی قسمت قبلی در مورد پکیج‌ها و توابع پایه در کاتلین یاد گرفتیم. توابع قسمت مهمی در کاتلین هستند، به همین خاطر توی این قسمت نگاه عمیق‌تری بهشون میندازیم. ما انواع زیر از توابع رو در این قسمت بررسی می‌کنیم :

  • top-level functions
  • lambda functions or function literals
  • anonymous functions
  • local or nested functions
  • infix functions
  • member functions

شما حتما بعد از فهمیدن قابلیت‌های کاتلین شگفت‌زده خواهید شد.

1- ا Top-Level Functions

توابع top-level توابعی هستند که با وجود اینکه اونا داخل یک پکیج قرار دارن، خارج از هرگونه کلاس، آبجکت یا اینترفیسی هستن. این معنیش اینه که توابعی هستند که اونا رو به طور مستقیم و بدون نیاز به ساخت آبجکت یا فراخوانی هیچ کلاسی، می‌تونید فراخوانی می‌کنید.

به احتمال قوی، اگه توی جاوا کد زده باشید، حتما می‌دونید که متدهای utilityی استاتیک رو معمولا داخل کلاس‌های helper تعریف می‌کنن. در حقیقت این کلاس‌ها کار خاصی انجام نمیدن - داخلشون دستور یا instanceای از متدهای دیگه وجود نداره، وظیفشون فقط نگهداری متدهای استاتیک هست.یک مثال معمول، همین کلاس Collections داخل پکیج java.util و بقیه‌ی متدهای استاتیکش هست.

توی کاتلین، توابع top-level میتونن به عنوان جایگزینی برای متدهای utility استاتیکی که توی جاوا داخل کلاس‌های helper ازشون استفاده می‌کردیم، قرار بگیرن. حال به کد پایین که داخلش یک تابع top-level تعریف شده یه نگاهی بندازید.

https://gist.github.com/sajjadyousefnia/adff2c989426ca7220d6284a3241c27e

توی کد بالایی، ما یک پکیجی به اسم com.chikekotlin.projectx.utils رو داخل فایلی به نام UserUtils.kt تعریف کردیم، و داخل همون فایل یک تابع top-level به اسم ()checkUserStatus رو هم تعریف کردیم. این یک مثال خیلی ساده هست که یک رشته رو return میکنه.

کار بعدی‌ای که می‌خوایم انجام بدیم، استفاده از این تابع utility توی یک پکیج یا فایل دیگه هست.

https://gist.github.com/d6bea917cb83e85a8a5535c86bb63cbb.git

توی کد قبلی، ما تابع رو از یک پکیج دیگه import کردیم و اجراش کردیم! همونطور که می‌بینید، برای فراخوانی این تابع مجبور به ساخت یک آبجکت یا ارجاع دادن یک کلاس نیستیم.

تطابق پذیری با جاوا

جاوای فعلی توانایی پشتیبانی از توابع top-level رو نداره، در حقیقت کامپایلر کاتلین یک کلاس جاوایی می‌سازه و هر تابع top-level تبدیل به متدهای استاتیک میشه. توی قسمت قبلی، UserUtilsKt کلاس جاوایی تولید شده با متد استاتیک checkUserStatus بود.

https://gist.github.com/sajjadyousefnia/d8b596b9d7ec41d6c32c10ed86c15b83

این معنیش اینه که توی جاوا میشه به سادگی یک متد رو از کلاس تولید شده فراخوانی کرد، درست مثل هر متد استاتیک دیگه.

https://gist.github.com/sajjadyousefnia/0c5d465f8b0d30d0b06a0c5fa5708b71

دقت کنید که ما می‌تونیم اسم کلاس جاوایی، که داخل کاتلین تولید و استفاده میشه رو با استفاده از انوتیشن Jvname@ تغییر بدیم، لطفا به کد پایینی دقت کنید.

https://gist.github.com/sajjadyousefnia/16ba227319cc8fa500a6a6777209d688

توی کد بالایی، ما انوتیشن JvmName@ رو اضافه کردیم و کلاسی به اسم UserUtils رو داخل اون فایل به وسیله‌ی همون انوتیشن تعیین کردیم. دقت کنید که این انوتیشن ابتدای فایل کاتلینی و قبل از تعریف پکیج نوشته میشه.

توی جاوا مشابه کد پایینی می‌تونیم فراخوانیش کنیم:

https://gist.github.com/sajjadyousefnia/817656aed11b55a552670561e42fa21f

2- توابع لامبدایی ( Lambda functions)

ویژگی Lambda functions(یا function Literals ) اینه که با یک Entity خاص ( مثلا یک کلاس، آبجکت یا یک اینترفیس خاص) محدود نمیشن. در ضمن میتونن به عنوان آرگومان به توابع دیگه فرستاده بشن، که در این صورت بهشون توابع higher-order میگیم ( در قسمت بعدی در موردش بیشتر بحث می‌کنیم). یک Lambda function صرفا به صورت یک بلوکی از کد هست، و باعث میشه که شلوغی توی کدهامون به حداقل برسه.

اگه به احتمال زیاد جاوا کار کرده باشید حتما میدونید که JAVA 8 و نسخه‌های بعدیش لامبدا رو ساپورت میکنن. ولی برای نسخه‌های قیدیمیش که از لامبدا ساپورت نمیکنن باید از کتابخونه‌ی Retrolambda استفاده کنید.

برای حل مشکل پشتیبانی نکردن جاوای 6 و 7 از لامبدا موقع interoperate کردن یا تطبیق دادن بین این دو زبان، کاتلین به صورت ناملموس و پشت پرده، یک کلاس anonymous جاوایی رو ایجاد میکنه. ولی دقت کنید که ساخت یک lambda function کاملا متفاوت با جاوا هست.

پایین چند تا از ویژگی‌های یک lambda function توی کاتلین رو گفتم:

  • حتما بایستی با استفاده از {} محصور بشه.
  • از کلمه‌ی کلیدی fun استفاده نمیکنه.
  • از access modifier ها ( یعنی private و public و protected ) در اون استفاده نمیشه، دلیلش اینه که به هیچ کلاس، آبجت و یا اینترفیسی تعلق نداره.
  • تابع لامبدایی اسم نداره یا به قولی anonymous یا همون ناشناس هست.
  • جنس return داخلش مشخص نشده، چون جنس به وسیله‌ی کامپایلر تفسیر میشه.
  • پارامترها به وسیله‌ی پرانتز محصور شدن.

ساخت lambda exprssion

حالا وقتشه که یک نگاهی به چند نمونه از lambda function بندازیم. توی کد پایینی ما یک lambda function بدون پارامتر ساختیم و اونو به متغییر assign ،mesage کردیم ( یا همون برابر قرار دادن ). بعدش این lambda function رو با فراخوانی ()message اجرا کردیم.

https://gist.github.com/sajjadyousefnia/b5387dd8bb51fc5806d41a18f35fdf44

حالا، ببیینیم که اگه پارامترها را داخل lambda function بگذاریم چجوری میشه.

https://gist.github.com/sajjadyousefnia/43838b3dab849f4d5a4ed15599d415f7

توی کد بالایی، ما یک lambda function رو با استفاده از پارامتر استرینگی myString ساختیم . همونطور که میبینید، جلوی جایی که باید جنس پارامتر قرار داده بشه، یک فلش هست، که لیست پارامترها رو از بدنه‌ی لامبدا جدا میکنه. برای خلاصه‌تر شدن، می‌تونیم جنس پارامتر رو به طور کامل نادیده بگیریم و اونو ننویسیم.

https://gist.github.com/sajjadyousefnia/37656e51419a83c9dfa3dca4a1092ca3

وقتی که چند تا پارامتر داریم، می‌تونیم اونا رو با کاما جدا کنیم. و یادتون باشه که مثل جاوا لیستی از پارامترها رو داخل یک پرانتز ننویسید.

https://gist.github.com/sajjadyousefnia/33c184e511ce43a872dbcb4a3e23365c

اما یادتون باشه که در این صورت کامپایلر نمیتونه جنس پارامترها رو خودش اتوماتیک تفسیر کنه، و جنس داده‌ها رو باید به طور واضح اعلام کرد ( مثل نمونه ) چون در غیر این صورت کد کامپایل نمیشه.

https://gist.github.com/sajjadyousefnia/903c8d9a44a9657e8c64b35daf95d16b

فرستادن لامبداها به توابع

ما می‌تونیم lambda function ها رو به عنوان پارامتر به توابع بفرستیم، که به این توابع میگن : higher-order functions ( یعنی توابع دارای اولویت بالاتر ) چون که اونا خودشون یک تابع هستن که متعلق به یک تابع دیگه هستن: برای مثال ()last توی تابع collection

توی کد پایینی، ما یک lambda function رو به تابع ()last فرستادیم. همونطور که از اسمش معلومه، آخرین عنصر لیست رو برمی‌گردونه. last یک lambda function رو به عنوان یک پارامتر دریافت میکنه و یک آرگومان از جنس String رو تولید میکنه. یعنی کار این lambda function اینه که آخرین عنصر رو میتونه از دل یک لیست دیگه بکشه بیرون.

https://gist.github.com/sajjadyousefnia/0db3e8fa266d7d5b45649fb22d361411

حالا بار میتونیم یک حرکت دیگه‌ای انجام بدیم که باعث خواناتر شدن و خلاصه‌تر شدن کد بشه.

https://gist.github.com/sajjadyousefnia/12db1e5f44ff0d1ae5679d54fb642b95

در صورتی که آخرین آرگومان یک تابع، یک lambda function باشه، کامپایلر کاتلین به ما اجازه میده که، پرانتزهای تابع رو حذف کنیم. همونطور که کد بالایی رو میبینید، ما تونستیم پرانتزها رو حذف کنیم، چون که این lambda function آخرین و تنها آرگومانی هست که تابع last میفرسته.

حتی کد رو با حذف جنس پارامتر میشه خلاصه‌تر هم کرد.

https://gist.github.com/sajjadyousefnia/93078a6a30896183a35e138a49f06e57

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

آرگومان it

ما می‌تونیم lambda function رو از طریق جایگزینی آرگومان it - که به طور خودکار تولید میشه و یک آرگومان default هم هست - ، به جای آرگومان lambda function ساده‌تر کنیم.

https://gist.github.com/sajjadyousefnia/49d43d1f246c733abca147743dcc4f4d

به این دلیل از آرگومان it استفاده شده که متد last میتونه یک lambda function یا یک anonymous function رو که تنها با یک آرگومان بپذیره، ( که به طور خلاصه‌وار بررسیش میکنیم ) و کامپایلر میتونه جنسش رو خودش تشخیص بده.

ه Return محلی در Lambda Functionها

بیایید با یک مثال شروع کنیم، توی کد پایینی، ما یک lambda function رو به ()foreach که بعد از کالکشن intList اومده، فرستادیم. این تابع کل عناصر کالکشن رو مرور میکنه و لامبدا رو روی هر عنصری که داخل لیست هستش، اجرا میکنه. در صورتی که یک عنصری رو پیدا کنه که بر 2 بخش پذیر باشه، حلقه رو متوقف میکنه و دستور return کردن از لامبدا رو اجرا میکنه.

https://gist.github.com/sajjadyousefnia/73ec60f8811fe96631dfde4c1afe6f90

اجرای کد بالایی احتمالا اون نتیجه‌ای رو که مورد نظر شما هست رو بهتون نداده. این به این خاطره که دستور return باعث نمیشه که از لامبدا خارج بشیم، بلکه باعث میشه که کلا از تابع ()surroundingFunction خارج بشیم! پس یعنی دیگه کلا دستورات توی این تابع surroundingFunction دیگه اجرا نخواهد شد.

https://gist.github.com/sajjadyousefnia/e3b720a32c75b54669a12c07b2238af5

برای حل این مشکل، لازمه که با استفاده از یک لیبل مشخص کنیم که میخوایم عمل return رو از کدوم قسمت انجام بدیم.

https://gist.github.com/sajjadyousefnia/2cd4063c8b1e484d5f96e276b1ef2f06

توی کد بالایی که یک مقداری تغییر کرده، تگ foreach@ رو داخل لامبدا و درست بعد از return قرار دادیم. با این کار ما به کامپایلر دستور میدیم که به جای این که ()surroundingFunction خارج بشه، از لامبدا خارج بشه. بعدش آخرین دستور متد ()surroundingFunction اجرا خواهد شد.

در ضمن دقت کنید که ما خودمون میتونیم لیبل رو تعریف کنیم.

https://gist.github.com/sajjadyousefnia/14b4c1c234d18a181bc14724b4115dab

توی کد بالایی، یک لیبل کاستوم شده به نام myLabel و بعدش به return معرفیش کردیم. لیبل @foreach که توسط کامپایلر تولیدشده، دیگه معتبر نیست چون بجاش از @myLabel استفاده کردیم.

3- ه Member Functions

احتمالا بیشتر افراد با این موضوع آشنایی دارن، این نوع از تابع داخل یک کلاس، اینترفیس یا object تعریف میشه. با استفاده از member functions می‌تونیم برنامه رو بیشتر ماژول‌بندی کنیم. حالا بیایم یه نگاهی به نحوه‌ی ساخت یک member function بندازیم.

https://gist.github.com/sajjadyousefnia/9fb92ba7424057adddf12837c6845264

توی قطعه کد بالا یک کلاس Circle رو می‌بینیم که یک member function به نام ()calculateArea داره. این تابع یک پارامتری به اسم radius رو برای محاسبه‌ی محیط دایره می‌گیره.

فراخوانی تابع به صورت زیر انجام شده که فکر میکنم لازم به توضیحات اضافی نیست.

https://gist.github.com/sajjadyousefnia/1944223c7e6efd32bf66860805357f3d

4- ( توابع ناشناس ) Anonymous Functions

استفاده از Anonymous function یک روشی هست که میشه در اون بلوکی از کد تعریف کرد که قابل فرستادن به تابعی دیگه باشه. هیچ نوع identifier برای اون استفاده نمیشه. و چند تا از ویژگی‌هاشو پایین می‌تونید ببینید :

  • هیچ اسمی نداره.
  • با کلمه‌ی کلیدی fun ساخته میشه.
  • ه body هم داره.
https://gist.github.com/sajjadyousefnia/e1c6f849163fde29159ff33eb7994f28

اگه به کد بالایی دقت کنید حتما متوجهید که نمی‌تونیم از return استفاده کنیم و به قولی برای ما اینجا return ملموس نیست و نمی‌بینیمش. دلیلش اینه که ما یک لامبدا رو به تابع last فرستادیم. حالا در صورتی که بخوایم از return استفاده کنیم و برامون ملموس‌تر بشه، بایستی که به جای لامبدا از anonymous function استفاده کنیم.

https://gist.github.com/sajjadyousefnia/0392700aeee703e06cff1b203d8e66bc

توی قطعه کد بالا، به جای لامبدا از anonymous funcion استفاده کردیم، چون میخوایم return قابل دیدن باشه و بشه دستکاریش کرد.

اواخر قسمت لامبدا، ما از لیبل استفاده کردیم تا مشخص کنیم که میخوایم از درون چه تابعی return انجام بشه. با استفاده از anonymous function به جای lambda، این مشکلمون حل میشه، و اینجوری میتونیم مشکل تابع foreach رو به روش ساده‌ای حل کنیم. عبارت return باعث میشه که ما از داخل anonymous function خارج بشیم و نه از اون متدی که اون رو محصور کرده، ایندفعه از ()surroundingFunction استفاده می‌کنیم.

https://gist.github.com/sajjadyousefnia/badefc68e3f25c7f395177ee863ab4ea

5- توابع local (محلی) یا nested ( تودرتو)

اگه بخوایم برنامه رو نسبت به قبل بیشتر ماژول‌بندی کنیم، بایستی از قابلیت جدید local function - که اسم دیگش nested function هست - استفاده کنیم. اصولا local function تابعیه که داخل یک تابع دیگه تعریف شده.

https://gist.github.com/sajjadyousefnia/a534fbaa93b9e48b399e61a894a50de5

همونطور که در بالا مشاهده می‌کنید، ما از دوتا single-line function استفاده کردیم، یعنی : calCircumference() و calArea() که هر دوشون داخل یک متد دیگه و به صورت تودرتو قرار دارن. یک نکته‌ای که وجود داره اینه که توی کد بالا nested functionهایی که تعریف شدن، داخل یک متد دیگه قرار دارن و اونا رو فقط میشه از طریق اون متد بیرونی‌شون فراخوانی کرد و اون متدهای تعریف شده قابلیت اینو ندارن که از بیرون فراخوانی بشن. بازم یه بار دیگه می‌تونیم با استفاده از nested functionها کدمون رو تروتمیزتر و کوتاه‌تر کنیم.

در صورتی که بیایم توی local function هامون نوع return رو اعلام نکنیم، کدمون، کوتاه‌تر میشه. این کار به این دلیل شدنیه که local function - که در بطن یک متد قرار داره - میتونه این نکته رو متوجه بشه که به قولی جنس داده‌ای رو که این متد return میکنه رو به عنوان چه نوع متغییری مورد استفاده قرار میده. به مثال پایینی توجه کنید:

https://gist.github.com/sajjadyousefnia/76840e1f99d455e825078b17e1e1f9b9

همونطور که مشاهده می‌کنید، این کد جدیدی که ایجاد کردیم هم خواناتره و هم این که موارد زائد یا به قولی نویز کمتری داره نسبت به قبل. پس کلا چیز باحالیه :)

6- ه Infix Functions

با استفاده از نمادگذاری infix می‌تونیم به راحتی member functionها یا extension functionهایی رو که فقط یک پارامتر دارن رو فراخوانی کرد. علاوه‌بر داشتن یک تابع تک‌پارامتره، لازمه که حتما از نماد infix هم استفاده کنیم. اصولا برای ساخت infix function از دوتا پارامتر استفاده می‌کنیم. پارامتر اول، پارامتر هدف ما هست و پارامتر دوم هم صرفا یک پارامتری هستش که به تابع ما فرستاده شده و یعنی دومی معمولا زیاد چیز خاصی نیست.

ساخت یک infix member function

حالا یه نگاهی به نحوه‌ی ساخت یک تابع infix در یک کلاس میندازیم. توی کد پایینی، ما یک کلاس Student رو به همراه یک یک instance field ایجاد کردیم. با گذاشتن کلمه‌ی کلیدی infix قبل fun، اون متد رو تبدیل به یک infix function کردیم. همونطور که در پایین می‌بینید یک infix function به اسم addKotlinScore رو ساختیم، که ان متد یک متغییر به اسم score رو دریافت میکنه و سپس اون رو به instance field مون، - یعنی kotlinScore - اضافه میکنه.

https://gist.github.com/sajjadyousefnia/5e0a3bce14c3ac862862b42091ee0389

فراخوانی یک infix function

حالا بیایم ببینیم چجوری میشه اون infix functionای رو که ساختیم، فراخوانی کرد. توی کاتلین برای فراخوانی یک infix function، لازم نیست که از dot یا همون نقطه استفاده کنیم و نیازی هم به استفاده از براکت یا پرانتز نداریم.

https://gist.github.com/sajjadyousefnia/884e0ee74bd1dabad18f24675b962782

توی کد بالایی ما infix functionمون رو فراخوانی کردیم، اینجا آبجکت هدفمون student و عدد 95.00 - که جنسش double هست - به تابع فرستاده میشه.

با استفاده از infix function می‌تونیم به طور هوشمندانه‌ای کدمون رو کوتاه‌تر و زیباتر کنیم. موقع نوشتن unit test برای کاتلین، این موضوع خیلی ملموس‌تر میشه.

https://gist.github.com/sajjadyousefnia/25fc8b1b535eedb3576037fc42761729

استفاده از to به عنوان infix function

توی کاتلین می‌تونیم به جای استفاده از کانستراکتور Pair، با استفاده از to به عنوان infix function ، ساختار instanceای که از pair می‌سازیم رو خلاصه‌تر کنیم. ( در حقیقت، to یک instance از Pair ایجاد میکنه ). دقت کنید که تابع to یک extension function هست. ( که بعدا در موردش بیشتر صحبت می‌کنیم)

https://gist.github.com/sajjadyousefnia/700b6d9fcb6e94e905c56f74d4977842

حالا شکل Pair رو در موقعی که از to استفاده میکنه با موقعی که مستقیما از کانستراکتور Pair استفاده میکنه، مقایسه می‌کنیم، در ضمن کاری که انجام میدن یکسان هست و خودتون متوجه خواهیدشد که کدوم یکی بهتره.

https://gist.github.com/sajjadyousefnia/c41d772dc1255143c477f2bde2e0bee4

همونطور که در بالا مشاهده می‌کنید، با استفاده از to - که یک infix function هست - کدمون نسبت به موقعی که از کانستراکتور Pair برای ایجاد یک instance از Pair استفاده می‌کنیم، کوتاه‌تر میشه. دقت کنید که وقتی از to استفاده کردیم، 234 آبجکت هدف هست و "Nigeria" پارامتری هست که به تابع فرستاده میشه. علاوه‌بر این میتونیم از روش کاملا مشابه زیر هم استفاده کنیم :

https://gist.github.com/sajjadyousefnia/9b4395c3c359d2b859bf14a23354be78

اگه یادتون باشه، توی پستی که در مورد ranges و collections نوشته بودم، یک map collection رو با استفاده از دو تا متغییر درست کردیم. حالا بیایم ببینیم که ساختن map با to چه فرقی با ساختنش با Pair داره.

https://gist.github.com/sajjadyousefnia/93055909cb130f67032bb058c1a6ed5b

توی کد بالایی، ما با استفاده از to یک لیست که از جنس Pair هستن و با کاما هم از هم جدا شدن، رو ساختیم و به تابع mapOf فرستادیم. همین کار رو میشه با استفاده از Pair هم انجام داد.

https://gist.github.com/sajjadyousefnia/25ff5286fdd87d4d2f078c40e812adb9

همونطور که می‌بینید، استفاده از to به جای Pair باعث میشه که کدمون چشم‌نوازتر بشه.

نتیجه گیری

توی این آموزش، شما متوجه شدید که توی کاتلین چه کارهای باحالی رو میشه با استفاده از توابع انجام داد. ما توی این قسمت موارد زیر رو بررسی کردیم :

  • top-level functions
  • lambda functions or function literals
  • member functions
  • anonymous functions
  • local or nested functions
  • infix functions

لطفا اگه نظری دارید یا مشکلی وجود داره بفرمایید.