ما هي سكالا (Scala) ؟

Scala مشتقة من Scalable , ومعناها أن اللغة قادرة على التوسع و النمو بسبب بساطة ال syntax الأولى الخاص بها وذلك لأنها مصممة بشكل جيد جداً على قواعد قليلة , و لكنها تسمحُ بتمدد اللغة و خلق أدوات جديدة لغرض خاص لم تكن موجودة فى اللغة من قبل (أمثلة على ذلك لاحقاً) .

Scala أيضاً تستطيع التعامل مع كود java بكفاءة شديدة فتستطيع استيراد java libraries و ترث من java classes وهكذا .. وهي تعتمد عل java virtual machine (JVM)فى عملها لذلك فهى تكاد تضاهي لغة الجافا فى السرعة.

تتميز لغة سكالا (Scala) بالعديد من الملامح المميزة لها و التي سيتم شرحها بالتفصيل مثل: (Type Safe – Object-Oriented – Functional Language – …).

مصمم لغة سكالا (Scala) ومطور Generic Java هو مارتن أودرسكي Martin Odersky  (باحث في معهد EPFL – متخصص في تصميم لغات البرمجة كما أنه مدير مشروع مدير مشروع LAMP!!)

 

سكالا (Scala)

أعتقد ان لغة البرمجة سكالا (Scala) قد وجدت إستجابة للكثير من شكاوى المبرمجين على وجه الكرة الأرضية :) , فهي كما سنرى تجمع العديد من المزايا المختلفة التي كان يعتقد مسبقاً أنها لا تجتمع في أداه برمجية واحدة.

يمكننا الآن الدخول مباشرة في كتابة بعض الكود والبدء بعبارة :

1
2
Scala> println("Hello, world!")
Hello, world!

بالرغم انه سطر واحد إلا انه يوجد العديد من الاسئلة :

  1. أين يتم كتابة هذا الكود؟
  2. هل سكالا (Scala) لغة compiled أم interpreted ؟

أخبرتكم من قبل أن سكالا (Scala) تجمع العديد من المزايا المدهشة التي لا تجتمع في أي لغة تقليدية أخرى!!.

سكالا (Scala) لغة تقبل العمل بنظام Compiled كما انه يمكن استخدامها في إطار Interpreted.

لذلك يمكنك كتابة هذا الكود في ملف منفصل أو يمكنك معالجة هذا الامر من خلال سطر الاوامر ، و سيتم استخدام سطر الاوامر في شرح هذا الدرس.

اذا بعد ما انتهينا من المثال التاريخي Hello World دعنا نتعمق أكثر و أكثر!!

ولكن أولاً قبل التعمق لنجرب حيلة صغيرة و هي إستخدام سطر الاوامر كآلة حاسبة… يمكنك كتابة :

1
2
Scala> 1+2
res0: Int =3

الحقيقة انا لم اذكر هذا الامر بغرض إستخدام الآلة الحاسبة… فأنت لن تتعلم لغة برمجة لإجراء حاصل جمع رقمين لكن هناك ملحوظة أود ان أوضحها وهي:

1
res0: int = 3

يقوم سطر الاوامر بحجز متغير و تعيين له اسم –res0- و يضع به ناتج الامر السابق ، اي انه يمكنك إستخدام الناتج الخاص بالعملية السابقة بالشكل التالي:

1
2
Scala> res0 * 3
res1: Int =9
*

إذا كنت صاحب خلفية برمجية فأنت معتاد على مفهوم المتغيرات و التي سوف نراجع طريقة التعامل معها في سكالا (Scala) .

أما ان كانت هذة أولى الاسطر البرمجية لك فموضوع المتغيرات يمكن التعبير عنه ببساطة أنها أماكن في الذاكرة ينسب لها أسماء معينة حتى يسهل التعامل معها أو مع القيم المخزنة بها و تختلف فيما بينها حسب نوعها و طبيعتها كما سنرى لاحقا.

يمكنك الإعلان عن متغير معين بالشكل التالي:

1
Scala> val msg = "Hello, world!"

في هذا السطر تم الإعلان عن متغير اسمة “msg” عن طريق الامر val و تم وضع القيمة “Hello world” به

لقد تكلمنا سابقا عن ان سكالا – Scala تتبع نظام Static Types في معالجة المتغيرات و لكننا لم نعلن نوع المتغير – راجع تعريف المصطلحات –

من أهم مزايا سكالا – Scala هو نظام Type Inference: وهو نظام مسؤل عن استنتاج نوع المتغير تلقائيا وقت كتابة الكود و يتم تعيين نوعه وقت التصميم و ليس أثناء التنفيذ ، وبذلك تكون سكالا – Scala قد جمعت بين تقليل حجم الكود و ال Type Safety .!

وبالرغم من وجود نظام Type Inference إلا ان سكالا – Scala لم تحرمنا من امكانية الاعلان عن المتغيرات بشكل مباشر و صريح حيث يمكنك الإعلان عن المتغير بالشكل التالي:

1
2
Scala> val msg3: String = "Hello yet again, world!"
msg3: String = Hello yet again, world!

حيث String هو نوع المتغير

الان يمكننا تجربة شئ آخر مع المتغيرات…

ما رأيك بالسطر التالي

1
2
Scala> var msg3: String = "Hello yet again, world!"
msg3: String = Hello yet again, world!

هل تلاحظ اين الاختلاف … نعم انه var بدلا من val … و لكنها نفس النتيجة!! ما الفائدة إذا !!؟

تحدثنا سابقا عن مصطلحين Mutable , Immutable !!

تستخدم val لتعريف متغيرات من نوع immutable و التي لا يمكن نغييرها بعد وضع قيمة بها!

أما var فهي للأنواع ال Mutable و التي يمكنك تغيرها لاحقا !!

مثال:

1
2
3
4
5
6
7
8
9
10
11
12
Scala> val msg: String = "Hello yet again, world!"
msg: String = Hello yet again, world!
Scala> msg = "Goodbye cruel world!"
6: error: reassignment to val
msg = "Goodbye cruel
Scala> var greeting = "Hello, world!"
greeting: java.lang.String = Hello, world!
Scala> greeting = "Leave me alone, world!"
greeting: java.lang.String = Leave me alone, world!
*

الدوال (Functions)

كيفية التصريح بدالة how to declare a function ؟

فلنتأمل هذا المثال:

1
2
3
Scala> def max(x:Int, y:Int):Int = {
    | if(x > y)return x else return y }
max: (Int,Int)Int

عند طلب الدالة Function call :

1
2
Scala> max(4,5)
res0: Int = 5

ملاحظات:
  1. لو لا تقوم بارجاع قيمة اقدر احذف = و تسمي unit مش دالة
  2. كتابة نوع ال return value ليس ضروري يعني ممكن لا يكتب و يفهمه ال type inference
  3. كتابة نوع ال parameters ضروري .
  4. يمكن الاستغناء عن كلمة return
  5. يمكن الاستغناء عن {} فى حالة اذا كان جسم الدالة سطر واحد

بالتالى يمكن كتابة الدالة كالاتي:

1
Scala> def max(x:Int, y:Int)=if(x > y) x else y

6. اذا كانت الدالة لا تاخذ parameters يمكن حذف () الفارغين اثناء الطلبcall وبالتالى تقدر تعاملها كـمتغير

1
2
3
4
5
6
7
8
Scala> def teet()=5
teet: ()Int
Scala> teet
res3: Int = 5
Scala> teet()
res4: Int = 5

7. اذا كانت الدالة يمكن ان ترجع اكثر من نوع من القيم حسب احتمالات مختلفة يقوم ال type inference بجعل نوع القيمة ِAnyVal حتي يسمح باي قيمة

مثال:

1
2
Scala> def func(x:Int)=if(x > 0)1 else 0.1
func: (Int)AnyVal

لانه يمكن ان ترجع Int أو double .

8. طريقة اخري للتصريح بدالة Literal function

هذه الطريقة تسمح بالتصريح بدالة بدون اسم (ع الطاير) و سيكون لها استخدامات كثيرا مستقبلا فى higher order functions

1
2
Scala> (x:Int) => x+x
res9: (Int) => Int =
*

اللاجمل

تعودنا فى لغات البرمجة على عنوان كبير فى اي كتاب ألا و هو الجمل او Statements و تشمل if conditions و loops مثل for و while

لكن هنا فى سكالا يوجد اختلاف فى المعاملة فسكالا تعامل تقريبا كل شئ كتعبير expression وليس كجملة statement

لذلك علينا أولا معرفة ما الفرق بينهما ؟؟

  • الجملة: هي كل ما يفعل شئ و ليس له قيمة فى حد ذاته مثل conditions التى تحدد ما تقوم به بناء على شروط سابقة و loops التي تكرر ما تفعله لمده معينة محددة بشروط أيضا
  • التعبير: هو كل ماله نتيجة اي يمكن ان يوضع على الجانب الايمن لعلامة = (assign operator) أي يمكن تخزين قيمته فى متغير

في سكالا كل شئ تعبير يمكن أن يرجع قيمه.

*

مثال:

1
2
Scala> val x=if(5 >6)11 else 12
x: Int = 12

فى هذا المثال تم تعيين القيمة x لناتج الجملة/التعبير if

*

LOOPS

مثال على while loop تطبع ال arguments التي تكتب بجانب اسم البرنامج فى سطر الاوامر:

1
2
3
4
5
Scala> var i = 0
while (i < args.length) {
println(args(i))
i += 1
}

نلاحظ هنا loop عادية جدا ولكن كما قلنا سابقا ان في functional programming يفضل عدم استخدام المتغيرات و loops فما الحل ?

  • هذا حل مبدأي تخلصنا فيه من المتغير و لكن بقيت loop
1
Scala> for (arg <- args) println(arg)
  • هذا الحل النهائي يخلصنا من المتغير و ال loop بأن نستخدم داله foreach تاخذ argument عبارة عن دالة اخري تاخذ كل عنصر من عناصر args على حده كـ argument وتطبق عليه println
1
Scala> args.foreach(arg => println(arg))
  • و يمكن اختصارها ايضا هكذا :
1
Scala> args.foreach(println)

حيث ان دالة foreach لا ترجع الا قيمة واحدة و هى عناصر args .

*

المجموعات (Collections)

أولاً الصفوف Arrays :-

  • التصريح بصف جديد:
1
2
Scala> val GreetStrings = new Array[String](3)
GreetStrings: Array[String] = Array(null, null, null)
  • طريقة اخري للتصريح بصف :
1
2
Scala> val x=Array("ahmed","mohamed")
x: Array[String]= Array(ahmed, mohamed)
  • استرجاع قيمه معينة وفي هذه الحالة null :
1
2
Scala> GreetStrings(0)
res0: String = null

ملاحظة مهمة جدا : الحقيقة ان الـ syntax السابق هو اختصار لـ

1
2
Scala> GreetStrings.apply(0)
res3: String = null

وهذا يعني ان عملية استرجاع قيمة من صف ما هي الا دالة function ولكن يمكن حذف اسمها تبعا لقواعد الـ syntax الخاص بسكالا و مثل تلك القواعد البسيطة هو ما يعطي لسكالا قدرتها على التوسع و التشكل.

  • تعيين قيمة فى صف :
1
Scala> GreetStrings(0)="Hello"

كالعاده سيكون اختصار لداله و هي :

1
Scala> GreetStrings.update(0,"Hello)

مثال اخر على قدرة سكالا على التشكل:

1
2
3
4
Scala> for (i <- 0 to 2) println(GreetStrings(i))
Hello
null
null

هذا الـ genetartor هو ايضا اختصار لدالة و هي :

1
2
Scala> 0.to(2)
res7: Range.Inclusive = Range(0, 1, 2)

القاعدة: هي ان فى سكالا عند طلب دالة call a function يمكن حذف النقطة و استبداله بمسافة space .

و يمكن ايضا حذف الاقواس () اذا كان لها parameter واحد فقط و استبدالهم بمسافة

ثانيا : القوائم Lists :

  • التصريح بقائمة :
1
2
Scala> val x=List(1,2,3,4)
x: List[Int] = List(1, 2, 3, 4)
  • الفرق بين الصف و القائمة :
  1. الصف mutable و القائمة immutable .
  2. الصف عدد عناصره ثابت اما فى القائمة فعدد العناصر قابل للزيادة.
  • الاضافة للقائمة :

فلنتأمل الامثلة التالية

1
2
3
4
5
6
7
8
9
10
11
Scala> val onetwo=List(1,2)
onetwo: List[Int] = List(1, 2)
Scala> val threefour=List(3,4)
threefour: List[Int] = List(3, 4)
Scala> onetwo :: threefour
res22: List[Any] = List(List(1, 2), 3, 4)
Scala> onetwo ::: threefour
res23: List[Int] = List(1, 2, 3, 4)

سنلاحظ الفرق بين :: و :::

- :: بياخذ قبله اي نوع و بعده قائمة و يضع ما فبله كـ عنصر اول فى القائمة و لذلك يمكن ان يستخدم فى التصريح بقائمة عن طريق اضافة عنصر لقائمة فارغة.

1
2
Scala> 1::List
res24: List[Int] = List(1)

أو يمكن استخدام Nil لتعبير عن قائمة فارغة

1
2
Scala> 1::Nil
res27: List[Int] = List(1)

- ::: يأخذ قبله قائمة و بعده قائمة و يقوم باضافتهم لبعضهم البعض concatenation

ملاحظة هامة جدا :

نلاحظ هنا ان :: و ::: ما هم الا دوال و اننا نستخدم نفس القاعدة الخاصة باستبدال النقط و الاقواس بمسافات مع قاعدة اخري صغيرة ألا و هي : -

  • اذا انتهي اسم الدالة بـ : تُنفذ من اليمين لليسار بمعني اخر فى حالة
1
Scala> 1::Nil

تعتبر Nil هى parameter الاولى للدالة و 1 هو ال parameter الثانية.

بعض الدوال الخاصة بالقوائم :

1
2
Scala> val x=List(1,2,3,4)
x: List[Int] = List(1, 2, 3, 4)

سنلاحظ هنا اننا نقوم بعمل دالة ع الطاير تحوي اختبار معين كـ argument لدالة تانية لتقوم بتنفيذ هذا الاختبار بطريقة معينة

تطبق هذا الاختبار علي كل عناصر القائمة و اذا كان موجود ترجع true

1
2
Scala> x.exists(s => s==3)
res32: Boolean = true

ترجع قائمة بها العناصر التى تنطبق مع الاختبار فقط

1
2
Scala> x.filter(s=> s>3
res33: List[Int] = List(4)

تقوم بتنفيذ مهمة ما على كل عناصر القائمة

1
2
3
4
5
6
7
8
Scala> x.map(s=> s*2)
res34: List[Int] = List(2, 4, 6, 8)
Scala> x.isEmpty
res36: Boolean = false
Scala> x.length
res37: Int = 4

تحويل القائمة لـ string و وضع بين عناصرها الفاصل المطلوب و فى هذه الحالة مجرد مسافة

1
2
Scala> x.mkString
res39: String = 1 2 3 4

ترجع اول عنصر فى القائمة

1
2
Scala> x.head
res40: Int = 1

ترجع باقى عناصر فى القائمة بدون العنصر الاول

1
2
Scala> x.tail
res41: List[Int] = List(2, 3, 4)

ترجع اخر عنصر فى القائمة

1
2
Scala> x.last
res43: Int = 4

ترجع true فقط اذا كان الاختبار true على كل عناصر القائمة

1
2
3
4
5
Scala> x.forall(s=> s>3)
res42: Boolean = false
Scala> x.reverse
res44: List[Int] = List(4, 3, 2, 1)

تحذف اول عنصرين فى القائمة

1
2
Scala> x.drop(2)
res47: List[Int] = List(3, 4)

تحذف اخر عنصرين فى القائمة

1
2
Scala> x.dropRight(2)
res48: List[Int] = List(1, 2)

ثالثا : Tuples

تشبه القوائم و لكنها تختلف فى انها يمكن ان تحتوي على أنواع مختلفة من القيم على عكس القوائم التى تحتوي على نوع واحد فقط ولها حد اقصى من حيث عدد العناصر و هو 22

  • التصريح بـ Tupule
1
2
Scala> val pair=(99,"EgyptAir)
pair: (Int, java.lang.String) = (99,EgyptAir)
  • استرجاع قيمة
1
2
3
4
5
Scala> pair._1
res49: Int = 99
scala> pair._2
res50: java.lang.String = egypt

رابعا : المجموعات Sets :

هي عبارة عن قائمة ذات عناصر فريدة أي لا تسمح بالتكرار

  • التصريح بها:
1
2
Scala> val test=Set(1,1,3,4,5,6,6)
test: scala.collection.immutable.Set[Int] = Set(5, 3, 1, 6, 4)
  • هي ايضا immutable و لكن يمكن جعلها mutable عن طريق تنفيذ هذا الأمر قبل التصريح بها
1
2
3
4
Scala> import scala.collection.mutable.Set
import scala.collection.mutable.Set
Scala> val test=Set(1,1,3,4,5,6,6)
test: scala.collection.mutable.Set[Int] = Set(5, 3, 1, 6, 4)

خامسا : Maps :

  • التصريح به :
1
2
Scala> val x=Map("name"->"ahmed", "age"->55)
x: scala.collection.immutable.Map1 = Map(name -> ahmed, age -> 55)
  • استرجاع القيمة :
1
2
Scala> x("name")
res51: Any = ahmed

*

خريطة سكالا