Scope functions در Kotlin پنج تابع کتابخانه استاندارد (let, run, with, apply, also) هستند که یک بلوک کد را در زمینه یک شی اجرا میکنند. آنها در نحوه دسترسی به شی (از طریق it یا this) و مقدار بازگشتی (شی زمینه یا نتیجه لامبدا) متفاوت هستند. طبق مستندات Kotlin (2026)، انتخاب صحیح scope function کدهای تکراری را ۲۵-۴۰٪ کاهش میدهد. Scope functions به نوشتن مقداردهی اولیه، پیکربندی و تبدیل اشیا به سبک اعلانی کمک میکنند.
نکات اصلی
Scope functions (توابع محدوده) — مجموعهای از پنج تابع (let, run, with, apply, also) هستند که در کتابخانه استاندارد Kotlin تعبیه شدهاند. هر یک از آنها یک لامبدا میگیرد و به طور موقت محدوده دید را تغییر میدهد به طوری که داخل لامبدا کد در زمینه یک شی خاص اجرا میشود. این کار از تکرار نام شی در فراخوانیهای متعدد جلوگیری میکند و عملیات مرتبط را در یک بلوک گروهبندی میکند.
دو ویژگی کلیدی scope functions را متمایز میکند: نحوه دسترسی به شی زمینه (از طریق this یا it) و مقدار بازگشتی (خود شی زمینه یا نتیجه لامبدا). دسترسی از طریق this شی را به گیرنده ضمنی تبدیل میکند — متدها و ویژگیها بدون پیشوند فراخوانی میشوند. دسترسی از طریق it شی را به عنوان پارامتر صریح لامبدا منتقل میکند. بازگشت شی امکان ساخت زنجیره را میدهد، بازگشت نتیجه — انتساب مقدار تبدیلشده.
از نظر تاریخی، scope functions به عنوان جایگزینی برای الگوی کلاسیک Builder و متغیرهای موقت برای مقداردهی اولیه ظاهر شدند. به جای ایجاد یک کلاس builder جداگانه، برنامهنویس مینویسد apply { ویژگی1 = ...; ویژگی2 = ... }. این کار میزان کد را کاهش میدهد و مقداردهی اولیه را خواناتر میکند. در اکوسیستم Kotlin، scope functions در همه فریمورکها و کتابخانهها به عنوان یک اصطلاح استاندارد زبان استفاده میشوند.
انتخاب scope function مناسب توسط دو سوال تعیین میشود: آیا میخواهید از this یا it برای دسترسی به شی استفاده کنید، و آیا میخواهید خود شی یا نتیجه لامبدا را برگردانید. ترکیب دو ویژگی باینری چهار گزینه ممکن را میدهد، و with جایگاه ویژهای به عنوان تنها تابعی که extension نیست دارد.
| تابع | دسترسی | بازگشت | Extension؟ | سناریوی معمول |
|---|---|---|---|---|
| let | it | نتیجه لامبدا | بله | تبدیل، بررسی null |
| run | this | نتیجه لامبدا | بله | محاسبه با زمینه |
| with | this | نتیجه لامبدا | خیر | گروهبندی فراخوانیها |
| apply | this | شی زمینه | بله | مقداردهی ویژگیها |
| also | it | شی زمینه | بله | اثرات جانبی، لاگگیری |
برای پیکربندی شی بدون بازگشت مقدار جدید از apply (دسترسی از طریق this) یا also (دسترسی از طریق it) استفاده کنید. برای محاسبه مقدار جدید بر اساس شی از let (دسترسی از طریق it) یا run (دسترسی از طریق this) استفاده کنید. برای گروهبندی فراخوانیها بدون زنجیره از with استفاده کنید. پیروی از این طرح، کد را برای سایر توسعهدهندگان آشنا با Kotlin قابل پیشبینی میکند.
apply — scope function ای که شی زمینه را برمیگرداند و دسترسی به آن را از طریق this فراهم میکند. این ابزار ایدهآلی برای مقداردهی اولیه ویژگیهای شی پس از ایجاد است. در داخل بلوک apply میتوان ویژگیها را بدون تکرار نام شی تنظیم کرد. متد خود شی را برمیگرداند که امکان تعبیه apply در زنجیرهها یا استفاده در مقداردهیکنندهها را فراهم میکند.
data class ServerConfig(
var host: String = "localhost",
var port: Int = 8080,
var useTls: Boolean = false
)
val config = ServerConfig().apply {
host = "api.example.com"
port = 443
useTls = true
}
در لیستینگ، apply ServerConfig را پیکربندی میکند و مستقیماً از طریق this به ویژگیها دسترسی پیدا میکند (this حذف شده). بدون apply باید config.host = ..., config.port = ... نوشت — سه ارجاع تکراری به یک متغیر. بازگشت شی امکان ذخیره نتیجه پیکربندی را در val config فراهم میکند. also برخلاف apply، شی را از طریق it منتقل میکند و برای اثرات جانبی مناسب است.
fun saveUser(user: User) {
validate(user).also { result ->
println("نتیجه اعتبارسنجی: $result")
}
val savedUser = user.also {
log("Saving user ${it.id}")
database.save(it)
}
}
در مثال، also برای لاگگیری استفاده میشود — با بازگرداندن شی اصلی، یک اثر جانبی (چاپ پیام) را بدون تغییر شی انجام میدهد. این یک زنجیره است: validate(user) User را برمیگرداند، also نتیجه را لاگ میکند و کاربر را جلو میفرستد. also همچنین برای افزودن عناصر به مجموعهها یا اشکالزدایی در وسط زنجیره تبدیل مفید است.
let — scope function ای که نتیجه لامبدا را برمیگرداند و زمینه را از طریق it منتقل میکند. این تابع اصلی برای تبدیلهای امن از نظر null است: اگر شی nullable باشد، ترکیب ?.let { } بلوک را فقط با مقدار غیر null اجرا میکند. let همچنین برای محدود کردن محدوده دید متغیرهای موقت و تبدیلهای map-مانند از یک نوع به نوع دیگر استفاده میشود.
fun findUser(id: Int): User?
val displayName = findUser(42)?.let { user ->
"${user.name} (${user.email})"
} ?: "Unknown user"
// let با تبدیل مجموعه
val numbers = listOf("1", "2", "3")
val parsed = numbers.firstOrNull()?.let {
it.toIntOrNull()
} ?: 0
در مثال اول، let از طریق ?. فراخوانی میشود — بلوک فقط در صورتی اجرا میشود که findUser غیر null برگرداند. لامبدا user را از طریق it میگیرد (برای وضوح میتوان نامگذاری کرد) و یک رشته فرمتشده برمیگرداند. اگر کاربر پیدا نشد — عملگر Elvis ?: مقدار پیشفرض میدهد. در مثال دوم، let رشته را با null بالقوه به عدد تبدیل میکند.
run — مشابه let با دسترسی از طریق this، که نتیجه لامبدا را برمیگرداند. run برای محاسباتی که نیاز به زمینه شی دارند اما مقدار جدیدی برمیگردانند مناسب است. فراخوانی run() بدون شی (مانند run { ... }) یک محدوده دید موقت برای متغیرها ایجاد میکند. with — غیر extension با دسترسی از طریق this، مناسب برای گروهبندی فراخوانیها بدون زنجیره.
// run به عنوان محاسبه زمینهمحور
val transformed = listOf(1, 2, 3).run {
filter { it > 1 }.map { it * 10 }
}
println(transformed) // [20, 30]
// with برای گروهبندی فراخوانیها
val info = with(StringBuilder()) {
append("Name: ")
append("Alice")
toString()
}
در مثال، run روی لیست نتیجه فیلتر و map را برمیگرداند — خود تبدیل، نه لیست. with StringBuilder را به عنوان آرگومان میگیرد (نه از طریق نقطه) و داخل با this کار میکند: append, append, toString. نتیجه with — رشته info. انتخاب بین run و with اغلب سلیقهای است، اما زمانی ترجیح داده میشود که شی از جای دیگری بدون زنجیره فراخوانی منتقل میشود.
with با سایر scope functions تفاوت دارد زیرا یک تابع extension نیست. این تابع شی را به عنوان آرگومان اول و لامبدا را به عنوان آرگومان دوم میگیرد. داخل لامبدا، شی از طریق this قابل دسترسی است. with برای گروهبندی فراخوانیهای متدهای شی بدون ذخیره نتیجه یا زمانی که نیاز به «ورود» موقت به زمینه برای خواندن چند ویژگی است مناسب میباشد.
val user = User("Alice", "alice@example.com", "Admin")
val summary = with(user) {
"User $name has role $role and email $email"
}
println(summary)
// with برای مقداردهی اولیه کامپوننت UI
with(TextView(context)) {
text = "Hello"
textSize = 18.0f
setTextColor(Color.BLUE)
}
در مثال اول، with رشته summary را تشکیل میدهد و مستقیماً از طریق this به ویژگیهای user دسترسی پیدا میکند. بدون with باید «User ${user.name} has role ${user.role}» نوشت — تکرار مکرر user. در مثال دوم، with پیکربندی TextView را گروهبندی میکند: همه متدها و ویژگیها روی یک شی بدون تکرار فراخوانی میشوند. مضرات with — عدم امکان استفاده در زنجیره، زیرا extension نیست و شی زمینه را برنمیگرداند.
سوالات متداول
از apply استفاده کنید — شی را برمیگرداند و دسترسی از طریق this را فراهم میکند و امکان تنظیم ویژگیها بدون تکرار نام شی را میدهد. also زمانی مناسب است که پارامتر صریح it برای تمایز از ویژگیهای شی لازم باشد.
let زمینه را از طریق it (پارامتر صریح) منتقل میکند، run — از طریق this (گیرنده ضمنی). let برای بررسیهای null و تبدیلها مناسب است، run — برای محاسبات با زمینه. هر دو نتیجه لامبدا را برمیگردانند.
with extension نیست — شی را به عنوان آرگومان میگیرد. از with برای گروهبندی فراخوانیها زمانی که زنجیره لازم نیست استفاده کنید. apply — extension ای است که شی را برمیگرداند، مناسب برای مقداردهیکنندهها و الگوهای builder.
بله، اما مراقب this باشید — thisهای تودرتو باعث سردرگمی میشوند. برای زمینههای تودرتو از let یا also با نامهای صریح استفاده کنید تا هنگام ارجاع به شی خارجی ابهام ایجاد نشود.
همه scope functions inline هستند، بنابراین سربار کلاسهای ناشناس را ایجاد نمیکنند. تفاوت کارایی بین آنها ناچیز است. انتخاب باید بر اساس خوانایی و قراردادها باشد، نه سرعت.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید