آموزش لامبدا فانکشن در برنامه نویسی کاتلین با 9 گام!

آموزش لامبدا فانکشن در برنامه نویسی کاتلین با 9 گام!

کاربرد لامبدا فانکشن در کاتلین چگونه است؟

در این مقاله چگونگی استفاده از لامبدا فانکشن در کاتلین را نشان می دهیم.

لامبدا فانکشن (Lambda function) که بدان عبارت لامبدا هم گفته می شود تابعی بی نام است  که به عنوان یک value تلقی میشود! می توانیم لامبدا فانکشن را مانند یک value درون یک متغییر بریزیم، یا می توانیم آن را به عنوان یک پارامتر به تابع پاس بدهیم! یا به عنوان مقداری که یک تابع دلخواه برای ما return میکند؛ لامبدا فانکشن را دریافت کنیم!

val square: (Int) -> Int = { e: Int -> e * e }

در کاتلین ,عبارت لامبدا همواره درون { } قرار می گیرد.

عبارت لامبدا تابعی بی نام است که به عنوان یک value تلقی میشود!

توابع بی نام در کاتلین الزاما لامبدا فانکشن نیستند.

تابع بی نام تابعی است که نام ندارد.

package nouri.academy

fun main() {

    val vals = intArrayOf(-2, -1, 0, 1, 2, 3, 4)

    val filtered = vals.filter(fun(e) = e > 0)
    println(filtered)
}

ما یک آرایه از عددها تعریف کردیم. آن آرایه توسط یک تابع فیلتر،  فیلتر شده است. آن تابع فیلتر هم یک تابع بی نام به عنوان پارامتر می پذیرد. شما می توانید بجای این تابع بی نام یک لامبدا فانکشن بنویسید چون عبارت های لامبدا هم نوعی از توابع بی نام می باشند.

val filtered = vals.filter(fun(e) = e > 0)

تابع بی نام برای فیلتر کردن آرایه بکار می رود.

مثال بعدی بازنویسیِ مثال قبلی با استفاده از لامبدا فانکشن است.

package nouri.academy

fun main() {

    val vals = intArrayOf(-2, -1, 0, 1, 2, 3, 4)

    val filtered = vals.filter { e -> e > 0 }
    println(filtered)
}

در اینجا ما از { } و -> (که اِسکینی اَرُ خوانده می شوند) استفاده کردیم.

ساختار لامبدا فانکشن

تعریف نوع لامبدا فانکشن ها

نوع یعنی Type، مثلا 6 از نوع Int است. خب ما دانستیم لامبدا فانکشن ها value هستند اکنون میخواهیم بدانیم چگونه می توان آن ها را به یک نوعیت ارتباط داد.

برای تعریف نوع لامبدا فانکشن در کاتلین، ما همه پارامتر های ورودی تابع را را میان دو پرانتز می گذاریم؛ پس از آن اِسکینی اَرُ ( -> ) و پس از -> هم نوع مقداری که توسط تابع return می شود را می نویسیم.

package nouri.academy

fun main() {

    val square: (Int) -> Int = { e: Int -> e * e }

    val r1 = square(5)
    val r2 = square(3)

    println(r1)
    println(r2)
}

در مثال بالا ما نوع لامبدا فانکشن square را بدین شکل تعریف کردیم : یک پارامتر عدد صحیح برای ورودی و یک پارامتر عدد صحیح برای خروجی تابع؛ یعنی لامبدا فانکشن یک عدد را می گیرد و مربع اش را حساب می کند (مربع یک عدد یعنی آن عدد به توان دو شود و مکعب یک عدد یعنی آن عدد  به توان سه شود) و نتیجه را return می کند.

در واقع ما تعداد ورودی های تابع  بعلاوۀ نوع آنها و نوع مقدار return شوندۀ لامبدا فانکشن را تعیین می کنیم. همچنین اگر می خواهید لامبدا فانکشن هیچ ورودی ای نداشته باشد درون پرانتز های ورودی کافی است هیچی ننویسید و نیز اگر می خواهید لامبدا فانکشن هیچ خروجی ای return نکند باید از کلید واژه Unit استفاده کنید.

val square: (Int) -> Int = { e: Int -> e * e }

پرانتزی که نوع های ورودی های لامبدا فانکشن را مشخص می کند؛ در مثال کنونی ما بیانگر تک پارامتری، از نوع Int می باشد.

بهترین سن یادگیری برنامه نویسی چه زمانیه؟ این مقاله را بخوانید

تشخیص خودکار نوع Lambda Function توسط کاتلین

تشخیص خودکار نوع لامبدا فانکشن توسط کاتلین

خب البته که از مزایای کاتلین تشخیص خودکار و هوشمندانه ی نوع مقدار است و لامبدا فانکشن (Lambda function) ها هم از این قاعده مستثنا نیستند.

package nouri.academy

fun main() {

    val square1: (Int) -> Int = { e: Int -> e * e }
    val square2 = { e: Int -> e * e }
    val square3: (Int) -> Int = { e -> e * e }
//    val square4 = { e -> e * e }

    val r1 = square1(5)
    val r2 = square2(3)
    val r3 = square3(6)

    println(r1)
    println(r2)
    println(r3)
}

ما لامبدا فانکشن هایی برای محاسبه مربع یک عدد نوشته ایم و  همانطور که می بینید کاتلین می تواند بدون آنکه نوع لامبدا فانکشن یا متغییر پارامتر ورودی لامبدا فانکشن را بداند؛ نوع آنها را تشخیص دهد.

اگر در بخش مقدار return شوندۀ لامبدا از کلیدواژۀ Unit استفاده کنیم آنگاه کاتلین می فهمد شما نمی خواهید هیچ مقداری را return کنید.

package nouri.academy

fun main() {

    val l1 = { println("Hello there!") }
    val l2: (String) -> Unit = { name: String ->
        println("Hello $name!")
    }

    l1()
    l2("Lucia")
}

اگر عبارت لامبدا را بخواهیم به عنوان یک value درون یک متغییر بریزیم به خطا برخواهیم خورد زیرا هیچ مقداری توسط آن return نمی گردد چرا که از کلید واژۀ Unit برای مقداری که تابع بر می گرداند استفاده کردیم.

کلید واژۀ it در لامبد فانکشن ها

در واقع it یک واژۀ کلیدی است که هرگاه عبارت های لامبدای ما تنها یک پارامتر ورودی داشته باشند که به تابع پاس داده می شود؛ آن را درون بدنۀ لامبدا فانکشن نمایندگی می کند. این ویژگی از زبان برنامه نویسی Groovy توسط زبان کاتلین الگو برداری شد.

package nouri.academy

fun main() {

    val nums = listOf(1, 2, 3, 4, 5, 6)
    nums.forEach { println(it * 2) }
}

ما یک فهرست از عدد ها را داریم. با استفاده از forEach، در فهرست عنصر های list پیمایش می کنیم و هر یک از آنها را ضرب در دو می کنیم و بلافاصله بعد از ضرب هر عنصر در دو آن را چاپ می کنیم.

در واقع forEach یک تابع است (آن را forEach(f : (Int)->Unit ) تصور کنید)که یک لامبدا فانکشن (Lambda function) را به عنوان ورودی می خواهد اما بجای آنکه آن را به صورت :

nums.forEach ){ println(it * 2) })

ببینیم ؛ آن را به صورت :

nums.forEach { println(it * 2) }

دیدیم و علت آن این است که فارق از تعداد پارامتر های یک تابع (چه یک پارامتر ورودی برای یک تابع وجود داشته باشد و چه ده تا) چنانچه فقط آخرین پارامتر آن تابع یک لامبدا فانکشن را از ما به عنوان ورودی بخواهد کاتلین برای راحتی در نوشتن می گوید مستقیما  { } را باز کن و نیازی نیست حتما { } درون پرانتز ها باشد.

همچنین چنان که ملاحظه کردید عبارت لامبدای ما فقط یک ورودی دارد آن هم عنصری از list ای که ایجاد کردیم؛ و کلیدواژۀ it آن را دارد نمایندگی می کند.

پاس دادن Lambda Function به عنوان ورودی یک تابع دیگر

درمثال زیر ما لامبدا فانکشن را به عنوان پارامتر یک تابع پاس می دهیم.

package nouri.academy

val inc = { e: Int -> e + 1 }
val dec = { e: Int -> e - 1 }
val square = { e: Int -> e * e }
val triple = { e: Int -> e * e * e }

fun doProcess(vals: List<Int>, f: (Int) -> Int) {

    val processed = vals.map { e -> f(e) }
    println(processed)
}

fun main() {

    val vals = listOf(1, 2, 3, 4, 5, 6)

    doProcess(vals, inc)
    doProcess(vals, dec)
    doProcess(vals, square)
    doProcess(vals, triple)
}

ما چهارعبارت لامبدا تعریف کردیم برای افزایش تکی عدد صحیح، کاهش تکی عدد صحیح، محاسبه مربع عدد صحیح و محاسبه مکعب عدد صحیح؛ در آخر هم می توانیم هر کدام از لامبدا فانکشن ها را به تابع doProsses پاس بدهیم.

fun doProcess(vals: List<Int>, f: (Int) -> Int) {

    val processed = vals.map { e -> f(e) }
    println(processed)
}

با استفاده از تابع map میتوان بر تمام عناصر یک list تغییرات اعمال کرد و ما میتوانیم در آن لامبدا فانکشن (Lambda function) را قرار بدهیم تا هر عنصر list عددیِ ما یک واحد افزایش یا کاهش دهد یا مربع یا مکعبش را محاسبه کند.

تابع map در flow kotlin هم بکار می رود.

یک مثال جالب اعتبار سنجی با استفاده از لامبدا فانکشن

نکته: Pair یک data class در کاتلین است که دو property را برای ما نگه می دارد و به ترتیب نام های آنها first و second است.

در مثال بالا یک فهرستی از نام و نمرات دانش آموزان را داریم و می خواهیم با استفاده از یک lambda function بفهمیم کدامان یک از دانشجویان درس را پاس کرده یا کدامان یک درس را افتاده اند.

لامبدای دنباله دار

در مطالب بالاتر به این مسئله اشاره شد اما دوباره گویی آن تهی از لطف نیست.

اگر آخرین پارامتر یک تابع، یک تابع باشد؛ آنگاه یک لامبدا فانکشن می تواند بیرون و چسبیده به پرانتز ورودی های تابع قرار بگیرد. اگر لامبدا فانکشن تنها ورودی تابع باشد آنگاه همچنین می توان حتی از نوشتن پرانتز های ورودی تابع هم پرهیز کرد

مثال زیر را ببینیم:

package nouri.academy

data class User(val fname: String, val lname: String, val salary: Int)

fun main() {

    val users = listOf(
        User("John", "Doe", 1230),
        User("Lucy", "Novak", 670),
        User("Ben", "Walter", 2050),
        User("Robin", "Brown", 2300),
        User("Amy", "Doe", 1250),
        User("Joe", "Draker", 1190),
        User("Janet", "Doe", 980),
        User("Albert", "Novak", 1930)
    )

    val r1 = users.maxBy({ u: User -> u.salary })
    println(r1)

    val r2 = users.maxBy() { u: User -> u.salary }
    println(r2)

    val r3 = users.maxBy { u: User -> u.salary }
    println(r3)
}

در مثال بالا به این سه حالت نوشتن عبارت لامبدا به عنوان ورودی تابع maxBy که در لیست بر اساس اولویت دلخواه چینش می کند توجه کنید:

users.maxBy({ u: User -> u.salary })
users.maxBy() { u: User -> u.salary }
users.maxBy { u: User -> u.salary }

هر سه خط بالا یک معنا را می دهند.

توابع زنجیره ای با استفاده از لامبدا فانکشن در کاتلین

ما میتوانیم توابع را به صورت زنجیری با استفاه از لامبدا فانکشن (Lambda function) صدا بزنیم.

package nouri.academy

fun main() {

    val words = listOf("sky", "cup", "water", "den", 
        "knife", "earth", "falcon")

    val res = words.filter { it.length == 5 }.sortedBy { it }
        .map { it.replaceFirstChar(Char::titlecase) }
    println(res)
}

ما فهرستی از واژه ها  را فیلتر، مرتب و تغییر عمومی می دهیم. یک زنجیره سه تایی از صدا زدن توابع صورت گرفته است زیرا هر لامبدا فانکشن یک value ایی که آخرین تغییرات را روی آن اعمال کرده است بر می گرداند. توابع دارای لامبدا های دنباله دار هستند.

تخریب در لامبدا فانکشن ها!

پارامتر ها در لامبدا فانکشن ها می توانند تخریب یا نابود شوند. برای متغییرهای ناکار آمد می توانیم از نویسۀ زیرخط (_) استفاده کرد.

package nouri.academy

fun main() {

    val words = mapOf(
        1 to "sky", 2 to "cup", 3 to "water", 4 to "den",
        5 to "knife", 6 to "earth", 7 to "falcon"
    )

    words.forEach { (_, v) -> println(v) }
}

ما یک map از واژه ها را داریم (این map را با map ای که در list ها دیدید اشتباه نگیرید، این map خودش یک Data Type است). در ادامه در map با استفاده از forEach پیمایش می کنیم. تا زمانی که نیازی به استفاده از پارامتر key نداریم از نویسۀ زیرخط ( _ ) که در انگلیسی بدان underscore  می گویند استفاده می کنیم.

دیدگاه‌ها ۰
ارسال دیدگاه جدید