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

من موسوعة حسوب
أنشأ الصفحة ب'<noinclude>{{DISPLAYTITLE:التحميل الزائد للمعاملات (Operator Overloading) في لغة Kotlin}}</noinclude> تُتيح لغة Kotlin إجراءَ م...'
 
ط استبدال النص - 'Kotlin Functions' ب'Kotlin Function'
 
(مراجعتان متوسطتان بواسطة مستخدمين اثنين آخرين غير معروضتين)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:التحميل الزائد للمعاملات (Operator Overloading) في لغة Kotlin}}</noinclude>
<noinclude>{{DISPLAYTITLE:التحميل الزائد للمعاملات (Operator Overloading) في لغة Kotlin}}</noinclude>
تُتيح لغة Kotlin إجراءَ مجموعةٍ مُعرَّفة مسبقًا من العمليات على أنواع البيانات المختلفة وذلك باستخدام رموزٍ ثابتةٍ تعتمدها لغة Kotlin مثل الرمز <code>+</code> أو الرمز <code>*</code> وتختلف فيما بينها بالأولويّة (precedence)، حيث توجد دالةٌ (إما [[Kotlin/functions|دالة من الصنف [member]]] أو [[Kotlin/extensions|دالة إضافيّة [extension]]]) باسمٍ ثابتٍ لكلّ معاملٍ مُعرَّف بحسب النوع (نوعٌ يساريٌّ للعمليات الثنائيّة [binary operations] ونوع متحولاتٍ [argument type] للعمليات الأحاديّة [unary operations])، ويجب تحديد الدوال التي تحتوي على تحميلٍ زائدٍ للمعاملات بالمُحدَّد <code>operator</code>، تناقش الصفحة الاصطلاحات (conventions) التي تنظِّم التحميل الزائد لمختلف المعاملات.
تُتيح لغة Kotlin إجراءَ مجموعةٍ مُعرَّفة مسبقًا من العمليات على أنواع البيانات المختلفة وذلك باستخدام رموزٍ ثابتةٍ تعتمدها لغة Kotlin مثل الرمز <code>+</code> أو الرمز <code>*</code> وتختلف فيما بينها بالأولويّة (precedence)، حيث توجد دالةٌ (إما [[Kotlin/functions|دالة من الصنف [member]]] أو [[Kotlin/extensions|دالة إضافيّة [extension]]]) باسمٍ ثابتٍ لكلّ معاملٍ مُعرَّف بحسب النوع (نوعٌ يساريٌّ للعمليات الثنائيّة [binary operations] ونوع وسائط [argument type] للعمليات الأحاديّة [unary operations])، ويجب تحديد الدوال التي تحتوي على تحميلٍ زائدٍ للمعاملات بالمُحدَّد <code>operator</code>، تناقش الصفحة الاصطلاحات (conventions) التي تنظِّم التحميل الزائد لمختلف المعاملات.


== العمليات الأحادية (Unary Operations) ==
== العمليات الأحادية (Unary Operations) ==
سطر 19: سطر 19:
|}
|}
يقوم المُترجِم (compiler) بالخطوات الآتية عند إجراء إحدى العمليات السابقة ولتكن <code>a+</code> :
يقوم المُترجِم (compiler) بالخطوات الآتية عند إجراء إحدى العمليات السابقة ولتكن <code>a+</code> :
* تحديد نوع المتحول <code>a</code> وليكن <code>T</code>
* تحديد نوع المتغيِّر <code>a</code> وليكن <code>T</code>
* البحث عن الدالة <code>unaryPlus()</code>‎ المسبوقة بالمُحدِّد <code>operator</code> والتي لا تحتوي أيّة متحولات للمستقبِل <code>T</code>  (إما دالة من الصنف [member] أو دالة إضافيّة [extension]) 
* البحث عن الدالة <code>unaryPlus()</code>‎ المسبوقة بالمُحدِّد <code>operator</code> والتي لا تحتوي أيّة معاملات للمستقبِل <code>T</code>  (إما دالة من الصنف [member] أو دالة إضافيّة [extension]) 
* إن لم تكن الدالة موجودةً أو كانت غامضةً فسينتُج خطأٌ بالترجمة (compilation error)
* إن لم تكن الدالة موجودةً أو كانت غامضةً فسينتُج خطأٌ بالترجمة (compilation error)
* إذا وُجدت الدالة وكان نوعها المُعاد <code>R</code> فسيكون للتعبير <code>a+</code> النوع <code>R</code> أيضًا
* إذا وُجدت الدالة وكان نوعها المُعاد <code>R</code> فسيكون للتعبير <code>a+</code> النوع <code>R</code> أيضًا
سطر 43: سطر 43:
|<code>a.dec()‎</code>
|<code>a.dec()‎</code>
|}
|}
حيث ستُسنَد القيمة التي تعيدها الدالة <code>inc()‎</code> أو <code>dec()</code>‎ إلى المتحول الذي يُلحَق به المعامل <code>++</code> أو <code>--</code> دون أن تغيّر الدالةُ الكائنَ الذي استُدعيت من خلاله. يقوم المترجم بالخطوات الآتية لتنفيذ العملية اللاحقة (postfix) ولتكن <code>++a</code> مثلًا:
إذ ستُسنَد القيمة التي تعيدها الدالة <code>inc()‎</code> أو <code>dec()</code>‎ إلى المتغيِّر الذي يُلحَق به المعامل <code>++</code> أو <code>--</code> دون أن تغيّر الدالةُ الكائنَ الذي استُدعيت من خلاله. يقوم المترجم بالخطوات الآتية لتنفيذ العملية اللاحقة (postfix) ولتكن <code>++a</code> مثلًا:
* تحديد نوع المتحول <code>a</code> وليكن النوع <code>T</code>
* تحديد نوع المتغيِّر<code>a</code> وليكن النوع <code>T</code>


* البحث عن الدالة <code>inc()</code>‎ والمُعرَّفة بالمُحدِّد <code>operator</code> وتكون بدون متحولاتٍ وقابلةً للتطبيق على النوع <code>T</code>
* البحث عن الدالة <code>inc()</code>‎ والمُعرَّفة بالمُحدِّد <code>operator</code> وتكون بدون معاملات وقابلةً للتطبيق على النوع <code>T</code>
* التحقُّق من أنّ النوع المُعاد من هذه الدالة هو نوعٌ فرعيٌّ للنوع <code>T</code>
* التحقُّق من أنّ النوع المُعاد من هذه الدالة هو نوعٌ فرعيٌّ للنوع <code>T</code>
وينتُج عن حساب التعبير:
وينتُج عن حساب التعبير:
* تخزين القيمة الابتدائيّة (initial value) للمتحول <code>a</code> في متحول مؤقت <code>a0</code>
* تخزين القيمة الابتدائيّة (initial value) للمتغيِّر <code>a</code> في متغيِّر مؤقت <code>a0</code>
* إسناد قيمة نتيجة الدالة <code>inc()‎</code> إلى المتحول السابق <code>a</code>
* إسناد قيمة نتيجة الدالة <code>inc()‎</code> إلى المتغيِّر السابق <code>a</code>
* إعادة المتحول <code>a0</code> كنتيجةٍ للتعبير
* إعادة المتغيِّر <code>a0</code> كنتيجةٍ للتعبير
وتماثل خطوات تنفيذ العملية <code>--a</code> الخطوات السابقة.
وتماثل خطوات تنفيذ العملية <code>--a</code> الخطوات السابقة.


أمّا معاملات الزيادة والنقصان بصيغتها البادئة (prefix) بالشكل: <code>a++</code> و <code>a--</code> فهي تعمل بالطريقة السابقة ذاتها وتكون النتيجة:
أمّا معاملات الزيادة والنقصان بصيغتها البادئة (prefix) بالشكل: <code>a++</code> و <code>a--</code> فهي تعمل بالطريقة السابقة ذاتها وتكون النتيجة:
* إسناد نتيجة الدالة <code>a.inc()</code>‎ إلى المتحول <code>a</code>
* إسناد نتيجة الدالة <code>a.inc()</code>‎ إلى المتغيِّر <code>a</code>
* إعادة القيمة الجديدة للمتحول <code>a</code> كنتيجةٍ للتعبير
* إعادة القيمة الجديدة للمتغيِّر <code>a</code> كنتيجةٍ للتعبير


