Higher-order function هي دالة تأخذ دالة أخرى كمعامل أو تُرجع دالة كنتيجة. في Kotlin، الدوال هي مواطن من الدرجة الأولى: يمكن تخزينها في متغيرات، وتمريرها كوسائط، وإرجاعها. وفقًا لوثائق Kotlin (2026)، تقلل higher-order functions من تكرار الكود بنسبة 30% في المتوسط مقارنة بالأساليب الأمرية. Higher-order function هي مفهوم أساسي للأسلوب الوظيفي في التطوير الحديث.
الملامح الرئيسية
Higher-order function هي دالة تمتلك واحدة على الأقل من خاصيتين: أخذ دالة أخرى كوسيطة أو إرجاع دالة كنتيجة. في الرياضيات، تُسمى هذه الدوال دوال أو مؤثرات. في البرمجة، ظهرت في Lisp عام 1958 وأصبحت جزءًا إلزاميًا من جميع اللغات الحديثة — JavaScript وPython وSwift وKotlin وScala وHaskell. تسمح higher-order function بالتجريد من عملية محددة وتمرير السلوك كقيمة.
الفرق الرئيسي بين higher-order function والدالة العادية هو وجود معامل بنوع وظيفي أو قيمة وظيفية مُرجعة. في Kotlin، يُكتب النوع الوظيفي كـ (ParamType) -> ReturnType. على سبيل المثال، النوع (Int) -> String يعني دالة تأخذ Int وتُرجع String. النوع () -> Unit يرمز إلى دالة بدون معاملات لا تُرجع قيمة مفيدة. نظام الأنواع هذا هو ما يجعل higher-order functions آمنة النوع.
عكس higher-order function هي first-class function (دالة من الدرجة الأولى). first-class function تعني أنه يمكن استخدام الدالة مثل أي قيمة أخرى: تعيينها لمتغير، تخزينها في مجموعة، تمريرها كوسيطة. higher-order function هي دالة تستخدم first-class functions للاستقبال أو الإرجاع. تدعم Kotlin كلا المفهومين على مستوى اللغة دون مكتبات إضافية.
في Kotlin، أي دالة لديها معامل واحد على الأقل مُعلن بنوع وظيفي أو نوع الإرجاع وظيفي تُعتبر تلقائيًا higher-order function. لا يتطلب المترجم تعليقًا خاصًا — يكفي تحديد (T) -> R في التوقيع. عند استدعاء مثل هذه الدالة، تكون الوسيطة إما تعبير lambda، أو مرجعًا لدالة موجودة عبر ::، أو قيمة وظيفية مخزنة في متغير.
fun operate(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}
fun main() {
val sum = operate(10, 20) { x, y -> x + y }
println(sum) // 30
}
في القائمة، تأخذ الدالة operate عددين صحيحين ومعامل op من النوع (Int, Int) -> Int. جسم الدالة يستدعي العملية المُمررة في سطر واحد. في main، استدعاء operate يمرر lambda { x, y -> x + y } — يضعها Kotlin بعد الأقواس بفضل بناء جملة trailing lambda. إذا كانت lambda هي الوسيطة الأخيرة، يمكن نقلها بالكامل خارج الأقواس، مما يحسن قابلية قراءة سلاسل الاستدعاءات.
داخليًا، يُجمّع Kotlin higher-order functions من خلال واجهة Function (Function2 لمعاملين). تتحول كل lambda إلى فئة مجهولة تنفذ واجهة FunctionN المقابلة.هذا يعني أن إنشاء lambda يستلزم تخصيص كائن لكل عملية. لتقليل الحمل الزائد، يدعم Kotlin الدوال المضمنة (inline)، التي تستبدل جسم higher-order function في موقع الاستدعاء، مما يلغي إنشاء الفئة المجهولة.
تمرير دالة كـ وسيطة هو النمط الأكثر شيوعًا لاستخدام higher-order functions. بدلاً من إنشاء تسلسل هرمي للفئات بطريقة متعددة الأشكال، يمرر المطور السلوك المطلوب مباشرة إلى نقطة الاستخدام. هذا يحقق مبدأ مفتوح/مغلق دون وراثة: تتم إضافة عملية جديدة كـ lambda جديدة، وليس كفئة فرعية جديدة.
للدوال القابلة للعدم، يستخدم Kotlin النوع ((T) -> R)? مع علامة استفهام بعد الأقواس. يمكن استدعاء مثل هذه الدالة فقط بعد التحقق من null أو عبر عامل ?.invoke(). في هذه الحالة، يشير توقيع higher-order function صراحةً إلى أن تمرير الدالة اختياري — يمكن للكود المستدعي حذف الوسيطة. هذا مفيد لـ callbacks ومعالجات الأحداث بسلوك اختياري.
fun <T> List<T>.customFilter(
predicate: (T) -> Boolean
): List<T> {
val result = mutableListOf<T>()
for (item in this) {
if (predicate(item)) result.add(item)
}
return result
}
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val even = numbers.customFilter { it % 2 == 0 }
println(even) // [2, 4]
}
دالة الإضافة customFilter تأخذ معامل predicate من النوع الوظيفي (T) -> Boolean. داخل الحلقة، تستدعي predicate لكل عنصر وتجمع العناصر المطابقة. الاستدعاء في main يمرر lambda { it % 2 == 0 }، حيث it هو الاسم الضمني لمعامل lambda الوحيد. بفضل higher-order functions، يكون منطق التصفية معزولاً تمامًا عن آلية اجتياز المجموعة.
يمكن أن تُرجع higher-order function دالة — يُسمى هذا النمط مصنع الدوال أو مولد السلوك. يمكن للدالة المُرجعة التقاط (closure) متغيرات من النطاق الخارجي، والحفاظ عليها بين الاستدعاءات. هذا يسمح بإنشاء معالجات قابلة للتكوين وعمليات متخصصة بناءً على قوالب شائعة.
عند إرجاع دالة، يستنتج Kotlin النوع الوظيفي ((T) -> R)? المُعاد من التوقيع. يتحقق المترجم من أن جميع تعبيرات return في الجسم تُرجع قيمًا وظيفية متوافقة. تُخزَّن المتغيرات الملتقطة في كائن lambda وتظل متاحة طالما يوجد مرجع لها. هذه آلية قوية ولكنها تتطلب الانتباه إلى إدارة الذاكرة.
fun makeMultiplier(factor: Int): (Int) -> Int {
return { x -> x * factor }
}
fun main() {
val double = makeMultiplier(2)
val triple = makeMultiplier(3)
println(double(5)) // 10
println(triple(5)) // 15
}
تأخذ الدالة makeMultiplier عاملاً وتُرجِع lambda { x -> x * factor }، حيث factor ملتقط من النطاق الخارجي (closure). كل استدعاء لـ makeMultiplier ينشئ دالة جديدة بقيمة factor خاصة بها. تخزّن المتغيرات double و triple الدوال المُعادة ويمكن استدعاؤها عدة مرات. يُستخدم هذا النمط على نطاق واسع في تكوين عملاء HTTP، والمزينات، والوسائط.
يسمح الجمع بين higher-order functions و lambdas ببناء سلاسل عمليات تعبيرية دون متغيرات وسيطة. تحتوي المكتبة القياسية لـ Kotlin على العشرات من higher-order functions: let، run، apply، also، filter، map، flatMap، fold، reduce، forEach، groupBy وغيرها. كل منها تأخذ lambda وتُجري تحويل البيانات باستخدامها.
data class User(val name: String, val age: Int)
fun main() {
val users = listOf(
User("Alice", 25),
User("Bob", 17),
User("Charlie", 30)
)
val result = users
.filter { it.age >= 18 }
.map { it.name.uppercase() }
.sorted()
println(result) // [ALICE, CHARLIE]
}
في المثال، سلسلة من ثلاث higher-order functions تعالج قائمة من المستخدمين. filter تأخذ مسندًا، تاركة فقط البالغين. map تحول كل مستخدم إلى اسم بأحرف كبيرة. sorted ترتب النتيجة تصاعديًا. كل عملية تأخذ lambda، ويضمن Kotlin سلامة النوع في كل مرحلة. بدون higher-order functions، كان سيتعين كتابة حلقة مع if وقوائم مؤقتة وفرز يدوي.
الأسئلة الشائعة
Higher-order function تأخذ دالة أخرى كمعامل أو تُرجع واحدة. الدالة العادية تعمل فقط مع البيانات — الأرقام والسلاسل والكائنات. higher-order function تعمل مع السلوك، وتمرر المنطق كوسيطة.
نعم، المُعدّل inline يلغي الحمل الزائد لإنشاء فئة مجهولة لـ lambda. باستخدام crossinline أو noinline، يمكن التحكم في أي lambdas يتم تضمينها وأيها تبقى ككائنات.
الدالة بدون معاملات وبدون قيمة إرجاع لها النوع () -> Unit. الدالة بمعامل واحد من النوع T وإرجاع R تُكتب كـ (T) -> R. لمعاملين — (T, U) -> R، وهكذا حتى 22 وسيطة.
Lambda هي تدوين موجز { args -> body }، والدالة المجهولة هي fun(args): ReturnType { body }. لا يمكن أن تحتوي lambda على return بدون علامة، بينما الدالة المجهولة يمكن. كلاهما يمكن تمريرهما إلى higher-order function.
تجنب higher-order functions في الحلقات الحرجة بدون inline — كل lambda تنشئ كائنًا. للكود الحرج للأداء، استخدم inline fun. أيضًا لا تفرط في تداخل lambdas بشكل عميق — فهذا يقلل readability.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا