Android Corner
Android Corner
خواندن ۷ دقیقه·۱ سال پیش

اسکوپ فانکشن های کاتلین



اگه یادتون باشه، ما توی ماژول buildSrc از scope فانکشن with استفاده کردیم. امروز میخوایم scope function ها رو بررسی کنیم و ببینم چطور با استفاده از اینا به کدی تمیز میرسیم.

کتابخونه استاندارد Kotlin پنج تا فانکشن داره که بهشون میگن Scope function که هدفشون ایجاد یه محدوده از یه object هستش. وقتی که اینا رو روی یه آبجکت call کنین با lambda، اینا یه محدوده موقت از اون آبجِکته رو براتون میسازن که دیگه شما میتونین بدون نام اون به شیء دسترسی پیدا کنین. این پنج تا فانکشن: let، run، with، apply و also اینا هستن.

اصولاً ، این فانکشن ها یه کاری رو انجام میدن: یه بلاک کد رو رو یه شی اجرا میکنن، ولی تفاوتشون اینه که چطوری شیء رو در داخل بلوک(block) در دسترس قرار می دن و نتیجه expression چیه.

ببینید scope fun ها درسته که کد شما رو میتونن مختصر تر کنن، ولی نباید بیش از حد از اینا استفاده کنیم، این میتونه خوندن کد شما رو سخت کنه و منجر به خطا شه. همچنین توصیه میشه از این توابع بصورت تو در تو استفاده نکنید و هنگام زنجیر زدن شون مراقب باشید چون به راحتی می میتونید در مورد context object فعلی شون و مقادیر it و this اشتباه بگیرید.


قدم اول فرق شون رو بررسی میکنیم و بعد با مثال، نحوه استفاده شون رو میبینیم.

تفاوت ها

از اونجاییکه scope فانکشن ها از نظر ماهیت شبیه هم هستن، درک تفاوت بینشون مهمه. دو تفاوت اصلی بین هر scope فانکشن وجود داره:

1- نحوه ای که اونا به context object اشاره میکنن.

2- مقدار برگشتی شون.

معنی context object: همون آبجکتی که scope فانکشن رو روش فراخوانی میکنیم.

یک: context object

توی لامبدایِ scope فانکشنمون، بجای اینکه اسم context object مون رو هی تکرار کنیم میایم از it و this استفاده میکنیم؛ حالا هر scope فانکشنی نحوه ارجاع دادنشون به scope فانکشن متفاوته: یه lambda receiver  از this و یه lambda argument از it استفاده میکنه.

اولی: this

اسکوپ فانکش های run، with و apply کانتکس آبجکتشون رو بعنوان یه lambda receiver ارجاع میدن با واژه this.

داشتن context object بعنوان یه receiver (this) برای lambda هایی پیشنهاد میشه که عموما با member های آبجکت سروکار دارن.

val adam = Person(&quotAdam&quot).apply { age = 20 // same as this.age = 20 city = &quotLondon&quot } println(adam)

دومی: it

اسکوپ فانکش های let و also کانتکس آبجکتشون رو بعنوان یه lambda receiver ارجاع میدن با واژه it.

این مثل this نیست که صریحا به مثلا property های context object مون دسترسی داشته باشیم. ولی بهتره وقتی که از شیء بیشتر به عنوان آرگومان در تماس های تابع استفاده کردیم. همچنین این بهتره وقتی که شما از چندین متغیر در بلوک کد تون استفاده می کنید.

fun getRandomInt(): Int { return Random.nextInt(100).also { writeToLog(&quotgetRandomInt() generated value $it&quot) } } val i = getRandomInt() println(i)

دو: مقدار برگشتی

یک: apply و also خود "context object" رو برمیگردونن.

دو: let، run و with "نتیجه lamda" رو بر میگردونن.

** کانتکست آبجکت(context object)

چون مقدار برگشتی Apply و also خود خود context object هستش. میتونید اونها رو توی زنجیره فراخوانی(call chain) صدا بزنید: می توانید یکی پس از دیگری تماس های عملکرد زنجیره ای را در همان شیء ادامه دهید.

Code

ببینید، اول ما یه لیست خالی داریم و پاسش میدیم به also و یه message پرینت میکنه و لیست رو پاس میده به apply بعنوان context؛ توی apply به سری اعدا به لیست اضافه میشن و بعد apply این لیست تغییر یافته رو return میکنه و....

** نتیجه برگشتی

اسکوپ فانکنش های let، run و with نتیجه lambda رو برمیگردونن. میتونید هنگام اختصاص نتیجه به یه متغیر و .. از اینا استفاده کنید.

val countEndsWithE = numbers.run { add(&quotfour&quot) add(&quotfive&quot) count { it.endsWith(&quote&quot) } } println(&quotThere are $countEndsWithE elements that end with e.&quot)

میبینین که خط آخر رو برمیگردونه.

میتونیم از متد های (مثلا یه لیست) استفاده کنیم و یه temporary scope بسازیم.

معنیtemporary scope: مثلا اینجا متد های first و last رو ریختیم تو دوتا متغیر، این متد ها بعد از اجرا شدن این scope function از بین میرن.

val numbers = mutableListOf(&quotone&quot, &quottwo&quot, &quotthree&quot) val countEndsWithE = numbers.run { add(&quotfour&quot) add(&quotfive&quot) count { it.endsWith(&quote&quot) } } println(&quotThere are $countEndsWithE elements that end with e.&quot)

کاربرد هاشون

این بخش برای انتخاب مناسب هر scope فانکشن در جای مناسبشه.

فانکشن let

  • دسترسیِ context object رو با it بهمون میده.
  • نتیجه لامبدا رو برمیگردونه.

1- میتونیم از let برای استفاده کردن یک یا چند فانکشن تو یه زنجیره فراخوانی استفاده کنیم.

بدون let اینجوریه:

val numbers = mutableListOf(&quotone&quot, &quottwo&quot, &quotthree&quot, &quotfour&quot, &quotfive&quot) val resultList = numbers.map { it.length }.filter { it > 3 } println(resultList)

ببینید اینجا اول ما یه لیست string رو اومدیم map کردیم بعد filter و بعد تویه یه متغیر قرار دادیم تا بعدا استفاده کنیم ازش.

با let هم اینجوری:

val numbers = mutableListOf(&quotone&quot, &quottwo&quot, &quotthree&quot, &quotfour&quot, &quotfive&quot) numbers.map { it.length }.filter { it > 3 }.let { println(it) // and more function calls if needed }

ولی وقتی از let استفاده میکنیم دیگه لازم نیست این عملیات رو بریزیم تو یه متغیر. اینجا هم اول ما یه لیست string رو اومدیم map کردیم بعد filter کردیم و هرچی که انجام داده بودیم رو لیست رو ریختیم توی let بجای اینکه بریزیم توی یه متغیر، الان توی let هر چندتا fun ای که میخوایم رو صدا میزنیم و استفاده میکنیم.

2- اگه یه تَک فانکش داشتیم میتونیم از "::" استفاده کنیم، اینجوری:

val numbers = mutableListOf(&quotone&quot, &quottwo&quot, &quotthree&quot, &quotfour&quot, &quotfive&quot) numbers.map { it.length }.filter { it > 3 }.let(::println)

3- با let میتونیم یه محدوده(scope) ای بسازیم که تو اون مقدار های non-null قرار بگیره.(این تو بهتر بنویسم قبلی بود):

val str: String? = &quotHello&quot //processNonNullString(str) // compilation error: str can be null val length = str?.let { println(&quotlet() called on $it&quot) processNonNullString(it) // OK: 'it' is not null inside '?.let { }' it.length }

4- میتونیم بجا اینکه بصورت زنجیروار مثلا اول .first() و context object رو بهمون بده و دنبالش تو let استفاده کنیم. میتونیم بجاش اینجوری کدمون رو ساده تر کنیم:

val numbers = listOf(&quotone&quot, &quottwo&quot, &quotthree&quot, &quotfour&quot) val modifiedFirstItem = numbers.first().let { firstItem -> println(&quotThe first item of the list is '$firstItem'&quot) if (firstItem.length >= 5) firstItem else &quot!&quot + firstItem + &quot!&quot }.uppercase() println(&quotFirst item after modifications: '$modifiedFirstItem'&quot)

فانکشن with

  • دسترسیِ context object رو با this بهمون میده
  • نتیجه لامبدا رو برمیگردونه.

1- وقتی که نیازی به استفاده از نتیجه برگشتی ندارین، از with استفاده کنید:

val numbers = mutableListOf(&quotone&quot, &quottwo&quot, &quotthree&quot) with(numbers) { println(&quot'with' is called with argument $this&quot) println(&quotIt contains $size elements&quot) }

2- میتونید بعنوان یه helper هم ازش استفاده کنید اینطوری که یه سری محاسبات انجام بدید و بعدا حالا ازش استفاده کنید.

val numbers = mutableListOf(&quotone&quot, &quottwo&quot, &quotthree&quot) val firstAndLast = with(numbers) { &quotThe first element is ${first()},&quot + &quot the last element is ${last()}&quot } println(firstAndLast)

فانکشن run

  • دسترسیِ context object رو با this بهمون میده.
  • نتیجه لامبدا رو برمیگردونه.

این فانکشن مثل with با این تفاوت که run یه extension فانکشنه.

1- وقتی که میخواین هم object ها رو initialize کنین و هم میخواین نتیجه برگشتی رو محاسبه کنید از run استفاده کنید.

val service = MultiportService(&quothttps://example.kotlinlang.org&quot, 80) val result = service.run { port = 8080 query(prepareRequest() + &quot to port $port&quot) } // the same code written with let() function: val letResult = service.let { it.port = 8080 it.query(it.prepareRequest() + &quot to port ${it.port}&quot) }

2- میتونیم run رو روی یه آبجکت هم call نکنیم و بعنوان non-extension function ازش استفاده کنیم و همچنان نتیجه lambda رو برمیگردونه. وقتی که میخواین چندین statement رو call کنید تا اجرا بشه تو یه بلاک از استفاده بکنید:

val hexNumberRegex = run { val digits = &quot0-9&quot val hexDigits = &quotA-Fa-f&quot val sign = &quot+-&quot Regex(&quot[$sign]?[$digits$hexDigits]+&quot) } for (match in hexNumberRegex.findAll(&quot+123 -FFFF !%*& 88 XYZ&quot)) { println(match.value) }

اینجا ما regex رو initialize کردیم و بعنوان نتیجه برگردوندیم.

فانکشن apply

  • دسترسیِ context object رو با this بهمون میده.
  • خود آبجکت رو برمیگردونه

- هر وقت خواستین که یه سری چیزها رو به خود آبجکت نسبت بدین از apply استفاده کنید:

val adam = Person(&quotAdam&quot).apply { age = 32 city = &quotLondon&quot } println(adam)

فانکشن also

دسترسیِ context object رو با it بهمون میده.

خود آبجکت رو برمیگردونه.

1- هر وقت خواستین یه سری عملیات رو روی خود آبجکتتون انجام بدین از also استفاده کنید.

val numbers = mutableListOf(&quotone&quot, &quottwo&quot, &quotthree&quot) numbers .also { println(&quotThe list elements before adding new one: $it&quot) } .add(&quotfour&quot)

خب امیدوارم خوشتون اومده باشه. اگه نظری، پیشنهادی، باگی چیزی بود لطفا تو کامنت ها به اشتراک بذارین.

کانال Android Corner

kotlinandroid
اینجا جاییکه در مورد مسائل و اخبار اندرویدی حرف میزنیم. cornerdroid@gmail.com / کانال: https://t.me/AndroidCorner
شاید از این پست‌ها خوشتان بیاید