== العمليات الثنائيّة (Binary Operations) ==
== العمليات الثنائيّة (Binary Operations) ==
سطر 106: سطر 106:
|<code>‎!b.contains(a)‎</code>
|<code>‎!b.contains(a)‎</code>
|}
|}
وتكون الإجراءات نفسها في المعاملين حيث يُعكَس ترتيب المتحولات فقط.
وتكون الإجراءات نفسها في المعاملين حيث يُعكَس ترتيب الوسائط (arguments) فقط.


=== معاملات الوصول المفهرس (Indexed Access Operators) ===
=== معاملات الوصول المفهرس (Indexed Access Operators) ===
سطر 131: سطر 131:
|<code>a.set(i_1, ..., i_n, b)‎</code>
|<code>a.set(i_1, ..., i_n, b)‎</code>
|}
|}
إذ تُترجَم الأقواس <code>[]</code> إلى استدعاء الدالة <code>get</code> أو <code>set</code> بما يتناسب مع عدد المتحولات.
إذ تُترجَم الأقواس <code>[]</code> إلى استدعاء الدالة <code>get</code> أو <code>set</code> بما يتناسب مع عدد الوسائط.


=== معاملات الاستدعاء (Invoke Operators) ===
=== معاملات الاستدعاء (Invoke Operators) ===
سطر 150: سطر 150:
|<code>a.invoke(i_1, ..., i_n)‎</code>
|<code>a.invoke(i_1, ..., i_n)‎</code>
|}
|}
إذ تُترجَم الأقواس <code>()</code> إلى استدعاءات للدالة <code>invoke</code> بالعدد المناسب من المتحولات.
إذ تُترجَم الأقواس <code>()</code> إلى استدعاءات للدالة <code>invoke</code> بالعدد المناسب من الوسائط.


=== عمليات الإسناد المُحسَّنة (Augmented Assignments) ===
=== عمليات الإسناد المُحسَّنة (Augmented Assignments) ===
سطر 193: سطر 193:
ويكون معاملا التحقُّق من المرجعيّة (<code>===</code> و <code>==!</code>) غير قابلين للتحميل الزائد (not overloadable) وبالتالي ما من استخدامٍ اصطلاحيٍّ لهما.
ويكون معاملا التحقُّق من المرجعيّة (<code>===</code> و <code>==!</code>) غير قابلين للتحميل الزائد (not overloadable) وبالتالي ما من استخدامٍ اصطلاحيٍّ لهما.


ويتميز المعامل <code>==</code> بأنه يُترجَم إلى تعبيرٍ معقَّدٍ يُضمِر القيمة <code>null</code> أي تكون نتيجة العملية <code>null == null</code> صحيحةً دائمًا، وتكون قيمة <code>x == null</code> خاطئةً دائمًا إن كان المتحول <code>x</code> متحولًا  nullable (أي لا يقبل القيمة الفارغة <code>null</code>) ولن تُستدعَى بذلك الدالة <code>x.equals()‎</code>.
ويتميز المعامل <code>==</code> بأنه يُترجَم إلى تعبيرٍ معقَّدٍ يُضمِر القيمة <code>null</code> أي تكون نتيجة العملية <code>null == null</code> صحيحةً دائمًا، وتكون قيمة <code>x == null</code> خاطئةً دائمًا إن كان المتغيِّر <code>x</code> متغيِّرًا nullable (أي لا يقبل القيمة الفارغة <code>null</code>) ولن تُستدعَى بذلك الدالة <code>x.equals()‎</code>.


=== معاملات المقارنة (Comparison Operators) ===
=== معاملات المقارنة (Comparison Operators) ===
سطر 224: سطر 224:
[[تصنيف:Kotlin]]
[[تصنيف:Kotlin]]
[[تصنيف:Kotlin Operators]]
[[تصنيف:Kotlin Operators]]
[[تصنيف:Kotlin Functions]]
[[تصنيف:Kotlin Function]]

المراجعة الحالية بتاريخ 11:37، 30 أغسطس 2018

تُتيح لغة Kotlin إجراءَ مجموعةٍ مُعرَّفة مسبقًا من العمليات على أنواع البيانات المختلفة وذلك باستخدام رموزٍ ثابتةٍ تعتمدها لغة Kotlin مثل الرمز + أو الرمز * وتختلف فيما بينها بالأولويّة (precedence)، حيث توجد دالةٌ (إما دالة من الصنف [member] أو دالة إضافيّة [extension]) باسمٍ ثابتٍ لكلّ معاملٍ مُعرَّف بحسب النوع (نوعٌ يساريٌّ للعمليات الثنائيّة [binary operations] ونوع وسائط [argument type] للعمليات الأحاديّة [unary operations])، ويجب تحديد الدوال التي تحتوي على تحميلٍ زائدٍ للمعاملات بالمُحدَّد operator، تناقش الصفحة الاصطلاحات (conventions) التي تنظِّم التحميل الزائد لمختلف المعاملات.

العمليات الأحادية (Unary Operations)

المعاملات الأحادية البادئة (Unary Prefix Operators)

التعبير يُترجَم إلى
a+ a.unaryPlus()‎
a- a.unaryMinus()‎
a! a.not()‎

يقوم المُترجِم (compiler) بالخطوات الآتية عند إجراء إحدى العمليات السابقة ولتكن a+ :

  • تحديد نوع المتغيِّر a وليكن T
  • البحث عن الدالة unaryPlus()‎ المسبوقة بالمُحدِّد operator والتي لا تحتوي أيّة معاملات للمستقبِل T (إما دالة من الصنف [member] أو دالة إضافيّة [extension]) 
  • إن لم تكن الدالة موجودةً أو كانت غامضةً فسينتُج خطأٌ بالترجمة (compilation error)
  • إذا وُجدت الدالة وكان نوعها المُعاد R فسيكون للتعبير a+ النوع R أيضًا

وتُتاح هذه العمليات (بالإضافة لعملياتٍ أخرى) للأنواع الأساسيّة في لغة Kotlin ولا تتطلَّب استدعاء الدوال، فيمكن مثلًا استخدام التحميل الزائد للمعامل الأحاديّ - بالشكل:

data class Point(val x: Int, val y: Int)

operator fun Point.unaryMinus() = Point(-x, -y)

val point = Point(10, 20)
println(-point)  // "(-10, -20)" سيظهر

معاملات الزيادة والنقصان

التعبير يُترجَم إلى
++a a.inc()‎
--a a.dec()‎

إذ ستُسنَد القيمة التي تعيدها الدالة inc()‎ أو dec()‎ إلى المتغيِّر الذي يُلحَق به المعامل ++ أو -- دون أن تغيّر الدالةُ الكائنَ الذي استُدعيت من خلاله. يقوم المترجم بالخطوات الآتية لتنفيذ العملية اللاحقة (postfix) ولتكن ++a مثلًا:

  • تحديد نوع المتغيِّرa وليكن النوع T
  • البحث عن الدالة inc()‎ والمُعرَّفة بالمُحدِّد operator وتكون بدون معاملات وقابلةً للتطبيق على النوع T
  • التحقُّق من أنّ النوع المُعاد من هذه الدالة هو نوعٌ فرعيٌّ للنوع T

وينتُج عن حساب التعبير:

  • تخزين القيمة الابتدائيّة (initial value) للمتغيِّر a في متغيِّر مؤقت a0
  • إسناد قيمة نتيجة الدالة inc()‎ إلى المتغيِّر السابق a
  • إعادة المتغيِّر a0 كنتيجةٍ للتعبير

وتماثل خطوات تنفيذ العملية --a الخطوات السابقة.

أمّا معاملات الزيادة والنقصان بصيغتها البادئة (prefix) بالشكل: a++ و a-- فهي تعمل بالطريقة السابقة ذاتها وتكون النتيجة:

  • إسناد نتيجة الدالة a.inc()‎ إلى المتغيِّر a
  • إعادة القيمة الجديدة للمتغيِّر a كنتيجةٍ للتعبير

العمليات الثنائيّة (Binary Operations)

المعاملات الحسابيّة (Arithmetic Operators)

التعبير يُترجم إلى
a + b a.plus(b)‎
a - b a.minus(b)‎
a * b a.times(b)‎
a / b a.div(b)‎
a % b a.rem(b)‎ و a.mod(b)‎ (مُستبعدة)
a..b a.rangeTo(b)‎

تدعم Kotlin المعامل rem بدءًا من الإصدار Kotlin 1.1 ليحل محلّ mod في الإصدار Kotlin 1.0 (مُستبعد في الإصدار الحاليّ).

مثال

يبدأ الصنف Counter الآتي بقيمةٍ ابتدائيّةٍ من الممكن زيادتها باستخدام التحميل الزائد للمعامل + بالشكل:

data class Counter(val dayIndex: Int) {
    operator fun plus(increment: Int): Counter {
        return Counter(dayIndex + increment)
    }
}

المعامل in

التعبير يُترجَم إلى
a in b b.contains(a)‎
a !in b ‎!b.contains(a)‎

وتكون الإجراءات نفسها في المعاملين حيث يُعكَس ترتيب الوسائط (arguments) فقط.

معاملات الوصول المفهرس (Indexed Access Operators)

التعبير يُترجَم إلى
a[i]‎ a.get(i)‎
a[i,j]‎ a.get(i, j)‎
a[i_1, ..., i_n]‎ (a.get(i_1, ..., i_n
a[i] = b a.set(i, b)‎
a[i, j] = b a.set(i, j, b)‎
a[i_1, ..., i_n] = b a.set(i_1, ..., i_n, b)‎

إذ تُترجَم الأقواس [] إلى استدعاء الدالة get أو set بما يتناسب مع عدد الوسائط.

معاملات الاستدعاء (Invoke Operators)

التعبير يُترجَم إلى
a()‎ a.invoke()‎
a(i)‎ a.invoke(i)‎
a(i,j)‎ a.invoke(i,j)‎
a(i_1, ..., i_n)‎ a.invoke(i_1, ..., i_n)‎

إذ تُترجَم الأقواس () إلى استدعاءات للدالة invoke بالعدد المناسب من الوسائط.

عمليات الإسناد المُحسَّنة (Augmented Assignments)

التعبير يُترجَم إلى
a += b a.plusِAssign(b)‎
a -= b a.minusAssign(b)‎
a *= b a.timesAssign(b)‎
a /= b a.divAssign(b)‎
a %= b a.remAssign(b)‎ و a.modAssign(b)‎ (مُستبعدة)

إذ يقوم المُترجِم (compiler) بالخطوات الآتية لعمليات الإسناد، ولتكن العملية a += b مثلًا:

  • إن كانت الدالة متاحةً:
    • إن كانت الدالة الثنائيّة الموافقة لها (مثل الدالة plus()‎ بالنسبة للدالة plusAssign()‎) متاحةً أيضًا فيجب الإعلام بخطأ الالتباس (ambiguity)
    • التأكّد من أنّ النوع المُعاد هو Unit والإعلام بالخطأ خلاف ذلك
    • توليد الشيفرة المناسبة للدالة a.plusِAssign(b)
  • إن لم تكن الدالة متاحةً: يحاول توليد الشيفرة a = a + b وهذا يتضمَّن التحقُّق من النوع؛ أي أن يكون نوع التعبير a + b نوعًا فرعيًا (subtype) من a

ولا تُعدُّ عمليات الإسنادا تعابيرًا (expressions) في Kotlin.

معاملات المساواة (Equality) والاختلاف (Inequality)

التعبير يُترجَم إلى
a == b a?.equals(b) ?: (b === null)‎‎
a != b ‎!(a?.equals(b) ?: (b === null))‎

ويكون معاملا التحقُّق من المرجعيّة (=== و ==!) غير قابلين للتحميل الزائد (not overloadable) وبالتالي ما من استخدامٍ اصطلاحيٍّ لهما.

ويتميز المعامل == بأنه يُترجَم إلى تعبيرٍ معقَّدٍ يُضمِر القيمة null أي تكون نتيجة العملية null == null صحيحةً دائمًا، وتكون قيمة x == null خاطئةً دائمًا إن كان المتغيِّر x متغيِّرًا nullable (أي لا يقبل القيمة الفارغة null) ولن تُستدعَى بذلك الدالة x.equals()‎.

معاملات المقارنة (Comparison Operators)

التعبير يُترجم إلى
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

إذ تُترجَم كافّة عمليات المقارنة إلى استدعاءاتٍ للدالة compareTo التي تعيد النوع Int.

معاملات تعميم الخاصّيّات (Property Delegation Operators)

وهي الدوال provideDelegate و getValue و setValue ولمزيدٍ من المعلومات راجع الخاصّيّات المُعمَّمة (delegated properties).

استدعاء الدوال المسماة بالصيغة الضمنيّة (Infix)

تمكن محاكاة العمليّات بالصيغة الضمنيّة (infix) بالاعتماد على استدعاءات الدوال الضمنيّة.

مصادر