الفرق بين المراجعتين لصفحة: «Kotlin/functions»
طلا ملخص تعديل |
ط استبدال النص - 'Kotlin Functions' ب'Kotlin Function' |
||
(4 مراجعات متوسطة بواسطة مستخدم واحد آخر غير معروضة) | |||
سطر 15: | سطر 15: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | === المعاملات (Parameters) === | ||
تُعرَّف | تُعرَّف المعاملات بالصيغة المُعتمدَة في لغة Pascal وهي <code>name: type</code> ، ويُفصَل فيما بينها بالفاصلة <code>,</code> كما ويجب التحديد الصريح لنوع كلّ منها، مثل:<syntaxhighlight lang="kotlin"> | ||
fun powerOf(number: Int, exponent: Int) { | fun powerOf(number: Int, exponent: Int) { | ||
... | ... | ||
سطر 22: | سطر 22: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | === الوسائط الافتراضية (Default Arguments) === | ||
قد تحتوي | قد تحتوي المعاملات (parameters) قيمًا افتراضيّةً تُستخدَم عند حذف الوسيط (argument) الموافِق لها، وهذا يسمح بتقليل عمليات التحميل الزائد (overloading) مقارنةً باللغات الأخرى، مثل:<syntaxhighlight lang="kotlin"> | ||
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { | fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { | ||
... | ... | ||
سطر 29: | سطر 29: | ||
</syntaxhighlight>إذ تُعرَّف القيم الافتراضيّة باستخدام المعامِل <code>=</code> بعد النوع مباشرةً وتليه القيمة المطلوبة. | </syntaxhighlight>إذ تُعرَّف القيم الافتراضيّة باستخدام المعامِل <code>=</code> بعد النوع مباشرةً وتليه القيمة المطلوبة. | ||
وتعتمد التوابع (methods) التي تعيد تعريف (override) تابعٍ آخر على القيمة الافتراضيّة ذاتها | وتعتمد التوابع (methods) التي تعيد تعريف (override) تابعٍ آخر على القيمة الافتراضيّة ذاتها للمعاملات الموجودة في التابع الأساسيّ الذي يُعاد تعريفه، حيث تُحذَف تلك القيم من ترويسة التابع الذي يعيد التعريف، كما في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
open class A { | open class A { | ||
open fun foo(i: Int = 10) { ... } | open fun foo(i: Int = 10) { ... } | ||
سطر 37: | سطر 37: | ||
override fun foo(i: Int) { ... } // لا يُسمح بوجود القيم الافتراضيّة هنا | override fun foo(i: Int) { ... } // لا يُسمح بوجود القيم الافتراضيّة هنا | ||
} | } | ||
</syntaxhighlight>وإن سُبِق | </syntaxhighlight>وإن سُبِق المعامل الذي لا يحتوي على قيمةٍ افتراضيّةٍ بمعامل افتراضيٍّ فلا يُمكن حينئذٍ استخدام القيمة الافتراضيّة إلا باستدعاء الدالة بالوسيط المُسمّى (named argument)، أي:<syntaxhighlight lang="kotlin"> | ||
fun foo(bar: Int = 0, baz: Int) { /* ... */ } | fun foo(bar: Int = 0, baz: Int) { /* ... */ } | ||
foo(baz = 1) // ستُستخدَم هنا القيمة الافتراضية | foo(baz = 1) // ستُستخدَم هنا القيمة الافتراضية للمعامل وهي القيمة صفر | ||
</syntaxhighlight>أمّا عند تمرير | </syntaxhighlight>أمّا عند تمرير وسيط [[Kotlin/lambdas|lambda]] أخيرٍ في استدعاء الدالة خارج الأقواس <code>()</code> فيُسمَح حينئذٍ بعدم تمرير قيمةٍ للمعاملات الافتراضيّة، مثل:<syntaxhighlight lang="kotlin"> | ||
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /* ... */ } | fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /* ... */ } | ||
سطر 49: | سطر 49: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | === الوسائط المسماة (Named Arguments) === | ||
تُتيح لغة Kotlin تسمية | تُتيح لغة Kotlin تسمية المعاملات (parameters) في الدوال عند استدعائها، وهذا يُفيد لدى وجود عددٍ كبيرٍ من المعاملات أو القيم الافتراضيّة، لتكن الدالة الآتية مثلًا:<syntaxhighlight lang="kotlin"> | ||
fun reformat(str: String, | fun reformat(str: String, | ||
normalizeCase: Boolean = true, | normalizeCase: Boolean = true, | ||
سطر 58: | سطر 58: | ||
... | ... | ||
} | } | ||
</syntaxhighlight>يكون الاستدعاء باستخدام | </syntaxhighlight>يكون الاستدعاء باستخدام الوسائط الافتراضيّة بالشكل:<syntaxhighlight lang="kotlin"> | ||
reformat(str) | reformat(str) | ||
</syntaxhighlight>وبدون | </syntaxhighlight>وبدون الوسائط الافتراضيّة سيكون الاستدعاء بالشكل:<syntaxhighlight lang="kotlin"> | ||
reformat(str, true, true, false, '_') | reformat(str, true, true, false, '_') | ||
</syntaxhighlight>أمّا باستخدام | </syntaxhighlight>أمّا باستخدام الوسائط المُسمَّاة فستصبح الشيفرة أسهل للقراءة، مثل:<syntaxhighlight lang="kotlin"> | ||
reformat(str, | reformat(str, | ||
normalizeCase = true, | normalizeCase = true, | ||
سطر 69: | سطر 69: | ||
wordSeparator = '_' | wordSeparator = '_' | ||
) | ) | ||
</syntaxhighlight>وعند عدم الحاجة لكل | </syntaxhighlight>وعند عدم الحاجة لكل الوسائط يكون الاستدعاء بالشكل:<syntaxhighlight lang="kotlin"> | ||
reformat(str, wordSeparator = '_') | reformat(str, wordSeparator = '_') | ||
</syntaxhighlight>وعند استدعاء الدالة بكلٍّ من | </syntaxhighlight>وعند استدعاء الدالة بكلٍّ من الوسائط المُسمَّاة المكانيّة (positional) فيجب أن تُوضَع كافّة المتحولات المكانيّة قبل أوّل متحولٍ مُسمّى، أي أنّ الاستدعاء <code>f(1, y = 2)</code> يكون مسموحًا، ولا يُسمح بالاستدعاء <code>f(x = 1, 2)</code>. | ||
كما يمكن تمرير عددٍ متغيّرٍ من | كما يمكن تمرير عددٍ متغيّرٍ من الوسائط (<code>vararg</code>) بالصيغة المُسمّاة، وذلك باستخدام معامل الفصل (إسباق المصفوفة بالمعامل <code>*</code>) كما في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
fun foo(vararg strings: String) { /* ... */ } | fun foo(vararg strings: String) { /* ... */ } | ||
foo(strings = *arrayOf("a", "b", "c")) | foo(strings = *arrayOf("a", "b", "c")) | ||
</syntaxhighlight>لاحظ بأن صيغة | </syntaxhighlight>لاحظ بأن صيغة الوسائط المُسمّاة لا تُستخدَم عند استدعاء الدوال في Java، وذلك لأنّ الشيفرة bytecode في Java لا تحافظ دائمًا على أسماء الوسائط (arguments) للدوال. | ||
=== الدوال التي تعيد النوع <code>Unit</code> === | === الدوال التي تعيد النوع <code>Unit</code> === | ||
سطر 106: | سطر 106: | ||
يجب أن يُذكر النوع المُعاد صراحةً في بُنية الدالة (function block) ما لم يكن من النوع <code>Unit</code> (إضافتها اختياريّة)، وذلك لأنّ Kotlin لا تدعم توقُّع النوع المُعاد من الدالة لأنها قد تحتوي على بعض العمليات المعقَّدة للتحكُّم بالتدفّق (control flow)، ولن يكون من السهل تحديد النوع المُعاد (بالنسبة لقارئ الشيفرة أو حتى المُترجِم [compiler]). | يجب أن يُذكر النوع المُعاد صراحةً في بُنية الدالة (function block) ما لم يكن من النوع <code>Unit</code> (إضافتها اختياريّة)، وذلك لأنّ Kotlin لا تدعم توقُّع النوع المُعاد من الدالة لأنها قد تحتوي على بعض العمليات المعقَّدة للتحكُّم بالتدفّق (control flow)، ولن يكون من السهل تحديد النوع المُعاد (بالنسبة لقارئ الشيفرة أو حتى المُترجِم [compiler]). | ||
=== العدد المتغيّر | === العدد المتغيّر للوسائط (Varargs) === | ||
قد يُحدَّد أحد | قد يُحدَّد أحد المعاملات في الدالة بالمُحدِّد <code>vararg</code> (عادةً يكون الأخير)، مثل:<syntaxhighlight lang="kotlin"> | ||
fun <T> asList(vararg ts: T): List<T> { | fun <T> asList(vararg ts: T): List<T> { | ||
val result = ArrayList<T>() | val result = ArrayList<T>() | ||
سطر 114: | سطر 114: | ||
return result | return result | ||
} | } | ||
</syntaxhighlight>وهذا ما يسمح بعددٍ متغيّرٍ من | </syntaxhighlight>وهذا ما يسمح بعددٍ متغيّرٍ من الوسائط أثناء التمرير، مثل:<syntaxhighlight lang="kotlin"> | ||
val list = asList(1, 2, 3) | val list = asList(1, 2, 3) | ||
</syntaxhighlight>ويكون | </syntaxhighlight>ويكون الوسيط من النوع <code>T</code> والمُحدَّد بالكلمة المفتاحيّة <code>vararg</code> عبارةً عن مصفوفةٍ (array) من عناصر النوع <code>T</code> ، أي أنّ <code>ts</code> في المثال السابق هي مصفوفة من النوع <code>Array<out T></code>. | ||
ولا يُسمَح بأكثر من | ولا يُسمَح بأكثر من معاملٍ واحدٍ متغيّر العدد (محدَّدٍ بالكلمة المفتاحيّة <code>vararg</code>)، فإن لم يكن المعاملَ الأخيرَ بالقائمة فيمكن حينها تمرير قيم المعاملات التالية له بالاعتماد على صيغة الوسائط المُسمَّاة (named arguments) أو عبر تمرير lambda خارج الأقواس <code>()</code> إن كان للمعامل نوع الدالة. | ||
عند استدعاء دالةٍ بعددٍ متغيّرٍ من | عند استدعاء دالةٍ بعددٍ متغيّرٍ من الوسائط (<code>vararg</code>) بالإمكان تمرير الوسائط إفراديّةً متتابعةً أو كعناصر مصفوفةٍ عبر استخدام معامل النشر (إسباق المصفوفة بالمعامل <code>*</code>) كما في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
val a = arrayOf(1, 2, 3) | val a = arrayOf(1, 2, 3) | ||
val list = asList(-1, 0, *a, 4) | val list = asList(-1, 0, *a, 4) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== التدوين | === التدوين الداخلي (Infix notation) === | ||
تسمح لغة Kotlin باعتماد نمط التدوين الداخليّ لاستدعاء الدوال المُحدَّدة بالكلمة المفتاحيّة <code>infix</code> وذلك من خلال حذف المعامل <code>.</code> والأقواس <code>()</code> المُستخدَمَين بالاستدعاء، ويجب أن تحقِّق الدوال من النوع <code>infix</code> الشروط الآتية: | تسمح لغة Kotlin باعتماد نمط التدوين الداخليّ لاستدعاء الدوال المُحدَّدة بالكلمة المفتاحيّة <code>infix</code> وذلك من خلال حذف المعامل <code>.</code> والأقواس <code>()</code> المُستخدَمَين بالاستدعاء، ويجب أن تحقِّق الدوال من النوع <code>infix</code> الشروط الآتية: | ||
* أن تكون دالةً من الصنف (member function) أو دالةً إضافيّةً (extension function) | * أن تكون دالةً من الصنف (member function) أو دالةً إضافيّةً (extension function) | ||
* أن يكون لها | * أن يكون لها معامل واحدٌ فقط | ||
* أن يقبل هذا | * أن يقبل هذا المعامل عددًا متغيّرًا من الوسائط (vararg) وبدون قيمةٍ افتراضيّة. | ||
مثل:<syntaxhighlight lang="kotlin"> | مثل:<syntaxhighlight lang="kotlin"> | ||
infix fun Int.shl(x: Int): Int { | infix fun Int.shl(x: Int): Int { | ||
سطر 146: | سطر 146: | ||
* <code>0 until n * 2</code> و <code>0 until (n * 2)</code> | * <code>0 until n * 2</code> و <code>0 until (n * 2)</code> | ||
* <code>xs union ys as Set<*></code> و <code>xs union (ys as Set<*>)</code> | * <code>xs union ys as Set<*></code> و <code>xs union (ys as Set<*>)</code> | ||
ولكنها بأولويّة أعلى من المعاملين | ولكنها بأولويّة أعلى من المعاملين المنطقيين (boolean) <code>&&</code> و <code>||</code> وعمليات التحقُّق بالمعاملين <code>in</code> و <code>is</code> وبعض المعاملات الأخرى، وبالتالي فإن التعابير الآتية متكافئةٌ أيضًا: | ||
* <code>a && b xor c</code> و <code>a && (b xor c)</code> | * <code>a && b xor c</code> و <code>a && (b xor c)</code> | ||
* <code>a xor b in c</code> و <code>(a xor b) in c</code> | * <code>a xor b in c</code> و <code>(a xor b) in c</code> | ||
وتجدُر الإشارة هنا إلى أنّه يجب تحديد المستقبِل (receiver) | وتجدُر الإشارة هنا إلى أنّه يجب تحديد المستقبِل (receiver) والمعامل (parameter) دائمًا عند استخدام التدوين الداخليّ (infix) للدوال، كما ويجب الاستخدام الصريح (explicit) للكلمة المفتاحيّة <code>this</code> عند استدعاء التابع بالصيغة <code>infix</code> عبر المستقبِل الحاليّ حيث لا يمكن الاستغناء عنها كما هو الحال في الاستدعاءات النظاميّة، وهذا بهدف ضمان عدم حدوث التباس (ambigity) في التحليل (parsing)، كما في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
class MyStringCollection { | class MyStringCollection { | ||
infix fun add(s: String) { /* ... */ } | infix fun add(s: String) { /* ... */ } | ||
سطر 164: | سطر 164: | ||
قد تُعرَّف الدوال في Kotlin بالمستوى الأعلى (top-level) في الملف، ولا حاجة بذلك لإنشاء صنفٍ (class) مُخصَّصٍ لاحتواء هذه الدالة كما هو الحال في لغات البرمجة الأخرى مثل Java و C# و Scala، كما وتدعم لغة Kotlin الدوال المحليّة (local functions) والدوال التابعة للصنف (member functions) والدوال الإضافيّة (extension functions). | قد تُعرَّف الدوال في Kotlin بالمستوى الأعلى (top-level) في الملف، ولا حاجة بذلك لإنشاء صنفٍ (class) مُخصَّصٍ لاحتواء هذه الدالة كما هو الحال في لغات البرمجة الأخرى مثل Java و C# و Scala، كما وتدعم لغة Kotlin الدوال المحليّة (local functions) والدوال التابعة للصنف (member functions) والدوال الإضافيّة (extension functions). | ||
=== الدوال | === الدوال المحلية (Local Functions) === | ||
وهي الدوال المتوضِّعة داخل دالةٍ أخرى مثل:<syntaxhighlight lang="kotlin"> | وهي الدوال المتوضِّعة داخل دالةٍ أخرى مثل:<syntaxhighlight lang="kotlin"> | ||
fun dfs(graph: Graph) { | fun dfs(graph: Graph) { | ||
سطر 175: | سطر 175: | ||
dfs(graph.vertices[0], HashSet()) | dfs(graph.vertices[0], HashSet()) | ||
} | } | ||
</syntaxhighlight>وبإمكان هذه الدالة الوصول | </syntaxhighlight>وبإمكان هذه الدالة الوصول للمتغيرات المحليّة في الدوال الخارجيّة (outer functions) وبالتالي فقد يكون المتغيِّر <code>visited</code> في الشيفرة السابقة محليًا أي:<syntaxhighlight lang="kotlin"> | ||
fun dfs(graph: Graph) { | fun dfs(graph: Graph) { | ||
val visited = HashSet<Vertex>() | val visited = HashSet<Vertex>() | ||
سطر 199: | سطر 199: | ||
== الدوال المُعمَّمة (Generic Functions) == | == الدوال المُعمَّمة (Generic Functions) == | ||
قد تحتوي الدوال على | قد تحتوي الدوال على المعاملات المُعمَّمة التي تُحدَّد عبر الأقواس <code><></code> قبل اسم الدالة بالشكل:<syntaxhighlight lang="kotlin"> | ||
fun <T> singletonList(item: T): List<T> { | fun <T> singletonList(item: T): List<T> { | ||
// ... | // ... | ||
سطر 205: | سطر 205: | ||
</syntaxhighlight>راجع [[Kotlin/generics|الأنواع المُعمَّمة (generics)]] للمزيد من التفاصيل. | </syntaxhighlight>راجع [[Kotlin/generics|الأنواع المُعمَّمة (generics)]] للمزيد من التفاصيل. | ||
== الدوال | == الدوال المباشرة (Inline Functions) == | ||
راجع [[Kotlin/inline functions|الصفحة الخاصّة بها]]. | راجع [[Kotlin/inline functions|الصفحة الخاصّة بها]]. | ||
سطر 215: | سطر 215: | ||
== الدوال التعاوديّة (Tail Recursive Functions) == | == الدوال التعاوديّة (Tail Recursive Functions) == | ||
تدعم لغة Kotlin نمط البرمجة التعاوديّ المُستخدَم في البرمجة الوظائفيّة (functional programming)، مما يسمح لبعض الخوارزميّات التي من المفترض أن تعتمد على الحلقات (loops) بأن تُكتَب بالاعتماد على دالةٍ تعاوديّةٍ بالابتعاد عن مخاطر طفحان المُكدِّس (stack overflow)، فعندما تُحدَّد الدالة بالكلمة المفتاحيّة <code>tailrec</code> (وتكون بالشكل المناسب لذلك)، فسيقوم المُترجِم (compiler) بالاستدعاء التعاوديّ | تدعم لغة Kotlin نمط البرمجة التعاوديّ المُستخدَم في البرمجة الوظائفيّة (functional programming)، مما يسمح لبعض الخوارزميّات التي من المفترض أن تعتمد على الحلقات (loops) بأن تُكتَب بالاعتماد على دالةٍ تعاوديّةٍ بالابتعاد عن مخاطر طفحان المُكدِّس (stack overflow)، فعندما تُحدَّد الدالة بالكلمة المفتاحيّة <code>tailrec</code> (وتكون بالشكل المناسب لذلك)، فسيقوم المُترجِم (compiler) بالاستدعاء التعاوديّ الحلقيّ (loop-based) الأسرع والأكثر فعاليّةً، كما في الشيفرة:<syntaxhighlight lang="kotlin"> | ||
tailrec fun findFixPoint(x: Double = 1.0): Double | tailrec fun findFixPoint(x: Double = 1.0): Double | ||
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x)) | = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x)) | ||
سطر 232: | سطر 232: | ||
* [https://kotlinlang.org/docs/reference/functions.html صفحة الدوال في التوثيق الرسميّ للغة Kotlin] | * [https://kotlinlang.org/docs/reference/functions.html صفحة الدوال في التوثيق الرسميّ للغة Kotlin] | ||
[[تصنيف:Kotlin]] | [[تصنيف:Kotlin]] | ||
[[تصنيف:Kotlin | [[تصنيف:Kotlin Function]] | ||
[[تصنيف:Kotlin Parameters]] | [[تصنيف:Kotlin Parameters]] |
المراجعة الحالية بتاريخ 11:35، 30 أغسطس 2018
التصريح عن الدوال (Function Declarations)
يُصرَّح عن الدوال في لغة Kotlin باستخدام الكلمة المفتاحيّة fun
كما يلي:
fun double(x: Int): Int {
return 2 * x
}
استخدام الدوال (Function Usage)
يكون استدعاء الدوال كما في أيّ لغة برمجةٍ أخرى بالشكل:
val result = double(2)
أمّا استدعاء الدوال من الأصناف فيعتمد على المعامل .
كما في الشيفرة:
Sample().foo() // إنشاء كائنٍ من الصنف واستدعاء الدالة عبره
المعاملات (Parameters)
تُعرَّف المعاملات بالصيغة المُعتمدَة في لغة Pascal وهي name: type
، ويُفصَل فيما بينها بالفاصلة ,
كما ويجب التحديد الصريح لنوع كلّ منها، مثل:
fun powerOf(number: Int, exponent: Int) {
...
}
الوسائط الافتراضية (Default Arguments)
قد تحتوي المعاملات (parameters) قيمًا افتراضيّةً تُستخدَم عند حذف الوسيط (argument) الموافِق لها، وهذا يسمح بتقليل عمليات التحميل الزائد (overloading) مقارنةً باللغات الأخرى، مثل:
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
...
}
إذ تُعرَّف القيم الافتراضيّة باستخدام المعامِل =
بعد النوع مباشرةً وتليه القيمة المطلوبة.
وتعتمد التوابع (methods) التي تعيد تعريف (override) تابعٍ آخر على القيمة الافتراضيّة ذاتها للمعاملات الموجودة في التابع الأساسيّ الذي يُعاد تعريفه، حيث تُحذَف تلك القيم من ترويسة التابع الذي يعيد التعريف، كما في الشيفرة:
open class A {
open fun foo(i: Int = 10) { ... }
}
class B : A() {
override fun foo(i: Int) { ... } // لا يُسمح بوجود القيم الافتراضيّة هنا
}
وإن سُبِق المعامل الذي لا يحتوي على قيمةٍ افتراضيّةٍ بمعامل افتراضيٍّ فلا يُمكن حينئذٍ استخدام القيمة الافتراضيّة إلا باستدعاء الدالة بالوسيط المُسمّى (named argument)، أي:
fun foo(bar: Int = 0, baz: Int) { /* ... */ }
foo(baz = 1) // ستُستخدَم هنا القيمة الافتراضية للمعامل وهي القيمة صفر
أمّا عند تمرير وسيط lambda أخيرٍ في استدعاء الدالة خارج الأقواس ()
فيُسمَح حينئذٍ بعدم تمرير قيمةٍ للمعاملات الافتراضيّة، مثل:
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /* ... */ }
foo(1) { println("hello") } // ستستخدم القيمة الافتراضيّة baz = 1
foo { println("hello") } // ستستخدم القيمتين الافتراضيّتين bar = 0 , baz = 1
الوسائط المسماة (Named Arguments)
تُتيح لغة Kotlin تسمية المعاملات (parameters) في الدوال عند استدعائها، وهذا يُفيد لدى وجود عددٍ كبيرٍ من المعاملات أو القيم الافتراضيّة، لتكن الدالة الآتية مثلًا:
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
...
}
يكون الاستدعاء باستخدام الوسائط الافتراضيّة بالشكل:
reformat(str)
وبدون الوسائط الافتراضيّة سيكون الاستدعاء بالشكل:
reformat(str, true, true, false, '_')
أمّا باستخدام الوسائط المُسمَّاة فستصبح الشيفرة أسهل للقراءة، مثل:
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
وعند عدم الحاجة لكل الوسائط يكون الاستدعاء بالشكل:
reformat(str, wordSeparator = '_')
وعند استدعاء الدالة بكلٍّ من الوسائط المُسمَّاة المكانيّة (positional) فيجب أن تُوضَع كافّة المتحولات المكانيّة قبل أوّل متحولٍ مُسمّى، أي أنّ الاستدعاء f(1, y = 2)
يكون مسموحًا، ولا يُسمح بالاستدعاء f(x = 1, 2)
.
كما يمكن تمرير عددٍ متغيّرٍ من الوسائط (vararg
) بالصيغة المُسمّاة، وذلك باستخدام معامل الفصل (إسباق المصفوفة بالمعامل *
) كما في الشيفرة:
fun foo(vararg strings: String) { /* ... */ }
foo(strings = *arrayOf("a", "b", "c"))
لاحظ بأن صيغة الوسائط المُسمّاة لا تُستخدَم عند استدعاء الدوال في Java، وذلك لأنّ الشيفرة bytecode في Java لا تحافظ دائمًا على أسماء الوسائط (arguments) للدوال.
الدوال التي تعيد النوع Unit
إن لم تُعِد الدالة أيّة قيمةٍ فسيكون النوع المُعاد بالحالة الافتراضيّة Unit
الذي يحتوي على قيمة وحيدةٍ وهي Unit
إذ لا يجب التصريح عن إعادتها في شيفرة الدالة (أي ليس من الضروريّ إضافة return
أو return Unit
)، كما يلي:
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// ليس من الضروري هنا وجود
// 'return' أو 'return Unit'
}
كما أنّ وجود التصريح عن النوع Unit
اختياريّ إذ تكافِئ الشيفرة السابقة الشيفرة:
fun printHello(name: String?) {
...
}
الدوال وحيدة التعبير (Single-Expression functions)
عندما تعيد الدوال تعبيرًا واحدًا فقط تُحذَف الأقواس {}
وتُحدَّد بُنية الدال (وهي التعبير) مباشرةً بعد الرمز =
كما في الدالة:
fun double(x: Int): Int = x * 2
ويكون الذكر الصريح (explicit) للنوع المُعاد اختياريًا عند إمكانية تحديده من قِبل المُترجِم (compiler) كما في الشيفرة:
fun double(x: Int) = x * 2
الذكر الصريح للأنواع المُعادة (Explicit return types)
يجب أن يُذكر النوع المُعاد صراحةً في بُنية الدالة (function block) ما لم يكن من النوع Unit
(إضافتها اختياريّة)، وذلك لأنّ Kotlin لا تدعم توقُّع النوع المُعاد من الدالة لأنها قد تحتوي على بعض العمليات المعقَّدة للتحكُّم بالتدفّق (control flow)، ولن يكون من السهل تحديد النوع المُعاد (بالنسبة لقارئ الشيفرة أو حتى المُترجِم [compiler]).
العدد المتغيّر للوسائط (Varargs)
قد يُحدَّد أحد المعاملات في الدالة بالمُحدِّد vararg
(عادةً يكون الأخير)، مثل:
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts مصفوفة
result.add(t)
return result
}
وهذا ما يسمح بعددٍ متغيّرٍ من الوسائط أثناء التمرير، مثل:
val list = asList(1, 2, 3)
ويكون الوسيط من النوع T
والمُحدَّد بالكلمة المفتاحيّة vararg
عبارةً عن مصفوفةٍ (array) من عناصر النوع T
، أي أنّ ts
في المثال السابق هي مصفوفة من النوع Array<out T>
.
ولا يُسمَح بأكثر من معاملٍ واحدٍ متغيّر العدد (محدَّدٍ بالكلمة المفتاحيّة vararg
)، فإن لم يكن المعاملَ الأخيرَ بالقائمة فيمكن حينها تمرير قيم المعاملات التالية له بالاعتماد على صيغة الوسائط المُسمَّاة (named arguments) أو عبر تمرير lambda خارج الأقواس ()
إن كان للمعامل نوع الدالة.
عند استدعاء دالةٍ بعددٍ متغيّرٍ من الوسائط (vararg
) بالإمكان تمرير الوسائط إفراديّةً متتابعةً أو كعناصر مصفوفةٍ عبر استخدام معامل النشر (إسباق المصفوفة بالمعامل *
) كما في الشيفرة:
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
التدوين الداخلي (Infix notation)
تسمح لغة Kotlin باعتماد نمط التدوين الداخليّ لاستدعاء الدوال المُحدَّدة بالكلمة المفتاحيّة infix
وذلك من خلال حذف المعامل .
والأقواس ()
المُستخدَمَين بالاستدعاء، ويجب أن تحقِّق الدوال من النوع infix
الشروط الآتية:
- أن تكون دالةً من الصنف (member function) أو دالةً إضافيّةً (extension function)
- أن يكون لها معامل واحدٌ فقط
- أن يقبل هذا المعامل عددًا متغيّرًا من الوسائط (vararg) وبدون قيمةٍ افتراضيّة.
مثل:
infix fun Int.shl(x: Int): Int {
// ...
}
// استدعاء الدالة بنمط التدوين الداخليّ
1 shl 2
// وهو مكافئ للصيغة
1.shl(2)
ملاحظة:
للاستدعاءات بصيغة التدوين الداخليّ أولويةٌ (precedence) أدنى من أولويّة كلِّ من المعاملات الحسابيّة (arithmetic operators) وتحويلات الأنواع (type casts) والمعامل rangeTo
، وبالتالي فإن التعابير الآتية متكافئة:
1 shl 2 + 3
و1 shl (2 + 3)
0 until n * 2
و0 until (n * 2)
xs union ys as Set<*>
وxs union (ys as Set<*>)
ولكنها بأولويّة أعلى من المعاملين المنطقيين (boolean) &&
و ||
وعمليات التحقُّق بالمعاملين in
و is
وبعض المعاملات الأخرى، وبالتالي فإن التعابير الآتية متكافئةٌ أيضًا:
a && b xor c
وa && (b xor c)
a xor b in c
و(a xor b) in c
وتجدُر الإشارة هنا إلى أنّه يجب تحديد المستقبِل (receiver) والمعامل (parameter) دائمًا عند استخدام التدوين الداخليّ (infix) للدوال، كما ويجب الاستخدام الصريح (explicit) للكلمة المفتاحيّة this
عند استدعاء التابع بالصيغة infix
عبر المستقبِل الحاليّ حيث لا يمكن الاستغناء عنها كما هو الحال في الاستدعاءات النظاميّة، وهذا بهدف ضمان عدم حدوث التباس (ambigity) في التحليل (parsing)، كما في الشيفرة:
class MyStringCollection {
infix fun add(s: String) { /* ... */ }
fun build() {
this add "abc" // استخدام صحيح
add("abc") // استخدام صحيح
add "abc" // استخدام غير صحيح، يجب تحديد المستقبِل
}
}
مجال الدوال (Function Scope)
قد تُعرَّف الدوال في Kotlin بالمستوى الأعلى (top-level) في الملف، ولا حاجة بذلك لإنشاء صنفٍ (class) مُخصَّصٍ لاحتواء هذه الدالة كما هو الحال في لغات البرمجة الأخرى مثل Java و C# و Scala، كما وتدعم لغة Kotlin الدوال المحليّة (local functions) والدوال التابعة للصنف (member functions) والدوال الإضافيّة (extension functions).
الدوال المحلية (Local Functions)
وهي الدوال المتوضِّعة داخل دالةٍ أخرى مثل:
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: Set<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
وبإمكان هذه الدالة الوصول للمتغيرات المحليّة في الدوال الخارجيّة (outer functions) وبالتالي فقد يكون المتغيِّر visited
في الشيفرة السابقة محليًا أي:
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
الدوال التابعة للأصناف (Member Functions)
تُعرَّف داخل الصنف (class) أو الكائن (object) بالشكل:
class Sample() {
fun foo() { print("Foo") }
}
وتُستدعَى عبر المعامل .
كما يلي:
Sample().foo() // إنشاء كائن من الصنف واستدعاء الدالة عبره
راجع الأصناف والوراثة (inheritance) للمزيد من المعلومات عن الأصناف وإعادة تعريف (overriding) العناصر.
الدوال المُعمَّمة (Generic Functions)
قد تحتوي الدوال على المعاملات المُعمَّمة التي تُحدَّد عبر الأقواس <>
قبل اسم الدالة بالشكل:
fun <T> singletonList(item: T): List<T> {
// ...
}
راجع الأنواع المُعمَّمة (generics) للمزيد من التفاصيل.
الدوال المباشرة (Inline Functions)
راجع الصفحة الخاصّة بها.
الدوال الإضافيّة (Extension Functions)
راجع الصفحة الخاصّة بها.
الدوال بالمرتبة الأعلى (Higher-Order) و Lambdas
راجع الصفحة الخاصّة بها.
الدوال التعاوديّة (Tail Recursive Functions)
تدعم لغة Kotlin نمط البرمجة التعاوديّ المُستخدَم في البرمجة الوظائفيّة (functional programming)، مما يسمح لبعض الخوارزميّات التي من المفترض أن تعتمد على الحلقات (loops) بأن تُكتَب بالاعتماد على دالةٍ تعاوديّةٍ بالابتعاد عن مخاطر طفحان المُكدِّس (stack overflow)، فعندما تُحدَّد الدالة بالكلمة المفتاحيّة tailrec
(وتكون بالشكل المناسب لذلك)، فسيقوم المُترجِم (compiler) بالاستدعاء التعاوديّ الحلقيّ (loop-based) الأسرع والأكثر فعاليّةً، كما في الشيفرة:
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
إذ تحسِب هذه الدالة نقطة الثبات (fixpoint) في التابع الرياضيّ cosine (وهي قيمةٌ ثابتةٌ)، وبالتالي فإنّ الدالة التعاوديّة تستدعي الدالة Math.cos
بشكل متكرِّرٍ بدءًا من القيمة 1.0 وحتى الوصول إلى النقطة التي لا تتغيّر فيها القيمة (وهي 0.7390851332151607). تشابه الشيفرة السابقة الشيفرة التقليديّة المُعتادَة:
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (x == y) return x
x = y
}
}
وللالتزام بمُتطلَّبات الكلمة المفتاحيّة tailrec
يجب أن يكون استدعاء الدالة لذاتها كآخر عمليةٍ ممكنةٍ، إذ لا يُسمَح بوجود أيّ شيفرة بعد ذلك الاستدعاء أو في أيٍّ من أجزاء الاستثناء try
و catch
و finally
، وضع بالحُسبان أنّ Kotlin تدعم التعاوديّة فقط في JVM في الإصدار الحاليّ.