الفرق بين المراجعتين لصفحة: «Kotlin/lambdas»
ط استبدال النص - 'Kotlin Functions' ب'Kotlin Function' |
|||
(3 مراجعات متوسطة بواسطة مستخدم واحد آخر غير معروضة) | |||
سطر 1: | سطر 1: | ||
<noinclude>{{DISPLAYTITLE:الدوال من المرتبة الأعلى (Higher-Order Functions) و Lambdas}}</noinclude> | <noinclude>{{DISPLAYTITLE:الدوال من المرتبة الأعلى (Higher-Order Functions) و Lambdas}}</noinclude> | ||
== الدوال من المرتبة الأعلى (Higher-Order Functions) == | == الدوال من المرتبة الأعلى (Higher-Order Functions) == | ||
وهي الدوال التي تقبل دوالًا أخرى | وهي الدوال التي تقبل دوالًا أخرى كمعاملاتٍ (parameters) لها، أو تلك التي تُعيد (return) دوالًا أخرى كنتيجة لها، وكمثالٍ عنها لنأخذ الدالة <code>lock()</code>، وهي الدالة التي تقبل كائنًا lock ودالةً أخرى، حيث ستحصلُ الدالة على الكائن lock وتُنفِّذُ الدالةَ الوسيطةَ ثم تُحرِّر القفل في النهاية، كما في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
fun <T> lock(lock: Lock, body: () -> T): T { | fun <T> lock(lock: Lock, body: () -> T): T { | ||
lock.lock() | lock.lock() | ||
سطر 11: | سطر 11: | ||
} | } | ||
} | } | ||
</syntaxhighlight>إنّ <code>body</code> هو من نوع دالة (وهو <code>() -> T</code>) ومن الواضح أنّه دالةٌ خاليةٌ من | </syntaxhighlight>إنّ <code>body</code> هو من نوع دالة (وهو <code>() -> T</code>) ومن الواضح أنّه دالةٌ خاليةٌ من المعاملات تعيد النوع <code>T</code>، وتُستدعَى في الجزء <code>try</code> حيث تكون محميةً (protected) عبر lock، وتُعاد قيمتها عبر الدالة <code>lock()</code>. | ||
ولدى استدعاء الدالة <code>lock()</code> يمكن تمرير دالةٍ أخرى | ولدى استدعاء الدالة <code>lock()</code> يمكن تمرير دالةٍ أخرى كمعاملٍ لها (راجع [[Kotlin/reflection|مرجعيّات الدوال [function references]]] )، مثل:<syntaxhighlight lang="kotlin"> | ||
fun toBeSynchronized() = sharedResource.operation() | fun toBeSynchronized() = sharedResource.operation() | ||
سطر 21: | سطر 21: | ||
</syntaxhighlight>إذ تتصف تعابير lambda بما يلي: | </syntaxhighlight>إذ تتصف تعابير lambda بما يلي: | ||
* يٌحاط التعبير بالقوسين <code>{}</code> دائمًا. | * يٌحاط التعبير بالقوسين <code>{}</code> دائمًا. | ||
* تُعرَّف | * تُعرَّف معاملاته (إن وُجدَت) قبل المعامل <code>-></code> (وقد يُحذَف نوع المعاملات). | ||
* تتوضع البُنية (body) (إن وُجدَت) بعد المعامل <code>-></code> . | * تتوضع البُنية (body) (إن وُجدَت) بعد المعامل <code>-></code> . | ||
ويُصطلَح في Kotlin بأنّه إن كانت الدالة تقبل في | ويُصطلَح في Kotlin بأنّه إن كانت الدالة تقبل في معاملها الأخير دالةً أخرى ويُمرَّر تعبير lambda كوسيط لها، فيجب أن يوضع خارج القوسين <code>()</code> كما في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
lock (lock) { | lock (lock) { | ||
sharedResource.operation() | sharedResource.operation() | ||
سطر 36: | سطر 36: | ||
</syntaxhighlight>ويكون استدعاؤها بالشكل:<syntaxhighlight lang="kotlin"> | </syntaxhighlight>ويكون استدعاؤها بالشكل:<syntaxhighlight lang="kotlin"> | ||
val doubled = ints.map { value -> value * 2 } | val doubled = ints.map { value -> value * 2 } | ||
</syntaxhighlight>حيث أمكن حذف القوسين <code>()</code> لأنّ lambda هي | </syntaxhighlight>حيث أمكن حذف القوسين <code>()</code> لأنّ lambda هي الوسيط الوحيد المُستخدَم في هذا الاستدعاء. | ||
=== <code>it</code>: الاسم الضمني (implicit name) | === <code>it</code>: الاسم الضمني (implicit name) للمعامل الوحيد === | ||
ليس من الضروريّ وجود تصريح (declaration) الدالة إن كانت القيمة الحرفيّة (literal) لها | ليس من الضروريّ وجود تصريح (declaration) الدالة إن كانت القيمة الحرفيّة (literal) لها بمعاملٍ وحيدٍ حيث ستُستخدَم التسمية <code>it</code> للتعبير عنها، بالشكل:<syntaxhighlight lang="kotlin"> | ||
ints.map { it * 2 } | ints.map { it * 2 } | ||
</syntaxhighlight>ويسمح هذا بكتابة شيفرةٍ بالنمط LINQ كما يلي:<syntaxhighlight lang="kotlin"> | </syntaxhighlight>ويسمح هذا بكتابة شيفرةٍ بالنمط LINQ كما يلي:<syntaxhighlight lang="kotlin"> | ||
سطر 45: | سطر 45: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== استخدام الشرطة السفليّة (<code>_</code>) | === استخدام الشرطة السفليّة (<code>_</code>) للوسائط (arguments) غير المستخدمة (بدءًا من الإصدار 1.1) === | ||
إن لم تكن هناك حاجةٌ لأحد | إن لم تكن هناك حاجةٌ لأحد الوسائط في lambda فيمكن استخدام الرمز <code>_</code> بدلًا من الاسم، بالشكل:<syntaxhighlight lang="kotlin"> | ||
map.forEach { _, value -> println("$value!") } | map.forEach { _, value -> println("$value!") } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
سطر 53: | سطر 53: | ||
راجع [[Kotlin/multi declarations|التصريح بالتفكيك (destructuring declarations)]] حيث ستجد شرحًا عن التفكيك في lambda. | راجع [[Kotlin/multi declarations|التصريح بالتفكيك (destructuring declarations)]] حيث ستجد شرحًا عن التفكيك في lambda. | ||
== الدوال | == الدوال المباشرة (Inline Functions) == | ||
قد تُستخدَم [[Kotlin/inline functions|الدوال | قد تُستخدَم [[Kotlin/inline functions|الدوال المباشرة]] لتحسين الأداء في الدوال من المرتبة الأعلى. | ||
== تعابير Lambda والدوال المجهولة (Anonymous Functions) == | == تعابير Lambda والدوال المجهولة (Anonymous Functions) == | ||
يُعدُّ كلٌّ من تعبير lambda والدالة المجهولة قيمةً دالةً حرفيّةً (function literal) وهي دالةٌ لا يُصرَّح عنها بل تُمرَّر كتعبيرٍ مباشرةً، كما في المثال الآتي:<syntaxhighlight lang="kotlin"> | يُعدُّ كلٌّ من تعبير lambda والدالة المجهولة قيمةً دالةً حرفيّةً (function literal) وهي دالةٌ لا يُصرَّح عنها بل تُمرَّر كتعبيرٍ مباشرةً، كما في المثال الآتي:<syntaxhighlight lang="kotlin"> | ||
max(strings, { a, b -> a.length < b.length }) | max(strings, { a, b -> a.length < b.length }) | ||
</syntaxhighlight>إذ إنّ الدالة <code>max</code> هي دالةٌ من مرتبةٍ أعلى (higher order) لأنها تقبل دالةً في | </syntaxhighlight>إذ إنّ الدالة <code>max</code> هي دالةٌ من مرتبةٍ أعلى (higher order) لأنها تقبل دالةً في معاملها الثاني والذي هو "تعبيرٌ داليّ" أو بكلامٍ آخر هو "قيمة حرفيّة للدالة"، وهذا مكافئٌ للشيفرة: <syntaxhighlight lang="kotlin"> | ||
fun compare(a: String, b: String): Boolean = a.length < b.length | fun compare(a: String, b: String): Boolean = a.length < b.length | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== أنواع الدوال (Function Types) === | === أنواع الدوال (Function Types) === | ||
يجب تحديد نوع | يجب تحديد نوع المعامل في الدالة من النوع "دالة" حتى يقبل تمرير الدالة عبره، إذ سيعرَّف التابع <code>max</code> المذكور سابقًا كما يلي:<syntaxhighlight lang="kotlin" line="1"> | ||
fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? { | fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? { | ||
var max: T? = null | var max: T? = null | ||
سطر 72: | سطر 72: | ||
return max | return max | ||
} | } | ||
</syntaxhighlight>إنّ | </syntaxhighlight>إنّ المعامل <code>less</code> من النوع <code>(T, T) -> Boolean</code> والذي هو دالة بمعاملين من النوع <code>T</code> تعيد قيمةً منطقيّةً (boolean) بالقيمة <code>true</code> إن كان المعامل الأوّل بقيمةٍ أصغر من قيمة المعامل الثاني. | ||
وقد استُخدِم | وقد استُخدِم المعامل <code>less</code> في السطر الرابع كدالةٍ مُستدعَاة عبر تمرير المعاملين من النوع <code>T</code>، إذ يكتب نوع الدالة كما في الشيفرة السابقة، أو قد يُكتب بمعاملات مُسمَّاة (named parameters) عند الرغبة بتوثيق المعنى لكلِّ معاملٍ، كما يلي:<syntaxhighlight lang="kotlin"> | ||
val compare: (x: T, y: T) -> Int = ... | val compare: (x: T, y: T) -> Int = ... | ||
</syntaxhighlight>وللتصريح عن | </syntaxhighlight>وللتصريح عن معامل nullable من نوع الدالة يجب أن يُحاط كامل نوع الدالة بين قوسين <code>()</code> ويُضاف الرمز <code>?</code> تاليًا بالشكل:<syntaxhighlight lang="kotlin"> | ||
var sum: ((Int, Int) -> Int)? = null | var sum: ((Int, Int) -> Int)? = null | ||
</syntaxhighlight> | </syntaxhighlight> | ||
سطر 85: | سطر 85: | ||
</syntaxhighlight>إذ يُحاط تعبير lambda بالأقواس <code>{}</code> دائمًا حيث يُوضَع داخلهما التصريحُ بالصيغة الكاملة، وقد يحتوي على توصيفاتٍ للنوع (type annotations) وهذا أمرٌ اختياريّ، وتأتي بُنية التعبير بعد الرمز <code><-،</code> وإن لم يكن النوع المُعاد في التعبير من النوع <code>Unit</code> فسيُعامَل آخرُ تعبيرٍ واردٍ فيه وكأنه القيمة المُعادة، وبإزالة كافة التوصيفات الاختيارية نحصل على التعبير بالشكل:<syntaxhighlight lang="kotlin"> | </syntaxhighlight>إذ يُحاط تعبير lambda بالأقواس <code>{}</code> دائمًا حيث يُوضَع داخلهما التصريحُ بالصيغة الكاملة، وقد يحتوي على توصيفاتٍ للنوع (type annotations) وهذا أمرٌ اختياريّ، وتأتي بُنية التعبير بعد الرمز <code><-،</code> وإن لم يكن النوع المُعاد في التعبير من النوع <code>Unit</code> فسيُعامَل آخرُ تعبيرٍ واردٍ فيه وكأنه القيمة المُعادة، وبإزالة كافة التوصيفات الاختيارية نحصل على التعبير بالشكل:<syntaxhighlight lang="kotlin"> | ||
val sum: (Int, Int) -> Int = { x, y -> x + y } | val sum: (Int, Int) -> Int = { x, y -> x + y } | ||
</syntaxhighlight>ومن الشائع جدًا أن يكون للتعبير | </syntaxhighlight>ومن الشائع جدًا أن يكون للتعبير وسيطٌ واحدٌ فقط، وعندها لا حاجة إلى التصريح عنه إذ سيُعرَّف ضمنيًا (implicitly) بالتسمية <code>it</code>، كما في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
ints.filter { it > 0 } // القيمة الحرفيّة هنا هي من النوع | ints.filter { it > 0 } // القيمة الحرفيّة هنا هي من النوع | ||
// '(it: Int) -> Boolean' | // '(it: Int) -> Boolean' | ||
سطر 98: | سطر 98: | ||
return@filter shouldFilter | return@filter shouldFilter | ||
} | } | ||
</syntaxhighlight>أمّا عند وجود دالةٍ في | </syntaxhighlight>أمّا عند وجود دالةٍ في المعامل الأخير للدالة فيُمكن تمرير تعبير lambda خارج القوسين <code>()</code>. | ||
=== الدوال المجهولة (Anonymous Function) === | === الدوال المجهولة (Anonymous Function) === | ||
سطر 107: | سطر 107: | ||
return x + y | return x + y | ||
} | } | ||
</syntaxhighlight>وتُحدَّد | </syntaxhighlight>وتُحدَّد معاملاتها ونوعها المُعاد بالطريقة ذاتها المُستخدَمة بالدوال النظاميّة باستثناء إمكانية الاستغناء عن أنواع المعاملات إن أمكن استنتاجها من السياق العام كما في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
ints.filter(fun(item) = item > 0) | ints.filter(fun(item) = item > 0) | ||
</syntaxhighlight>ويُخمَّن النوع المُعاد للدوال المجهولة -كما في الدوال الأخرى- تلقائيًّا إن كانت بُنيتها بتعبيرٍ وحيدٍ، ويجب تحديده بشكلٍ صريحٍ (explicitly) إن كانت بُنية الدالة جزءًا كاملًا (block) وإلا فسيكون من النوع الافتراضيّ<code>Unit</code>، وتٌمرَّر | </syntaxhighlight>ويُخمَّن النوع المُعاد للدوال المجهولة -كما في الدوال الأخرى- تلقائيًّا إن كانت بُنيتها بتعبيرٍ وحيدٍ، ويجب تحديده بشكلٍ صريحٍ (explicitly) إن كانت بُنية الدالة جزءًا كاملًا (block) وإلا فسيكون من النوع الافتراضيّ<code>Unit</code>، وتٌمرَّر وسائط الدوال المجهولة دائمًا ضمن القوسين <code>()</code> ولا يُسمَح بتمريرها خارجهما (يُسمح بذلك في تعابير lambda فقط). | ||
وهناك فرقٌ آخر ما بين تعابير lambda والدوال المجهولة في [[Kotlin/inline functions|أوامر الرجوع غير المحليّة (non-local returns)]]، حيث ستعود التعليمة <code>return</code> (بدون التسمية [label]) من الدالة المُعرَّفة بالكلمة المفتاحيّة <code>fun</code> ، وهذا يعني أنّ الأمر <code>return</code> الواقع داخل التعبير lambda سيعود من الدالة المحيطة به (enclosing)، أمّا الأمر <code>return</code> الموجود داخل الدالة المجهولة سيعود من الدالة المجهولة نفسها. | وهناك فرقٌ آخر ما بين تعابير lambda والدوال المجهولة في [[Kotlin/inline functions|أوامر الرجوع غير المحليّة (non-local returns)]]، حيث ستعود التعليمة <code>return</code> (بدون التسمية [label]) من الدالة المُعرَّفة بالكلمة المفتاحيّة <code>fun</code> ، وهذا يعني أنّ الأمر <code>return</code> الواقع داخل التعبير lambda سيعود من الدالة المحيطة به (enclosing)، أمّا الأمر <code>return</code> الموجود داخل الدالة المجهولة سيعود من الدالة المجهولة نفسها. | ||
=== النطاق المُغلق (Closures) === | === النطاق المُغلق (Closures) === | ||
تستطيع كلٌّ من تعابير lambda أو الدوال المجهولة (anonymous functions) أو [[Kotlin/functions|الدوال المحليّة (local functions)]] أو [[Kotlin/object declarations|تعابير الكائنات (object expressions)]] الوصول إلى العناصر الموجودة ضمن نطاقها المغلق أي لكافّة | تستطيع كلٌّ من تعابير lambda أو الدوال المجهولة (anonymous functions) أو [[Kotlin/functions|الدوال المحليّة (local functions)]] أو [[Kotlin/object declarations|تعابير الكائنات (object expressions)]] الوصول إلى العناصر الموجودة ضمن نطاقها المغلق أي لكافّة المتغيِّرات الموجودة في المجال الخارجيّ (outer scope)، وعلى عكس لغة Java فإنّه من الممكن تعديل المتغيِّرات الموجودة ضمن هذا النطاق، كما في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
var sum = 0 | var sum = 0 | ||
ints.filter { it > 0 }.forEach { | ints.filter { it > 0 }.forEach { | ||
سطر 129: | سطر 129: | ||
</syntaxhighlight>إذ من الممكن استدعاؤها وكأنها تابعٌ للكائن المستقبِل بالشكل:<syntaxhighlight lang="kotlin"> | </syntaxhighlight>إذ من الممكن استدعاؤها وكأنها تابعٌ للكائن المستقبِل بالشكل:<syntaxhighlight lang="kotlin"> | ||
1.sum(2) | 1.sum(2) | ||
</syntaxhighlight>وتسمح صيغة الدالة المجهولة (anonymous function) بتحديد نوع المستقبِل للقيمة الحرفيّة للدالة مباشرةً، وهذا يفيد عند الحاجة إلى التصريح عن | </syntaxhighlight>وتسمح صيغة الدالة المجهولة (anonymous function) بتحديد نوع المستقبِل للقيمة الحرفيّة للدالة مباشرةً، وهذا يفيد عند الحاجة إلى التصريح عن معاملٍ من نوع الدالة مع المستقبِل لاستخدامه فيما بعد، مثل:<syntaxhighlight lang="kotlin"> | ||
val sum = fun Int.(other: Int): Int = this + other | val sum = fun Int.(other: Int): Int = this + other | ||
</syntaxhighlight>كما يمكن إسناد القيمة غير الحرفيّة (non-literal) من نوع الدالة بالمستقبِل أو تمريرها | </syntaxhighlight>كما يمكن إسناد القيمة غير الحرفيّة (non-literal) من نوع الدالة بالمستقبِل أو تمريرها كوسيطٍ بالموقع الذي تُتوقَّع فيه الدالة العاديّة، والتي تحتوي على معاملٍ أولٍ إضافيِّ من نوع المستقبِل وبالعكس، فمثلًا: يكون النوعان <code>String.(Int) -> Boolean</code> و <code>(String, Int) -> Boolean</code> متوافقين في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
val represents: String.(Int) -> Boolean = { other -> toIntOrNull() == other } | val represents: String.(Int) -> Boolean = { other -> toIntOrNull() == other } | ||
println("123".represents(123)) // true | println("123".represents(123)) // true | ||
سطر 160: | سطر 160: | ||
* [https://kotlinlang.org/docs/reference/lambdas.html صفحة الدوال من المرتبة الأعلى وLambdas في التوثيق الرسميّ للغة Kotlin] | * [https://kotlinlang.org/docs/reference/lambdas.html صفحة الدوال من المرتبة الأعلى وLambdas في التوثيق الرسميّ للغة Kotlin] | ||
[[تصنيف:Kotlin]] | [[تصنيف:Kotlin]] | ||
[[تصنيف:Kotlin | [[تصنيف:Kotlin Function]] | ||
[[تصنيف:Kotlin Lambdas]] | [[تصنيف:Kotlin Lambdas]] | ||
[[تصنيف:Kotlin Expressions]] | [[تصنيف:Kotlin Expressions]] | ||
[[تصنيف:Kotlin Parameters]] | [[تصنيف:Kotlin Parameters]] |
المراجعة الحالية بتاريخ 11:35، 30 أغسطس 2018
الدوال من المرتبة الأعلى (Higher-Order Functions)
وهي الدوال التي تقبل دوالًا أخرى كمعاملاتٍ (parameters) لها، أو تلك التي تُعيد (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() }
استخدام الشرطة السفليّة (_
) للوسائط (arguments) غير المستخدمة (بدءًا من الإصدار 1.1)
إن لم تكن هناك حاجةٌ لأحد الوسائط في lambda فيمكن استخدام الرمز _
بدلًا من الاسم، بالشكل:
map.forEach { _, value -> println("$value!") }
التفكيك (Destructuring) في Lambdas (بدءًا من الإصدار 1.1)
راجع التصريح بالتفكيك (destructuring declarations) حيث ستجد شرحًا عن التفكيك في lambda.
الدوال المباشرة (Inline Functions)
قد تُستخدَم الدوال المباشرة لتحسين الأداء في الدوال من المرتبة الأعلى.
تعابير Lambda والدوال المجهولة (Anonymous Functions)
يُعدُّ كلٌّ من تعبير lambda والدالة المجهولة قيمةً دالةً حرفيّةً (function literal) وهي دالةٌ لا يُصرَّح عنها بل تُمرَّر كتعبيرٍ مباشرةً، كما في المثال الآتي:
max(strings, { a, b -> a.length < b.length })
إذ إنّ الدالة max
هي دالةٌ من مرتبةٍ أعلى (higher order) لأنها تقبل دالةً في معاملها الثاني والذي هو "تعبيرٌ داليّ" أو بكلامٍ آخر هو "قيمة حرفيّة للدالة"، وهذا مكافئٌ للشيفرة:
fun compare(a: String, b: String): Boolean = a.length < b.length
أنواع الدوال (Function Types)
يجب تحديد نوع المعامل في الدالة من النوع "دالة" حتى يقبل تمرير الدالة عبره، إذ سيعرَّف التابع max
المذكور سابقًا كما يلي:
fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it))
max = it
return max
}
إنّ المعامل less
من النوع (T, T) -> Boolean
والذي هو دالة بمعاملين من النوع T
تعيد قيمةً منطقيّةً (boolean) بالقيمة true
إن كان المعامل الأوّل بقيمةٍ أصغر من قيمة المعامل الثاني.
وقد استُخدِم المعامل less
في السطر الرابع كدالةٍ مُستدعَاة عبر تمرير المعاملين من النوع T
، إذ يكتب نوع الدالة كما في الشيفرة السابقة، أو قد يُكتب بمعاملات مُسمَّاة (named parameters) عند الرغبة بتوثيق المعنى لكلِّ معاملٍ، كما يلي:
val compare: (x: T, y: T) -> Int = ...
وللتصريح عن معامل nullable من نوع الدالة يجب أن يُحاط كامل نوع الدالة بين قوسين ()
ويُضاف الرمز ?
تاليًا بالشكل:
var sum: ((Int, Int) -> Int)? = null
صيغة تعابير Lambda
تكون الصيغة الكاملة لتعابير lambda (القيم الحرفيّة لأنواع الدوال) بالشكل:
val sum = { x: Int, y: Int -> x + y }
إذ يُحاط تعبير lambda بالأقواس {}
دائمًا حيث يُوضَع داخلهما التصريحُ بالصيغة الكاملة، وقد يحتوي على توصيفاتٍ للنوع (type annotations) وهذا أمرٌ اختياريّ، وتأتي بُنية التعبير بعد الرمز <-،
وإن لم يكن النوع المُعاد في التعبير من النوع Unit
فسيُعامَل آخرُ تعبيرٍ واردٍ فيه وكأنه القيمة المُعادة، وبإزالة كافة التوصيفات الاختيارية نحصل على التعبير بالشكل:
val sum: (Int, Int) -> Int = { x, y -> x + y }
ومن الشائع جدًا أن يكون للتعبير وسيطٌ واحدٌ فقط، وعندها لا حاجة إلى التصريح عنه إذ سيُعرَّف ضمنيًا (implicitly) بالتسمية it
، كما في الشيفرة:
ints.filter { it > 0 } // القيمة الحرفيّة هنا هي من النوع
// '(it: Int) -> Boolean'
ويمكن استخدام أمر العودة المُقيّد (qualified return) في تعبير lambda لإعادة قيمةٍ ما حيث يتمّ ذكر بشكلٍ صريحٍ (explicitly)، وإن لم يُذكر ذلك فسيُعامَل آخرُ تعبيرٍ واردٍ فيه وكأنه القيمة المُعادة، ولذلك فإن مقطعي الشيفرة الآتيان متكافئان:
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
أمّا عند وجود دالةٍ في المعامل الأخير للدالة فيُمكن تمرير تعبير lambda خارج القوسين ()
.
الدوال المجهولة (Anonymous Function)
ليس من الضروريّ -أغلب الأحيان- تحديد النوع المُعاد للدالة حيث يُمكن توقُّعه بشكلٍ تلقائيّ، ولكن عند الحاجة للقيام بذلك بشكلٍ صريحٍ (explicitly) فتُستخدَم صيغة الدوال المجهولة بالشكل:
fun(x: Int, y: Int): Int = x + y
وهي تماثل الدالة النظاميّة وتختلف عنها فقط بعدم وجود اسمٍ لها، وقد تكون بُنيتها بتعبيرٍ وحيدٍ أو كجزءٍ كاملٍ (block) كما يلي:
fun(x: Int, y: Int): Int {
return x + y
}
وتُحدَّد معاملاتها ونوعها المُعاد بالطريقة ذاتها المُستخدَمة بالدوال النظاميّة باستثناء إمكانية الاستغناء عن أنواع المعاملات إن أمكن استنتاجها من السياق العام كما في الشيفرة:
ints.filter(fun(item) = item > 0)
ويُخمَّن النوع المُعاد للدوال المجهولة -كما في الدوال الأخرى- تلقائيًّا إن كانت بُنيتها بتعبيرٍ وحيدٍ، ويجب تحديده بشكلٍ صريحٍ (explicitly) إن كانت بُنية الدالة جزءًا كاملًا (block) وإلا فسيكون من النوع الافتراضيّUnit
، وتٌمرَّر وسائط الدوال المجهولة دائمًا ضمن القوسين ()
ولا يُسمَح بتمريرها خارجهما (يُسمح بذلك في تعابير lambda فقط).
وهناك فرقٌ آخر ما بين تعابير lambda والدوال المجهولة في أوامر الرجوع غير المحليّة (non-local returns)، حيث ستعود التعليمة return
(بدون التسمية [label]) من الدالة المُعرَّفة بالكلمة المفتاحيّة fun
، وهذا يعني أنّ الأمر return
الواقع داخل التعبير lambda سيعود من الدالة المحيطة به (enclosing)، أمّا الأمر return
الموجود داخل الدالة المجهولة سيعود من الدالة المجهولة نفسها.
النطاق المُغلق (Closures)
تستطيع كلٌّ من تعابير lambda أو الدوال المجهولة (anonymous functions) أو الدوال المحليّة (local functions) أو تعابير الكائنات (object expressions) الوصول إلى العناصر الموجودة ضمن نطاقها المغلق أي لكافّة المتغيِّرات الموجودة في المجال الخارجيّ (outer scope)، وعلى عكس لغة Java فإنّه من الممكن تعديل المتغيِّرات الموجودة ضمن هذا النطاق، كما في الشيفرة:
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
القيم الحرفيّة للدوال مع المستقبِل (Function Literals with Receiver)
تتيح لغة Kotlin إمكانيّة استدعاء قيمة حرفيّة للدالة بكائنٍ مستقبِل مُحدَّدٍ، إذ يمكن -داخل بُنية قيمة الدالة الحرفيّة- استدعاء التوابع عبر الكائن المُستقبِل دون استخدام أيّ مقيّدات (qualifiers) إضافيّة، وهذا يماثل الدوال الإضافيّة (extension functions) التي تسمح بالوصول (accessing) إلى عناصر الكائن المستقبِل داخل بُنية الدالة، ومن أشهر الأمثلة عنها المُنشِئ الحافظ للنوع (type-safe builders).
نوع هذه القيم الحرفيّة للدالة (function literals) هو نوع دالةٍ مع المستقبِل، أي:
sum : Int.(other: Int) -> Int
إذ من الممكن استدعاؤها وكأنها تابعٌ للكائن المستقبِل بالشكل:
1.sum(2)
وتسمح صيغة الدالة المجهولة (anonymous function) بتحديد نوع المستقبِل للقيمة الحرفيّة للدالة مباشرةً، وهذا يفيد عند الحاجة إلى التصريح عن معاملٍ من نوع الدالة مع المستقبِل لاستخدامه فيما بعد، مثل:
val sum = fun Int.(other: Int): Int = this + other
كما يمكن إسناد القيمة غير الحرفيّة (non-literal) من نوع الدالة بالمستقبِل أو تمريرها كوسيطٍ بالموقع الذي تُتوقَّع فيه الدالة العاديّة، والتي تحتوي على معاملٍ أولٍ إضافيِّ من نوع المستقبِل وبالعكس، فمثلًا: يكون النوعان String.(Int) -> Boolean
و (String, Int) -> Boolean
متوافقين في الشيفرة:
val represents: String.(Int) -> Boolean = { other -> toIntOrNull() == other }
println("123".represents(123)) // true
fun testOperation(op: (String, Int) -> Boolean, a: String, b: Int, c: Boolean) =
assert(op(a, b) == c)
testOperation(represents, "100", 100, true) // OK
وعندما يكون من الممكن تحديد نوع المستقبِل من السياق العامّ فتستخدم حينئذٍ تعابير lambda كقيمٍ حرفيّةٍ للدوال مع المستقبِل، مثل:
class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // إنشاء الكائن المستقبِل
html.init() // تمرير الكائن المستقبِل إلى lambda
return html
}
html { // بداية lambda
// مع المستقبِل
body() // استدعاء تابع عبر الكائن المستقبِل
}