مقدمة عن الدوال في لغة الكوتلن

مقالة متخصصة عن البرمجية الدالية في لغة البرمجة الكوتلن.

Mohammad Laifمنذ سنتين

بسم الله الرحمن الرحيم

السلام عليكم ورحمة الله وبراكته

 

مقالة متخصصة عن البرمجة الدالية Functional Programming في لغة الكوتلن.

 

طويل جدا لم أقرأه ؟

  • طريقتين للبرمجة الاولى تعتبر تصريحية Declarative تعتمد على التصريح عن الطلب What أي ماذا تريد أنت وتبرز هنا البرمجة الدالية. والاخرى تعتبر آمرية Imperative تعتمد على اعطاء وكتابة الأوامر لكيفية القيام بالخطوات How وتبرز هنا البرمجة الكائنية. الكوتلن تأخذ بالطريقتين معاً.
  • البرمجة الدالية تجعل كل شئ غير قابل للتغير Immutable للحد من المشاكل. دوالها Functions لا تحدث اي تغيير في النظام, وتقتصر وظيفتهم في أخذ مدخل وإخراج ناتج. بعكس الـ Methods التي قد تحدث تغييرات في النظام.
  • الدوال في لغة الكوتلن تعتبر من الدرجة الأولى First Class وهذا يعني اننا نستطيع التعامل معهم كيفما نشاء.
  • ننشئ الدالة في لغة الكوتلن بإستخدام الكلمة fun ونحدد المدخلات في القوسين () ونحدد المخرج نقطتين رأسيتين : ونحدد النطاق بالقوسين المتعرجان {} لكتابة منطق الدالة.
  • في الكوتلن مدخلات متعددة للدوال مثل Parameter وهو مانحددة للدالة عند إنشائها. و Argument وهو القيم المدخلة على الدالة عند استخدامها. و Receiver نعبر عنه بالعنصر المدخل على بعض الدوال. و it ونعبر عنه عن المدخل الوحيد للدالة لامبدا lambda.

 

ماذا ستقرأ في هذه المقالة؟

  • نماذج البرمجة.
    • النمودج الاول: البرمجة الأمرية Imperative Programming.
    • النموذج الثاني: البرمجة التصريحية Declarative Programming.
    • اين موقع لغة الكوتلن من كل هذا؟
  • البرمجة الدالية.
    • الفرق بين الـ Function و الـ Method.
    • شروط الدالة في البرمجة الدالية.
  • الدوال في لغة الكوتلن.
    • إنشاء الدوال في الكوتلن.
    • القيم الافتراضية Default Arguments في مدخلات الدوال.
    • المدخلات المتعددة في الدالة.
    • إستخدام اسم المدخلات في الدوال.
    • فرق المدخلات Parameter و Argument و Receiver و it للدالة الكوتلن.
  • المصادر.

 

نماذج البرمجة

في هذا القسم سنناقش نموذجين للبرمجة Programming Paradigms. نموذج البرمجة الأمرية Imperative Programming و نموذج البرمجة التصريحية Declarative Programming. 

 

النمودج الاول: البرمجة الأمرية Imperative Programming

هي أن تقوم بكتابة الخطوات خطوة تلوى الاخرى وهذه الطريقه متعارف عليها لدى الاغلبية. وتركز هذه الطريقة على الكيفية How. وأغلب اللغات الكائنية Object-oriented programming اتخذتها نهجاً لها.

مثال: برنامج لاستخراج الأعداد الزوجية من متسلسلة ما:

// List
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// Imperative Way: How the steps
// Object-Oriented Programming way
val evenNumbersImperative = ArrayList<Int>()
for (i: Int in 0..numbers.lastIndex) {
    val item: Int = numbers[i]
    if (item % 2 == 0) {
        evenNumbersImperative.add(item)
    }
}
  • لاحظ أننا في هذه الطريقة قمنا بتفصيل الخطوات تفصيلاً دقيقا للغة, وكأننا نريد اعلامها بكيفية How استخراج الأعداد الزوجية خطوة بخطوة. اي كأننا قمنا بإعطاء الأوامر.

 

النموذج الثاني: البرمجة التصريحية Declarative Programming

هي أن تطلب ماذا تريد What تاركاً تحديد الخطوات من أوامر والكيفية How لمن بنى اللغة. ولغات الـ Functional Programming اتخذتها نهجاً لها.

مثال: برنامج لاستخراج الأعداد الزوجية من متسلسلة ما:

// List
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// Declarative Way: What you want
val evenNumbersDeclarative: List<Int> = numbers.filter { it % 2 == 0 }
  • لاحظ طريقة التصريح بماذا نريد “الطلب” What بإستخدام lambda ودالة filter التي تم كتابة وتغليف خطواتها سابقاً من مؤلفي اللغة, هنا نحن فقط قمنا بإستخدامهم لطلب ماذا نريد. أي كأننا صرحنا عن ماذا نريد:
numbers.filter { it % 2 == 0 }
  • لاحظ انه يقوم بتصفية fitler جميع الارقام في المتسلسلة numbers التي باقي قسمته على اثنين يساوي صفراً, حتى نستخرج الأعداد الزوجية.

 

ملاحظة: يوجد نمودج آخر مشابه لهما ساذكره حتى لايحدث لبس وهو البرمجة الأجرائية Procedural programming يعتبر مشتق من البرمجة الأمرية Imperative ويسعى الى البرمجة التصريحية Declarative.

 

تستطيع استخدام النمودجين سواء الأمرية أو التصريحية. فمثلاً في مكتبة الكروتينات للغة الكوتلن وبخاصة التدفق Flow تستطيع كتابته بإستخدام النمودجين.

 

اين موقع لغة الكوتلن من كل هذا؟

أغلب لغات البرمجة الكائنية OOP اخذت بعض من مفاهيم الـ Declarative Programming وبعض من طرق الـ Functional Programming ولهذا ظهرت لدينا أشياء مثل Stream و RxJava و lambda وما الى ذلك. أما بالنسبة الى لغة الكوتلن فهي تعتبر لغة برمجية تحاول الاخد بالـ Declarative Programming والـ Functional Programming وهذا يعني انها تحاول الاتيان بالطريقتين معاً لتصبح كائنية OOP ودالية FP.

 

البرمجة الدالية

ببساطة هي البرمجة بإستخدام الدوال Functions. حيث ننشئ دوال نعطيها مدخلات وتعطينا ناتج. أي ان الاعتماد الاكبر في هذا النوع هو على الدوال وليس على الكائنات Objects.

وتفرض علينا البرمجة الدالية أستخدام عناصر غير قابلة للتغير Immutable. وأستخدام دوال Functions مع أختلاف قليل عن ماتعودنا عليه في دوال البرمجة الكائنية Methods. وايضاً تدخل علينا استخدام تقنية الرجعية Recursion (نستطيع استخدام ذيل الرجعية Tail Recursion لتحسين الاداء) بدلاً من تقنية التكرار Iterative كما تعودنا على الحلقات Loop كالـ For و While في البرمجة الكائنية.

وتتوعد البرمجة الدالية بالتقليل من المشاكل التي تنتج في البرنامج. ومن ابرز الطرق التي تتخذها لتحقيق هذا الشئ هو جعل كل شئ غير قابل للتغيير Immutable عند إنشائه. كالخواص Properties وغيرها من العناصر. ولهذا تحث على استخدام val الذي ينشئ عناصر غير قابلة للتغيير بدلاً من إستخدام var الذي ينشئ عناصر قابلة للتغيير.

fun main() {
    // Val vs Var
    val name: String = "Mohammad"
    var age: Int = 15
}

ففي لغة الكوتلن تستخدم كلمة val اختصاراً لكلمة value لتعريف شئ ثابت ويسمى immutable variable اي غير قابل للتغير (في الجافا نستخدم final). – اما كلمة var اختصاراً لكلمة variable لتعريف متغير ويسمى mutable variable. > دائماً قدر المستطاع فضل استخدام val على var.

أتخاذ هذا النهج سيريحنا من مشاكل كثيرة كالتي تنتج من أستخدام التزامن Concurrency والتي لانعلم كيف ومن اين جاء الناتج! كمثال لو ان لدينا خاصية لكلاس A. وتقوم بتحديثها كلاس B, وتقرئها كلاس C. اذا طرئ خطئ على تلك الخاصية سنضيع الكثير من الوقت لتتبعه. اليس من الافضل جعله ثابت حتى لاتستطيع اي كلاس تغييره. لذلك جعل العناصر غير قابلة للتغيير سيريحنا كثيراً وخاصة في التزامن وتعدد الخيوط الحاسوبية بدون القلق من ان عناصرنا سيتم تغييرهم.

 

الفرق بين الـ Function و الـ Method

وللخوض في فهم البرمجة الدالية Functional Programming يجب علينا ان نعرف ما الفرق بين كلاً من الـ Function و الـ Method.

  • الـ Method: هي ماتعارفنا عليه مسبقاً في البرمجة تحت مسمى دالة.

  • الـ Function: هي التي يطلق عليها مصطلح دالة بشكل صحيح. وهذا النوع مشتق من دوال الرياضيات. و لتحقيقه نحتاج الى اتباع شروط صارمة.

 

شروط الدالة في البرمجة الدالية

لتحقيق الدالة Function في البرمجة الدالية Functional Programming بشكل نقي Pure يجب إتباع هذه الشروط:

  • يجب أن لايكون لها اي تأثيرات جانبية Side Effect. أي لاتقوم بتغيير أي شئ في البرنامج. عملها يقتصر على أخذ معطيات واخراج ناتج فقط لاغير.
  • يجب الا تغير اي عنصر خارجها. لهذا لغة الكوتلن تشجع استخدام val لصناعة عناصر غير قابلة للتغير.
  • يجب الا تغير مدخلاتها Argument.
  • يجب الا ترمي بإخطاء Exception. لهذا لغة الكوتلن لاتشجع على رمي الـ Exception.
  • يجب ان تخرج دائماً نتيجة. لهذا لغة الكوتلن بها نوع Unit حتى نقوم بإستخدامة عندما لانريد اخراج لاشئ.
  • يجب ان يتشابه المخرج وفق المدخلات دائماً. اي أننا اذا ادخلنا لها المعادلة ١ + ١ يجب دائماً أن تخرج الناتج ٢ في كل الضروف (حتى في قطرات المطر؟).

فعندما تتحق هذه الشروط في اي دالة Method حينها نستطيع القول عنها انها دالة دالية Function. اما العكس, فعندما تخرق اي دالة Function بعض من هذه الشروط ستصبح Method عادية وتخرج من نطاق كونها دالة دالية. اخيراً بتحقيق هذه الشروط تصبح البرمجة الدالية متقدمة خطوة في قلة المشاكل.

 

الدوال في لغة الكوتلن

الدوال Functions في لغة الكوتلن تعتبر بمرتبة الدرجة الاولى First Class. وهذا يعني أننا نستطيع التعامل معهم من حيث:

  • نخزنهم في متغيراتObjects.
  • نخزنهم في هياكل البيانات Data Structure كالمصفوفات وما الى ذلك.
  • نستطيع استخدامهم كمدخلات Input للدوال الاخر.
  • نستطيع استخدامهم كمخرجات Output للدوال الاخر.
  • نستطيع إنشاء دالة بداخل داخلة اخرى.
  • ننشئ دوال خارج الفئات Classes تستقر بنفسها في ملف.

أي اننا نستطيع التعامل مع الدوال كأنهم بيانات Data او كائنات Object بحريه!

 

إنشاء الدوال في الكوتلن

fun sum(x: Int, y: Int): Int {
    return x + y
}
  • لإنشاء دالة في لغة الكوتلن نستخدم كلمة fun.
  • ثم نكتب اسم الدالة ونتبعه بقوسين ().
  • وفي داخل القوسين نقوم بكتابة اسماء المدخلات وتحديد نوعهم.
  • ولتحديد نوع المخرج نتبع ذلك بعلامة الـ :.
  • ومن ثم يأتي النطاق {} لكتابة مانريد من الدالة ان تفعله.

 

القيم الافتراضية Default Arguments في مدخلات الدوال

نستطيع تحديد قيمة المدخلات الافتراضية Default Arguments للدوال وذلك من خلال استخدام الرمز = عندما نحدد المدخلات Parameters. هذا الشئ يسمح لنا بتعدد استخدام الدالة بدون الحاجة الى مبدء الـ Overloading للدوال.

// Default Argument which is 0 value in argument z
fun sumWithDefaultArgument(x: Int, y: Int, z: Int = 0): Int {
    return x + y + z
}
// Usage
sum(2,2)
Sum(2,2,4)

 

المدخلات المتعددة في الدالة

نستطيع إستخدام كلمة vararg عند إنشاء المدخلات Parameter لتحديد السماح بإدخال العدد المرغوب فيه من قيم المدخلات Argument من قبل المبرمج.

// Normal Method with varargs
// varargs is an array of type T(Array<T>) which T in this case is Int
fun sum(vararg numbers: Int): Int {
    var result = 0
    for (number: Int in numbers) {
        result += number
    }
    return result
}
// Usage
sum(10, 5, 5, 20, 60) // 100
sum(5, 10) // 15
  •  هذا يذكرنا بالمدخلات في الدالة main الرئيسية للغة الكوتلن🤓 في الـ Hello World!.

 

إستخدام اسم المدخلات في الدوال

 تمكننا الكوتلن من استخدام اسم المدخل الـ Parameter عندما نريد اعطاء الدالة قيمة مدخل Argument. وذلك بكتابة اسم المدخل و علامة الـ = عند نداء الدالة. مناسبة عندما نريد استخدام مدخل مع vararg او عندما نريد جعل القرائة اكثر سهولة.

fun sums(vararg numbers: Int, bounce: Int): Int {
    var result = 0
    for (number: Int in numbers) {
        result += number
    }
    return result + bounce
}
// Usage
sum(10, 5, 5, 20, 60, bounce = 100) // 200
sum(5, 10, bounce = 100) //115

 

فرق المدخلات Parameter و Argument و Receiver و it للدالة الكوتلن.

تتعدد المدخلات للدوال في لغة الكوتلن, وحتى لايحدث لبس في مفهوم المدخلات هذه هي اسمائهم مع وظائفهم.

  • الـ Parameter المدخلات التي نحددها للدالة عند إنشائها.
  • الـ Argument القيم التي ندخلها على الدالة عند ندائها.
  • الـ Receiver العنصر المدخل على بعض الدوال الدالية.
  • الـ it المدخل الوحيد للدالة lambda.

مثال يوضح الفرق بين المدخلات Parameter والقيم Argument:

// Parameters is ‘x’ and ‘y’ that are typed ‘Int’
fun sum(x: Int, y: Int): Int {
  return x + y
}
// Arguments is ‘1’ and ‘2’
sum(1, 2)

 

بالنسبة للـ Receiver فهو يأتي مع:

  • الدوال الإضافية الحرفية Extension Function Literals.
  • وايضاً مع الدوال المجهولة Anonymous Function.
  • ونعبر عنه في داخل هذه الدوال بالـ this.
  • اما خارجها عند استخدامها فسيكون هو العنصر الذي طبقنا عليه الدالة عند ندائها.

مثال يوضح استخدام المدخل المستقبل Receiver:

// Receiver with Extension Function Literals
// Which Int. is the Receiver Type
val isEven: Int.() -> Boolean = {this % 2 == 0}
// Usage (which '4' represent the Receiver):
4.isEven()
// Receiver with Anonymous Function
val sum = fun Int.(other: Int): Int = this + other
// Usage (which 3 represent the Receiver):
3.sum(10000)

 

اما المدخل it يأتي مع دوال lambda التي تتميز بمدخل واحد لها فقط. مثال يوضح استخدام المدخل it:

// it
val square: (Int) -> Int = { it * it }
val capitalize: (String) -> String = { it.toUpperCase() }
val lettersCount: (String) -> Int = { it.length }
// Usage
square(3)
captialize("mohammad")
lettersCount("mohammad")
// In Android
viewModel.allNotes.observe(this, Observer { adapter.setNotes(it) })

 

كلمة it في الكوتلن تستخدم في دوال الـ Lambda التي لديها مدخل واحد Parameter ونعبر عن هذا المدخل بـ it. فلا داعي لكتابة اسم المدخل بشكل صريح Explicit, فاغلب الحالات يستطيع المترجم استدلاله. اي اننا نستخدم كلمة it اذا كانت الدالة من نوع: – لامبدا. – وبها مدخل واحد فقط.

 

في المقالة القادمة سنتعرف على أنواع وأصناف الدوال في لغة الكوتلن.

 

المصادر

كلمات دليلية: kotlin
0
إعجاب
2052
مشاهدات
0
مشاركة
1
متابع

التعليقات (0)

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !