الفرق بين المراجعتين لصفحة: «Kotlin/inline functions»

من موسوعة حسوب
أنشأ الصفحة ب'<noinclude>{{DISPLAYTITLE:الدوال السطريّة (Inline Functions) في لغة Kotlin}}</noinclude> == الدوال السطريّة (Inline Functions) == يفر...'
 
تدقيق وتحسين وإعادة صياغة
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:الدوال السطريّة (Inline Functions) في لغة Kotlin}}</noinclude>
<noinclude>{{DISPLAYTITLE:الدوال السطريّة (Inline Functions) في لغة Kotlin}}</noinclude>
== الدوال السطريّة (Inline Functions) ==
== الدوال المباشرة (Inline Functions) ==
يفرض استخدام الدوال من المرتبة الأعلى بعض من المصاعب أثناء التنفيذ حيث تُعدُّ كل دالة كائنًا ضمن نطاقٍ مغلقٍ (يشمل المتحولات التي يمكن الوصول إليها في بنية الدالة)، كما ويكون هناك تكلفة إضافية عند تخصيص جزء من الذاكرة (لكلٍ من كائنات الدوال والأصناف) والاستدعاءات الوهمية أثناء التنفيذ.
ينتُج عن استخدام [[Kotlin/lambdas|الدوال من المرتبة الأعلى (higher-order functions)]] بعض التأثيرات السلبيّة أثناء التنفيذ (runtime)، إذ تُعدُّ كل دالة كائنًا (object) ضمن نطاقٍ مغلقٍ (closure) يشمل المتحولات التي يمكن الوصول إليها في بُنية الدالة، كما ويتطلَّب ذلك تكلفةً إضافيّةً عند تخصيص جزءٍ من الذاكرة (لكلٍ من كائنات الدوال والأصناف [classes]) وعند الاستدعاءات الوهمية (virtual calls) أثناء التنفيذ.


وقد يُحدُّ من هذه المشاكل باللجوء إلى تعابير lambda السطرية، وتُعدُّالدالة lock()‎ مثالًا عن مثل هذه الحالات التي يمكن جعلها سطريّة في مواقع الاستدعاء، مثل:<syntaxhighlight lang="kotlin">
وقد يُحدُّ من هذه المشاكل باللجوء إلى تعابير lambda المباشرة، إذ تُعدُّ الدالة <code>lock()</code>‎ مثالًا جيدًا لمثل هذه الحالات التي تُجعَل فيها lambda مباشرةً في موقع الاستدعاء، مثل:<syntaxhighlight lang="kotlin">
lock(l) { foo() }
lock(l) { foo() }
</syntaxhighlight>إذ بدلًا من إنشاء كائن الدالة للمتحولات وتوليد الاستدعاء يمكن أن يقوم المترجم بالشيفرة الآتية:<syntaxhighlight lang="kotlin">
</syntaxhighlight>إذ بدلًا من إنشاء كائن الدالة للمتحوّلات وتوليد الاستدعاء يمكن أن يقوم المٌترجِم (compiler) بالشيفرة الآتية:<syntaxhighlight lang="kotlin">
l.lock()
l.lock()
try {
try {
سطر 13: سطر 13:
     l.unlock()
     l.unlock()
}
}
</syntaxhighlight>وللقيام بذلك يجب تحديد الدالة الدالة lock()‎ بالمحدد <code>inline</code> كما يلي:<syntaxhighlight lang="kotlin">
</syntaxhighlight>حيث يجب تحديد الدالة <code>lock()‎</code> بالمُحدِّد <code>inline</code> كما يلي:<syntaxhighlight lang="kotlin">
inline fun <T> lock(lock: Lock, body: () -> T): T {
inline fun <T> lock(lock: Lock, body: () -> T): T {
     // ...
     // ...
}
}
</syntaxhighlight>والذي يؤثر على كلٍ من الدالة ذاتها وتعبير lambda الممرر لها، حيث سيتم تضمين كل ذلك سطريًا في موقع الاستدعاء.
</syntaxhighlight>والذي سيؤثّر على كلٍّ من الدالة -ذاتها- وتعبير lambda المُمرَّر لها، حيث سيُضمَّن كل ذلك مباشرةً في موقع الاستدعاء، وقد تصبح الشيفرة أكثر طولًا، ولكن إن تمّ هذا بطريقةٍ سديدةٍ (بتجنُّب التضمين المباشر للدوال الضخمة) فسيُجدي ذلك في الأداء، وخاصة عند مواقع الاستدعاء في الحلقات (loops).


وقد يجعل ذلك الشيفرة أطول ولكن إن كان القيام بذلك منطقي (كتجنب الدوال السطرية الطويلة) فسيصب ذلك لصالح الأداء وخاصة عند مواقع الاستدعاء في الحلقات.
== المُحدِّد <code>noinline</code> ==
 
قد نحتاج أحيانًا إلى التضمين المباشر "للبعض" (والبعض فقط) من تعابير lambda (المُمرَّرة إلى الدوال المباشرة)، يُستخدَم المُحدِّد <code>noinline</code> عندئذٍ لتحديد العناصر التي لا تخضع للتضمين المباشر، كما في الشيفرة:<syntaxhighlight lang="kotlin">
== المحدد <code>noinline</code> ==
قد تحتاج في بعض الأحيان إلى تضمين البعض من تعابير lambda (الممررة إلى الدوال السطرية) سطريًا فيمكن حينئذٍ استخدام المحدد noinle لتحديد العناصر التي لن تجعل سطرية، كما في الشيفرة:<syntaxhighlight lang="kotlin">
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
     // ...
     // ...
}
}
</syntaxhighlight>إذ يسمح باستدعاء تعابير lambda السطرية داخل الدوال السطرية فقط أو تمريرها كمتحول سطرية، أما العناصر المحدد بالكلمة noinline فيمكن أن تعامَل بأي طريقة كانت كتخزينها في الحقول أو تمريرها أو ... إلخ.
</syntaxhighlight>إذ يُسمَح باستدعاء تعابير lambda المباشرة داخل الدوال المباشرة فقط أو بتمريرها كمتحولٍ مباشرٍ، أمّا العناصر المحُدَّدة بالكلمة المفتاحيّة <code>noinline</code> فيمكن أن تُعامَل بأيّة طريقة كانت، كتخزينها في الحقول (fields) أو تمريرها أو ... إلخ.


ويجدر الإشارة إلى أن المترجم سيعلم بتحذيرٍ إن احتوت الدالة السطرية على متحولات غير سطرية أو متحولات من النوع reified، لأن مثل هذا التضمين السطري لن يكون نافعًا، ويمكن تجاوز هذا التحذير عند التأكد من الحاجة الملحة له باستخدام التوصيف ‎<code>@Suppress("NOTHING_TO_INLINE")</code>‎.
وتجدر الإشارة إلى أنّ المترجِم سيُعلِم بتحذيرٍ إن احتوت الدالة المباشرة على متحولاتٍ غير مباشرة أو متحولات نوعٍ ليست reified (كما ستُشرَح لاحقًا)، لأنّ ذلك لا يُجدي نفعًا، وبالإمكان تجاوز هذا التحذير -عند الحاجة المُلحَّة للتضمين المباشر- باستخدام التوصيف (annotation) الآتي: ‎<code>@Suppress("NOTHING_TO_INLINE")</code>‎.


== أوامر العودة غير المحليّة (Non-local returns) ==
== أوامر العودة غير المحليّة (Non-local returns) ==
نستطيع في Kotlin استخدام الأمر return غير المقيّد للخروج من الدالة المُسمّاة أو الدالة المجهولة، وبالتالي فللعودة من تعبير lambda يجب استخدام تسمية (label)، ولا يسمح بأمر return مفردًا كما هو داخل التعبير، لأن التعبير لا يقوم بالعودة من الدالة المحيطة، مثل:<syntaxhighlight lang="kotlin">
نستطيع في Kotlin استخدام الأمر <code>return</code> غير المقيّد (unqualified) للخروج من الدالة المُسمّاة (named function) أو الدالة المجهولة (anonymous function)، أمّا للعودة من تعبير lambda يجب استخدام [[Kotlin/returns|التسمية (label)]]، ولا يُسمَح بأمر <code>return</code> مفردًا كما هو داخل التعبير، لأن تعبير lambda غير قادرٍ على العودة من الدالة المحيطة به (enclosing)، مثل:<syntaxhighlight lang="kotlin">
fun foo() {
fun foo() {
     ordinaryFunction {
     ordinaryFunction {
سطر 37: سطر 35:
     }
     }
}
}
</syntaxhighlight>أما إن كانت الدالة التي يمرر لها تعبير lambda سطرية فيمكن حينئذٍ جعل أمر العودة سطريًا كذلك، وبالتالي فإن الشيفرة الآتية مسموحة:<syntaxhighlight lang="kotlin">
</syntaxhighlight>أمّا إن كانت الدالة التي يُمرَّر لها التعبير lambda مباشرةً فيمكن عندئذٍ جعل أمر العودة مباشرًا كذلك، وبالتالي تكون الشيفرة الآتية صحيحةً:<syntaxhighlight lang="kotlin">
fun foo() {
fun foo() {
     inlineFunction {
     inlineFunction {
سطر 44: سطر 42:
     }
     }
}
}
</syntaxhighlight>ومثل أوامر العودة هذه (الموجودة في تعبير lambda ولكنها تخرج من الدالة المحيطة بها) تسمى بأوامر الرجوع غير المحلية وهي البنى المعتادة في الحلقات التي تحيط بها الدوال عادة، مثل:<syntaxhighlight lang="kotlin">
</syntaxhighlight>وتُسمّى أوامر الرجوع هذه (والموجودة في تعبير lambda ولكنّها المسؤولة عن العودة من الدالة المحيطة به) بأوامر الرجوع غير المحليّة، وهذا ما تكون عليه عادةً بُنية الحلقات التي تحيط بها الدوال المباشرة، مثل:<syntaxhighlight lang="kotlin">
fun hasZeros(ints: List<Int>): Boolean {
fun hasZeros(ints: List<Int>): Boolean {
     ints.forEach {
     ints.forEach {
سطر 51: سطر 49:
     return false
     return false
}
}
</syntaxhighlight>ويمكن لبعض الدوال السطرية استدعاء تعابير lambda الممررة لها كمتحولات، ليس مباشرةً من بنية الدالة وإنما من أي سياق تنفيذيّ آخر كالكائن المحلي أو دالة متداخلة، وبهذه الحالات لا يسمح بالتحكم بالتدفق غير المحلي في تعابير lamnda، إذ يجب أن تكون محددة بالكلمة المفتاحية <code>crossinline</code> كما في الشيفرة:<syntaxhighlight lang="kotlin">
</syntaxhighlight>ويمكن لبعض الدوال المباشرة استدعاء تعابير lambda المُمرَّرة لها كمتحولاتٍ، ولا يتمّ ذلك من بُنية الدالة مباشرةً (directly) وإنمّا من أي سياقٍ تنفيذيّ آخر كالكائن المحلي (local object) أو دالةٍ متداخلةٍ (nested)، ولا يُسمَح حينها بالتحكم بالتدفق غير المحليّ في تعابير lamnda، إذ يجب أن يكون متحول lambda مُحدَّدًا بالكلمة المفتاحيّة <code>crossinline</code> كما في الشيفرة:<syntaxhighlight lang="kotlin">
inline fun f(crossinline body: () -> Unit) {
inline fun f(crossinline body: () -> Unit) {
     val f = object: Runnable {
     val f = object: Runnable {
سطر 58: سطر 56:
     // ...
     // ...
}
}
</syntaxhighlight>لا يسمح باستخدام الأمرين break و continue في دوال lambda السطرية في Kotlin وقد يسمح بذلك في الإصدارات القادمة.
</syntaxhighlight>ملاحظة: إنّ استخدام الأمرين <code>break</code> و <code>continue</code> في دوال lambda المباشرة في Kotlin غير ممكنٍ، وقد يُسمَح بذلك في الإصدارات القادمة.


== المتحولات من النوع Reified ==
== المتحوّلات من النوع <code>reified</code> ==
قد نحتاج أحيانًا للوصول إلى نوعٍ ممرر كمتحول، مثل:<syntaxhighlight lang="kotlin">
قد نحتاج أحيانًا للوصول إلى نوعٍ مُمرَّر كمتحولٍ وسيطٍ (parameter)، مثل:<syntaxhighlight lang="kotlin">
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
     var p = parent
     var p = parent
سطر 70: سطر 68:
     return p as T?
     return p as T?
}
}
</syntaxhighlight>حيث سيتم المرور بعقد الشجرة وتستخدم الانعكاسات هنا للتحقق من النوع في تلك العقد وسيكون موقع الاستدعاء بالشكل:<syntaxhighlight lang="kotlin">
</syntaxhighlight>إذ سيجري عبور الشجرة حيث ستُستخدَم الانعكاسات (reflections) للتحقُّق من النوع في العقد، وسيكون موقع الاستدعاء بالشكل:<syntaxhighlight lang="kotlin">
treeNode.findParentOfType(MyTreeNode::class.java)
treeNode.findParentOfType(MyTreeNode::class.java)
</syntaxhighlight>وما نريده فعليًا هو تمرير النوع لهذه الدالة، أي استدعائها كما يلي:<syntaxhighlight lang="kotlin">
</syntaxhighlight>وما نريده فعليًا هو تمرير النوع لهذه الدالة، أي استدعاؤها كما يلي:<syntaxhighlight lang="kotlin">
treeNode.findParentOfType<MyTreeNode>()
treeNode.findParentOfType<MyTreeNode>()
</syntaxhighlight>للقيام بذلك تدعم الدوال السطرية المتحولات من النوع reified وبالتالي من الممكن كتابة الشيفرة بالشكل:<syntaxhighlight lang="kotlin">
</syntaxhighlight>للقيام بذلك تُستخدَم المتحولات من النوع <code>reified</code> ، وتصبح الشيفرة بالشكل:<syntaxhighlight lang="kotlin">
inline fun <reified T> TreeNode.findParentOfType(): T? {
inline fun <reified T> TreeNode.findParentOfType(): T? {
     var p = parent
     var p = parent
سطر 82: سطر 80:
     return p as T?
     return p as T?
}
}
</syntaxhighlight>إذ تُقيّد متحولات النوع بالمحدد <code>reified</code> مما يجعلها متاحة داخل الدالة وكأنها صنف عاديّ. وبما أن الدالة سطرية فلا حاجة لأي انعكاس هنا، ويتاح استخدام المعاملات مثل <code>‎!is</code> و<code>as</code>كما ويمكن استدعاؤها كما ذكر سابقًا بالصيغة: <code>myTree.findParentOfType<MyTreeNodeType>()</code>‎.
</syntaxhighlight>إذ تُقيَّد متحولات النوع بالمُحدِّد <code>reified</code> مما يجعلها متاحةً داخل الدالة وكأنها صنف عاديّ. وبما أن الدالة مباشرةٌ فلا حاجة لأي انعكاس (reflection) هنا، ويُتاح استخدام المعاملات مثل <code>‎!is</code> و<code>as</code> كذلك، كما ويمكن استدعاؤها -كما ذُكر سابقًا- بالصيغة: <code>myTree.findParentOfType<MyTreeNodeType>()</code>‎.


وعلى الرغم من أنه لا حاجة للانعكاسات في كثير من الحالات، فما يزال ممكنًا استخدامها مع متحولات النوع reified كما يلي:<syntaxhighlight lang="kotlin">
وعلى الرغم من أنه لا حاجة للانعكاسات في كثيرٍ من الحالات، فما يزال استخدامها ممكنًا مع متحولات النوع <code>reified</code> كما يلي:<syntaxhighlight lang="kotlin">
inline fun <reified T> membersOf() = T::class.members
inline fun <reified T> membersOf() = T::class.members


سطر 90: سطر 88:
     println(membersOf<StringBuilder>().joinToString("\n"))
     println(membersOf<StringBuilder>().joinToString("\n"))
}
}
</syntaxhighlight>ولا يمكن للدوال العادية متحولاتٍ من النوع reified إذ لا يمكن للنوع الذي لا يملك تمثيلًا تنفيذيًا (كمتحول من النوع non-reified  أو النوع الزائف مثل <code>Nothing</code>) أن يستخدم كمتحولٍ من النوع reified.
</syntaxhighlight>ولا يمكن أن تحتوي الدوال العادية على متحولاتٍ من النوع <code>reified</code> ؛ لأنه  لا يُسمَح للنوع الذي لا يملك تمثيلًا تنفيذيًا (كالمتحول الذي لا يكون <code>reified</code> أو النوع الزائف مثل <code>Nothing</code>) أن يكون من النوع <code>reified</code>.


== الخاصّيّات السطرية (بدءًا من الإصدار 1.1) ==
== الخاصّيّات المباشرة (بدءًا من الإصدار 1.1) ==
يمكن استخدام الكلمة المفتاحية <code>inline</code> للوصول إلى الخاصيات التي ليس لها حقول مساعدة، ويمكن توصيف الوصول إلى الخاصية بشكل منفردٍ كما يلي:<syntaxhighlight lang="kotlin">
تُستخدَم الكلمة المفتاحيّة <code>inline</code> للوصول إلى الخاصّيّات التي ليس لها حقولٌ مساعدةٌ (backing fields)، وبالإمكان توصيف عمليات الوصول بشكلٍ منفصلٍ عن بعضهما كما يلي:<syntaxhighlight lang="kotlin">
val foo: Foo
val foo: Foo
     inline get() = Foo()
     inline get() = Foo()
سطر 100: سطر 98:
     get() = ...
     get() = ...
     inline set(v) { ... }
     inline set(v) { ... }
</syntaxhighlight>كما ويمكن توصيف الخاصية بمجملها وهذا يجعل كلًا من <code>get</code> <code>وset</code> سطريتين:<syntaxhighlight lang="kotlin">
</syntaxhighlight>كما ويمكن توصيف الخاصية بمجملها، وهذا يجعل كلًا من <code>get</code> و<code>set</code> مباشرتين:<syntaxhighlight lang="kotlin">
inline var bar: Bar
inline var bar: Bar
     get() = ...
     get() = ...
     set(v) { ... }
     set(v) { ... }
</syntaxhighlight>إذ تضمن سطريًا في موقع الاستدعاء كما لو أنها دالة سطرية عادية.
</syntaxhighlight>إذ تُضمّن مباشرةً في موقع الاستدعاء كما لو أنها دالةٌ مباشرةٌ عاديّة.
 
== التقييدات على الدوال السطريات في الواجهات العامة (public API) ==
عندما تكون الدالة السطرية من النوع <code>public</code> أو <code>protected</code> وليست جزءًا من تصريحٍ من النوع <code>private</code> أو <code>internal</code> فإنها حينئذٍ تعد واجهة عامة للوحدة، وبالتالي يمكن استدعاؤها من أي وحدة أخرى كما ويمكن جعلها سطرية في مواقع الاستدعاء.


وهذا سيفرض بعض المخاطر من ناحية توافقية التبديل الثنائية التي تحدث نتيجة بعض التغييرات في الوحدة التي تصرح عن الدالة السطرية إن كان استدعاء الوحدة لا يترجم ثانية بعد ذلك التغيير.
== التقييدات على الدوال المباشراة في الواجهات العامة (Public API) ==
عندما تكون الدالة المباشرة من النوع <code>public</code> أو <code>protected</code> وليست جزءًا من تصريحٍ من النوع <code>private</code> أو <code>internal</code> فإنها حينئذٍ تُعدُّ واجهةً عامةً للوحدة (module)، وبالتالي فمن الممكن استدعاؤها من أيّ وحدةٍ أخرى، كما ويمكن جعلها مباشرةً في مواقع الاستدعاء، لكنّ هذا سيفرض بعض المخاطر من ناحية عدم التوافق الثنائيّ (binary incompatibility) والذي يحدث نتيجةً لبعض التغييرات في الوحدة التي تصرِّح عن الدالة المباشرة، وذلك إن كان استدعاء الوحدة لا يُترجَم ثانيةً بعد ذلك التغيير.


وللحد من هذه المخاطر، لا يسمح باستخدام تصريحات الواجهات غير العامة من قِبل الدوال السطرية في واجهات API العامّة أي لا يسمح لها بالتصريحات من النوع <code>private</code> أو <code>internal</code> في بنيتها.
وللحد من هذه المخاطر لا يُسمح للدوال المباشرة في واجهات API العامّة باستخدام تصريحات الواجهات غير العامّة (non-public-API declarations)، أي لا يُسمح لها بالتصريحات من النوعين <code>private</code> و <code>internal</code> في بُنيتها.


ويسمح بإضافة التوصيف ‎<code>@PublishedApi</code> للتصريح من النوع <code>internal</code> مما سيسمح باستخدامه في الدوال السطرية في الواجهات العامّة، وعند تحديد الدالة السطرية من النوع <code>internal</code> بالتوصيف ‎<code>@PublishedApi</code> فسيتمّ التحقق من بنية الدالة حينها وكأنها عامة.
وتُتاح إضافة التوصيف (annotation) بالصيغة ‎<code>@PublishedApi</code> إلى التصريح من النوع <code>internal</code> ، ممّا سيسمح باستخدامه في الدوال المباشرة في الواجهات العامّة، وعند تحديد الدالة المباشرة من النوع <code>internal</code> (عبر التوصيف ‎<code>@PublishedApi</code>) فسيُتحقَّق من بُنيتها وكأنها عامّة (public).


== مصادر<span> </span> ==
== مصادر<span> </span> ==
* [https://kotlinlang.org/docs/reference/inline-functions.html صفحة الدوال السطريّة في التوثيق الرسميّ للغة Kotlin]
* [https://kotlinlang.org/docs/reference/inline-functions.html صفحة الدوال المباشرة في التوثيق الرسميّ للغة Kotlin]
[[تصنيف:Kotlin]]
[[تصنيف:Kotlin]]
[[تصنيف:Kotlin Functions]]
[[تصنيف:Kotlin Functions]]

مراجعة 11:53، 25 مارس 2018

الدوال المباشرة (Inline Functions)

ينتُج عن استخدام الدوال من المرتبة الأعلى (higher-order functions) بعض التأثيرات السلبيّة أثناء التنفيذ (runtime)، إذ تُعدُّ كل دالة كائنًا (object) ضمن نطاقٍ مغلقٍ (closure) يشمل المتحولات التي يمكن الوصول إليها في بُنية الدالة، كما ويتطلَّب ذلك تكلفةً إضافيّةً عند تخصيص جزءٍ من الذاكرة (لكلٍ من كائنات الدوال والأصناف [classes]) وعند الاستدعاءات الوهمية (virtual calls) أثناء التنفيذ.

وقد يُحدُّ من هذه المشاكل باللجوء إلى تعابير lambda المباشرة، إذ تُعدُّ الدالة lock()‎ مثالًا جيدًا لمثل هذه الحالات التي تُجعَل فيها lambda مباشرةً في موقع الاستدعاء، مثل:

lock(l) { foo() }

إذ بدلًا من إنشاء كائن الدالة للمتحوّلات وتوليد الاستدعاء يمكن أن يقوم المٌترجِم (compiler) بالشيفرة الآتية:

l.lock()
try {
    foo()
}
finally {
    l.unlock()
}

حيث يجب تحديد الدالة lock()‎ بالمُحدِّد inline كما يلي:

inline fun <T> lock(lock: Lock, body: () -> T): T {
    // ...
}

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

المُحدِّد noinline

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

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
    // ...
}

إذ يُسمَح باستدعاء تعابير lambda المباشرة داخل الدوال المباشرة فقط أو بتمريرها كمتحولٍ مباشرٍ، أمّا العناصر المحُدَّدة بالكلمة المفتاحيّة noinline فيمكن أن تُعامَل بأيّة طريقة كانت، كتخزينها في الحقول (fields) أو تمريرها أو ... إلخ.

وتجدر الإشارة إلى أنّ المترجِم سيُعلِم بتحذيرٍ إن احتوت الدالة المباشرة على متحولاتٍ غير مباشرة أو متحولات نوعٍ ليست reified (كما ستُشرَح لاحقًا)، لأنّ ذلك لا يُجدي نفعًا، وبالإمكان تجاوز هذا التحذير -عند الحاجة المُلحَّة للتضمين المباشر- باستخدام التوصيف (annotation) الآتي: ‎@Suppress("NOTHING_TO_INLINE")‎.

أوامر العودة غير المحليّة (Non-local returns)

نستطيع في Kotlin استخدام الأمر return غير المقيّد (unqualified) للخروج من الدالة المُسمّاة (named function) أو الدالة المجهولة (anonymous function)، أمّا للعودة من تعبير lambda يجب استخدام التسمية (label)، ولا يُسمَح بأمر return مفردًا كما هو داخل التعبير، لأن تعبير lambda غير قادرٍ على العودة من الدالة المحيطة به (enclosing)، مثل:

fun foo() {
    ordinaryFunction {
        return // سينتج خطأ: لا يمكن العودة من الدالة هنا
    }
}

أمّا إن كانت الدالة التي يُمرَّر لها التعبير lambda مباشرةً فيمكن عندئذٍ جعل أمر العودة مباشرًا كذلك، وبالتالي تكون الشيفرة الآتية صحيحةً:

fun foo() {
    inlineFunction {
        return // صحيحة لأن التعبير lambda
               // من النوع السطريّ
    }
}

وتُسمّى أوامر الرجوع هذه (والموجودة في تعبير lambda ولكنّها المسؤولة عن العودة من الدالة المحيطة به) بأوامر الرجوع غير المحليّة، وهذا ما تكون عليه عادةً بُنية الحلقات التي تحيط بها الدوال المباشرة، مثل:

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // العودة من hasZeros
    }
    return false
}

ويمكن لبعض الدوال المباشرة استدعاء تعابير lambda المُمرَّرة لها كمتحولاتٍ، ولا يتمّ ذلك من بُنية الدالة مباشرةً (directly) وإنمّا من أي سياقٍ تنفيذيّ آخر كالكائن المحلي (local object) أو دالةٍ متداخلةٍ (nested)، ولا يُسمَح حينها بالتحكم بالتدفق غير المحليّ في تعابير lamnda، إذ يجب أن يكون متحول lambda مُحدَّدًا بالكلمة المفتاحيّة crossinline كما في الشيفرة:

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
    // ...
}

ملاحظة: إنّ استخدام الأمرين break و continue في دوال lambda المباشرة في Kotlin غير ممكنٍ، وقد يُسمَح بذلك في الإصدارات القادمة.

المتحوّلات من النوع reified 

قد نحتاج أحيانًا للوصول إلى نوعٍ مُمرَّر كمتحولٍ وسيطٍ (parameter)، مثل:

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T?
}

إذ سيجري عبور الشجرة حيث ستُستخدَم الانعكاسات (reflections) للتحقُّق من النوع في العقد، وسيكون موقع الاستدعاء بالشكل:

treeNode.findParentOfType(MyTreeNode::class.java)

وما نريده فعليًا هو تمرير النوع لهذه الدالة، أي استدعاؤها كما يلي:

treeNode.findParentOfType<MyTreeNode>()

للقيام بذلك تُستخدَم المتحولات من النوع reified ، وتصبح الشيفرة بالشكل:

inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}

إذ تُقيَّد متحولات النوع بالمُحدِّد reified مما يجعلها متاحةً داخل الدالة وكأنها صنف عاديّ. وبما أن الدالة مباشرةٌ فلا حاجة لأي انعكاس (reflection) هنا، ويُتاح استخدام المعاملات مثل ‎!is وas كذلك، كما ويمكن استدعاؤها -كما ذُكر سابقًا- بالصيغة: myTree.findParentOfType<MyTreeNodeType>()‎. وعلى الرغم من أنه لا حاجة للانعكاسات في كثيرٍ من الحالات، فما يزال استخدامها ممكنًا مع متحولات النوع reified كما يلي:

inline fun <reified T> membersOf() = T::class.members

fun main(s: Array<String>) {
    println(membersOf<StringBuilder>().joinToString("\n"))
}

ولا يمكن أن تحتوي الدوال العادية على متحولاتٍ من النوع reified ؛ لأنه لا يُسمَح للنوع الذي لا يملك تمثيلًا تنفيذيًا (كالمتحول الذي لا يكون reified أو النوع الزائف مثل Nothing) أن يكون من النوع reified.

الخاصّيّات المباشرة (بدءًا من الإصدار 1.1)

تُستخدَم الكلمة المفتاحيّة inline للوصول إلى الخاصّيّات التي ليس لها حقولٌ مساعدةٌ (backing fields)، وبالإمكان توصيف عمليات الوصول بشكلٍ منفصلٍ عن بعضهما كما يلي:

val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ...
    inline set(v) { ... }

كما ويمكن توصيف الخاصية بمجملها، وهذا يجعل كلًا من get وset مباشرتين:

inline var bar: Bar
    get() = ...
    set(v) { ... }

إذ تُضمّن مباشرةً في موقع الاستدعاء كما لو أنها دالةٌ مباشرةٌ عاديّة.

التقييدات على الدوال المباشراة في الواجهات العامة (Public API)

عندما تكون الدالة المباشرة من النوع public أو protected وليست جزءًا من تصريحٍ من النوع private أو internal فإنها حينئذٍ تُعدُّ واجهةً عامةً للوحدة (module)، وبالتالي فمن الممكن استدعاؤها من أيّ وحدةٍ أخرى، كما ويمكن جعلها مباشرةً في مواقع الاستدعاء، لكنّ هذا سيفرض بعض المخاطر من ناحية عدم التوافق الثنائيّ (binary incompatibility) والذي يحدث نتيجةً لبعض التغييرات في الوحدة التي تصرِّح عن الدالة المباشرة، وذلك إن كان استدعاء الوحدة لا يُترجَم ثانيةً بعد ذلك التغيير.

وللحد من هذه المخاطر لا يُسمح للدوال المباشرة في واجهات API العامّة باستخدام تصريحات الواجهات غير العامّة (non-public-API declarations)، أي لا يُسمح لها بالتصريحات من النوعين private و internal في بُنيتها.

وتُتاح إضافة التوصيف (annotation) بالصيغة ‎@PublishedApi إلى التصريح من النوع internal ، ممّا سيسمح باستخدامه في الدوال المباشرة في الواجهات العامّة، وعند تحديد الدالة المباشرة من النوع internal (عبر التوصيف ‎@PublishedApi) فسيُتحقَّق من بُنيتها وكأنها عامّة (public).

مصادر