الفرق بين المراجعتين ل"Kotlin/functions"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(أنشأ الصفحة ب'<noinclude>{{DISPLAYTITLE:الدوال (Functions) في لغة Kotlin}}</noinclude> == التصريح عن الدوال (Function Declarations) == يُصرَّح عن ا...')
 
(إضافة فقرات)
سطر 79: سطر 79:
 
</syntaxhighlight>لاحظ بأن صيغة المتحولات المُسمّاة لا تُستخدَم عند استدعاء الدوال في Java، وذلك لأنّ الشيفرة bytecode في Java لا تحافظ دائمًا على أسماء المتحولات الوسيطة للدوال.
 
</syntaxhighlight>لاحظ بأن صيغة المتحولات المُسمّاة لا تُستخدَم عند استدعاء الدوال في Java، وذلك لأنّ الشيفرة bytecode في Java لا تحافظ دائمًا على أسماء المتحولات الوسيطة للدوال.
  
 +
=== الدوال التي تعيد النوع <code>Unit</code> ===
 +
إن لم تُعِد الدالة أيّة قيمةٍ فسيكون النوع المُعاد بالحالة الافتراضيّة <code>Unit</code> الذي يحتوي على قيمة وحيدةٍ وهي <code>Unit</code> إذ لا يجب التصريح عن إعادتها في شيفرة الدالة (أي ليس من الضروريّ إضافة <code>return</code> أو <code>return Unit</code>)، كما يلي:<syntaxhighlight lang="kotlin">
 +
fun printHello(name: String?): Unit {
 +
    if (name != null)
 +
        println("Hello ${name}")
 +
    else
 +
        println("Hi there!")
 +
    // ليس من الضروري هنا وجود
 +
    // 'return' أو 'return Unit'
 +
 +
}
 +
</syntaxhighlight>كما أنّ وجود التصريح عن النوع <code>Unit</code> اختياريّ إذ تكافِئ الشيفرة السابقة الشيفرة:<syntaxhighlight lang="kotlin">
 +
fun printHello(name: String?) {
 +
    ...
 +
}
 +
</syntaxhighlight>
 +
 +
=== الدوال وحيدة التعبير (Single-Expression functions) ===
 +
عندما تعيد الدوال تعبيرًا واحدًا فقط تُحذَف الأقواس <code>{}</code> وتُحدَّد بُنية الدال (وهي التعبير) مباشرةً بعد الرمز <code>=</code> كما في الدالة:<syntaxhighlight lang="kotlin">
 +
fun double(x: Int): Int = x * 2
 +
</syntaxhighlight>ويكون الذكر الصريح (explicit) للنوع المُعاد اختياريًا عند إمكانية تحديده من قِبل المُترجِم (compiler) كما في الشيفرة:<syntaxhighlight lang="kotlin">
 +
fun double(x: Int) = x * 2
 +
</syntaxhighlight>
 +
 +
=== الذكر الصريح للأنواع المُعادة (Explicit return types) ===
 +
يجب أن يُذكر النوع المُعاد صراحةً في بُنية الدالة (function block) ما لم يكن من النوع <code>Unit</code> (إضافتها اختياريّة)، وذلك لأنّ Kotlin لا تدعم توقُّع النوع المُعاد من الدالة لأنها قد تحتوي على بعض العمليات المعقَّدة للتحكُّم بالتدفّق (control flow)، ولن يكون من السهل تحديد النوع المُعاد (بالنسبة لقارئ الشيفرة أو حتى المُترجِم [compiler]).
 +
 +
=== العدد المتغيّر للمتحولات (Varargs) ===
 +
قد يُحدَّد أحد المتحولات في الدالة بالمُحدِّد <code>vararg</code> (عادةً يكون الأخير)، مثل:<syntaxhighlight lang="kotlin">
 +
fun <T> asList(vararg ts: T): List<T> {
 +
    val result = ArrayList<T>()
 +
    for (t in ts) // ts مصفوفة
 +
        result.add(t)
 +
    return result
 +
}
 +
</syntaxhighlight>وهذا ما يسمح بعددٍ متغيّرٍ من المتحولات أثناء التمرير، مثل:<syntaxhighlight lang="kotlin">
 +
val list = asList(1, 2, 3)
 +
</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 list = asList(-1, 0, *a, 4)
 +
</syntaxhighlight>
 +
 +
=== التدوين الداخليّ (Infix notation) ===
 +
تسمح لغة Kotlin باعتماد نمط التدوين الداخليّ لاستدعاء الدوال المُحدَّدة بالكلمة المفتاحيّة <code>infix</code> وذلك من خلال حذف المعامل <code>.</code> والأقواس <code>()</code> المُستخدَمَين بالاستدعاء، ويجب أن تحقِّق  الدوال من النوع <code>infix</code> الشروط الآتية:
 +
* أن تكون دالةً من الصنف (member function) أو دالةً إضافيّةً (extension function)
 +
* أن يكون لها متحولٌ واحدٌ فقط
 +
* أن يقبل هذا المتحول عددًا متغيّرًا من المتحولات (vararg) وبدون قيمةٍ افتراضيّة.
 +
مثل:<syntaxhighlight lang="kotlin">
 +
infix fun Int.shl(x: Int): Int {
 +
    // ...
 +
}
 +
 +
// استدعاء الدالة بنمط التدوين الداخليّ
 +
1 shl 2
 +
 +
// وهو مكافئ للصيغة
 +
1.shl(2)
 +
</syntaxhighlight>'''ملاحظة:'''
 +
 +
للاستدعاءات بصيغة التدوين الداخليّ أولويةٌ (precedence) أدنى من أولويّة كلِّ من المعاملات الحسابيّة (arithmetic operators) وتحويلات الأنواع (type casts) والمعامل <code>rangeTo</code> ، وبالتالي فإن التعابير الآتية متكافئة:
 +
* <code>‎1 shl 2 + 3‎</code> و <code>‎1 shl (2 + 3)‎</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>
 +
ولكنها بأولويّة أعلى من المعاملين الثنائيَين (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 xor b in c</code> و <code>‎(a xor b) in c</code>
 +
وتجدُر الإشارة هنا إلى أنّه يجب تحديد المستقبِل (receiver) والمتحوّل الوسيط (parameter) دائمًا عند استخدام التدوين الداخليّ (infix) للدوال، كما ويجب الاستخدام الصريح (explicit) للكلمة المفتاحيّة <code>this</code> عند استدعاء التابع بالصيغة <code>infix</code> عبر المستقبِل الحاليّ حيث لا يمكن الاستغناء عنها كما هو الحال في الاستدعاءات النظاميّة، وهذا بهدف ضمان عدم حدوث التباس (ambigity) في التحليل (parsing)، كما في الشيفرة:<syntaxhighlight lang="kotlin">
 +
class MyStringCollection {
 +
    infix fun add(s: String) { /* ... */ }
 +
   
 +
    fun build() {
 +
        this add "abc"  // استخدام صحيح
 +
        add("abc")      // استخدام صحيح
 +
        add "abc"        // استخدام غير صحيح، يجب تحديد المستقبِل
 +
    }
 +
}
 +
</syntaxhighlight>
 
== مصادر<span> </span> ==
 
== مصادر<span> </span> ==
 
* [https://kotlinlang.org/docs/reference/functions.html صفحة الدوال في التوثيق الرسميّ للغة Kotlin]
 
* [https://kotlinlang.org/docs/reference/functions.html صفحة الدوال في التوثيق الرسميّ للغة Kotlin]

مراجعة 05:10، 24 مارس 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 لا تحافظ دائمًا على أسماء المتحولات الوسيطة للدوال.

الدوال التي تعيد النوع 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"        // استخدام غير صحيح، يجب تحديد المستقبِل
    }
}

مصادر