الدوال من المرتبة الأعلى (Higher-Order Functions) و Lambdas

من موسوعة حسوب
مراجعة 09:25، 24 مارس 2018 بواسطة Nourtam (نقاش | مساهمات) (أنشأ الصفحة ب'<noinclude>{{DISPLAYTITLE:الدوال من المرتبة الأعلى (Higher-Order Functions) و Lambdas}}</noinclude> == الدوال من المرتبة الأعل...')
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)

الدوال من المرتبة الأعلى (Higher-Order Functions)

وهي الدوال التي تقبل دوالًا أخرى كمتحولاتٍ وسيطةٍ لها، أو تلك التي تُعيد (return) دوالًا أخرى كنتيجة لها، وكمثالٍ عنها لنأخذ الدالة lock()‎، وهي الدالة التي تقبل كائنًا lock ودالةً أخرى، حيث ستحصلُ الدالة على الكائن lock وتُنفِّذُ الدالةَ الوسيطةَ ثم تُحرِّر القفل في النهاية، كما في الشيفرة:

fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
    }
    finally {
        lock.unlock()
    }
}

إنّ body هو من نوع دالة (وهو ‎() -> T) ومن الواضح أنّه دالةٌ خاليةٌ من المتحولات تعيد النوع T، وتُستدعَى في الجزء try حيث تكون محميةً (protected) عبر lock، وتُعاد قيمتها عبر الدالة lock()‎. ولدى استدعاء الدالة lock()‎ يمكن تمرير دالةٍ أخرى كمتحولٍ لها (راجع مرجعيّات الدوال [function references] )، مثل:

fun toBeSynchronized() = sharedResource.operation()

val result = lock(lock, ::toBeSynchronized)

ومن الأفضل بمثل هذه الحالة تمريرُ تعبير lambda بالشكل:

val result = lock(lock, { sharedResource.operation() })

إذ تتصف تعابير lambda بما يلي:

  • يٌحاط التعبير بالقوسين {} دائمًا.
  • تُعرَّف متحولاته (إن وُجدَت) قبل المعامل ‎->‎ (وقد يُحذَف نوع المتحولات).
  • تتوضع البُنية (body) (إن وُجدَت) بعد المعامل ‎->‎ .

ويُصطلَح في Kotlin بأنّه إن كانت الدالة تقبل في متحولها الأخير دالةً أخرى ويُمرَّر تعبير lambda كمتحولٍ لها، فيجب أن يوضع خارج القوسين () كما في الشيفرة:

lock (lock) {
    sharedResource.operation()
}

وتُعدُّ الدالة map()‎ مثالًا آخر عن الدوال من المرتبة الأعلى وهي بالشكل:

fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
    val result = arrayListOf<R>()
    for (item in this)
        result.add(transform(item))
    return result
}

ويكون استدعاؤها بالشكل:

val doubled = ints.map { value -> value * 2 }

حيث أمكن حذف القوسين () لأنّ lambda هي المتحول الوحيد المُستخدَم في هذا الاستدعاء.

it: الاسم الضمني (implicit name) للمتحول الوحيد

ليس من الضروريّ وجود تصريح (declaration) الدالة إن كانت القيمة الحرفيّة (literal) لها بمتحولٍ وحيدٍ حيث ستُستخدَم التسمية it للتعبير عنها، بالشكل:

ints.map { it * 2 }

ويسمح هذا بكتابة شيفرةٍ بالنمط LINQ كما يلي:

strings.filter { it.length == 5 }.sortedBy { it }.map { it.toUpperCase() }

استخدام الشرطة السفليّة (_) للمتحولات غير المستخدمة (بدءًا من الإصدار 1.1)

إن لم تكن هناك حاجةٌ لأحد المتحولات الوسيطة في lambda فيمكن استخدام الرمز _ بدلًا من الاسم، بالشكل:

map.forEach { _, value -> println("$value!") }

التفكيك (Destructuring) في Lambdas (بدءًا من الإصدار 1.1)

راجع التصريح بالتفكيك (destructuring declarations) حيث ستجد شرحًا عن التفكيك في lambda.

الدوال السطريّة (Inline Functions)

قد تُستخدَم الدوال السطريّة لتحسين الأداء في الدوال من المرتبة الأعلى.

مصادر