المجالات (Ranges) في لغة Kotlin
استخدام المجالات
تُصاغُ تعابير المجالات (range expressions) من خلال دوال rangeTo
التي تعتمد على المعامل ..
والذي بدوره يتُمَّم بالمعاملين in
و !in
، إذ من الممكن أن يُعرَّف المجال لأيّ نوعٍ يقبل المقارنة، أما في حالة الأنواع الأساسيّة المتكاملة (integral primitive types) فلها تعريف استخدام مُحسَّن (optimized)، وهذه بعض الأمثلة:
if (i in 1..10) { // مكافئ للصياغة
// 1 <= i && i <= 10
println(i)
}
ولمجالات الأنواع المتكاملة (IntRange
و LongRange
و CharRange
) ميّزة إضافيّة إذ يمكن المرور بعناصرها (iteration)، حيث يحوِّلها المترجم لشكلٍ مناظرٍ لحلقة for
المُفهرَسة (indexed) في Java دون أيّ تكلفةٍ زائدةٍ، مثل:
for (i in 1..4) print(i) // "1234" ستظهر القيم
for (i in 4..1) print(i) // لن تظهر أيّة قيمة
ولكن ماذا لو دعت الحاجة للمرور بالعناصر بالترتيب العكسيّ؟ هذا بسيط! ويكون باستخدام الدالة downTo()
المُعرَّفة في المكتبة القياسيّة، بالشكل:
for (i in 4 downTo 1) print(i) // "4321" ستظهر القيم بالترتيب العكسي
وبالإمكان أيضًا الانتقال بقيمة خطوةٍ غير الواحد وذلك باستخدام الدالة step()
كما في الشيفرة الآتية:
for (i in 1..4 step 2) print(i) // "13" ستظهر القيم
for (i in 4 downTo 1 step 2) print(i) // "42" ستظهر القيم
ولإنشاء مجالٍ مفتوح النهاية (دون الوصول للعنصر الأخير فيه) يُستفاد من الدالة until
كما في الشيفرة:
for (i in 1 until 10) { // تُستثنى القيمة 10 وهي مكافئة للشكل
// i in [1, 10)
println(i)
}
آليّة عمل المجالات
تُعرَّف المجالاتُ استخدامَ (implement) الواجهة المشتركة ClosedRange<T>
في المكتبة، والتي تعبِّر عن المفهوم الرياضيّ للمجال المُغلَق (يشمل قيمتيّ البداية والنهاية) والمُعرَّفِ للقيم القابلة للمقارنة (comparable)، وله نقطتان تُحدِّدانه: البداية start
والنهاية endInclusive
(المُتضمَّنة في المجال)، و تكون contains
العمليةَ الأساسيّة المُستخدمَة فيها وهي تعتمد على صيغةٍ تستخدم المعاملين in
و !in
.
أمّا أنواع المتتاليات المتكاملة (integral type progressions) مثل IntProgression
وLongProgression
و CharProgression
فهي تعبِّر عن المتتاليات الرياضيّة وتُعرَّف بالعنصر الأول first
والعنصر الأخير last
وبخطوة step
غير صفريّة، حيث يبدأ المجال بالقيمة الأولى first
وتنتُج كلُّ قيمةٍ تاليةٍ عن القيمة السابقة بعد إضافة الخطوة step
وصولًا للعنصر الأخير last
وذلك ما لم تكن المتتالية فارغة.
وتُعدُّ المتتاليات نوعًا فرعيًا (subtype) منIterable<N>
حيث يعبِّر النوع N
عن أحد الأنواع Int
أو Long
أو Char
، وبالتالي فبالإمكان استخدامها في حلقات for
وبعض الدوالّ مثل map
و filter
ومايماثلهما، ويكون عبور المتتالية Progression
مماثلًا لحلقة for
المُفهرَسة (indexed) في Java أو JavaScript ، مثل:
for (int i = first; i != last; i += step) {
// ...
}
إذ يُنشِئ المُعامِل ..
-في حالة الأنواع المتكاملة- كائنًا (object) يعرِّف استخدام (implement) كلٍ من ClosedRange<T>
و *Progression
فعلى سبيل المثال؛ يعرِّف المجال IntRange
استخدام ClosedRange<T>
ويُوسِّع (extend) المتتالية IntProgression
، وبالتالي فإنّ كلَّ العمليات المُعرَّفة لصالح IntProgression
ستُتاح أيضًا في IntRange
، وتكون النتيجة الصادرة عن الدالتين downTo()
و step()
من النوع *Progression
دائمًا.
وتُنشَأ المتتاليات باستخدام الدالة fromClosedRange
المُعرَّفة في كائناتها المُرافِقة (companion objects) بالشكل:
IntProgression.fromClosedRange(start, end, step)
حيث يُحسَب العنصرُ الأخير last
من المتتالية لإيجاد أكبر قيمةٍ لا تتجاوز القيمة end
إن كانت الخطوة step
موجبة، أو لإيجاد أصغر قيمةٍ لا تقِّل عن القيمة end
في حالة الخطوة step
السالبة بحيث يكون (last - first) % step == 0
.
بعض الدوال المساعدة
الدالة rangTo()
تستدعي معامِلات الدالة rangTo()
-في الأنواع المتكاملة- البواني الموجودة في الأصناف *Range
مثل:
class Int {
//...
operator fun rangeTo(other: Long): LongRange = LongRange(this, other)
//...
operator fun rangeTo(other: Int): IntRange = IntRange(this, other)
//...
}
ولا تُعرِّف أعداد الفاصلة العائمة (مثل Float
و Double
) المعامِل rangeTo
، بل يُستخدَم بدلًا من ذلك النوع المُعمَّم (generic) والموجود في المكتبة القياسيّة مثل:
public operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
ولا يُمكِن المرور بعناصر (iteration) المجال الناتج عن هذه الدالة.
الدالة downTo()
تٌعرَّف الدالة الإضافيّة (extension function) downTo()
لأيّ زوجٍ (pair) من الأنواع المتكاملة (integral types)، وفيما يلي مثالان عنها:
fun Long.downTo(other: Int): LongProgression {
return LongProgression.fromClosedRange(this, other.toLong(), -1L)
}
fun Byte.downTo(other: Int): IntProgression {
return IntProgression.fromClosedRange(this.toInt(), other, -1)
}
الدالة reversed()
تٌعرَّف الدالة الإضافيّة (extension function) reversed()
لكلّ أصناف المتتاليات *Progression
، وينتُج عن ذلك متتاليةٌ معكوسةٌ، أي:
fun IntProgression.reversed(): IntProgression {
return IntProgression.fromClosedRange(last, first, -step)
}
الدالة step()
تٌعرَّف الدالة الإضافيّة (extension function) step()
لكلّ أصناف المتتاليات *Progression
، وينتُج عن ذلك متتالياتٌ بخطوةٍ مُعدَّلةٍ، حيث تكون قيمة الخطوة موجبةً دائمًا وبالتالي لا تغيّر هذه الدالة من اتجاه المرور بعناصر المجال، مثل:
fun IntProgression.step(step: Int): IntProgression {
if (step <= 0) throw IllegalArgumentException("Step must be positive, was: $step")
return IntProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}
fun CharProgression.step(step: Int): CharProgression {
if (step <= 0) throw IllegalArgumentException("Step must be positive, was: $step")
return CharProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}
وقد تختلف القيمة last
للمتتالية المُعادَة عن مثيلتها في المتتالية الأصليّة للحفاظ على الشرط (last - first) % step == 0
، مثل:
(1..12 step 2).last == 11 // [1, 3, 5, 7, 9, 11] المتتالية بالقيم
(1..12 step 3).last == 10 // [1, 4, 7, 10] المتتالية بالقيم
(1..12 step 4).last == 9 // [1, 5, 9] المتتالية بالقيم