تعلم سكالا من 0-60: الأساسيات

Scala هي لغة برمجة عالية المستوى للأغراض العامة توفر توازنًا بين تطوير البرامج الوظيفية والبرامج الموجهة للكائنات.

ما هو كل شيء عن البرمجة الوظيفية؟ بعبارات بسيطة ، تعتبر الوظائف مواطنين من الدرجة الأولى في البرمجة الوظيفية. من أجل التوسع في مجموعة أساسية من وظائف البرنامج ، نميل إلى كتابة فئات إضافية تمتد على بعض الإرشادات / الواجهات. في البرمجة الوظيفية ، تساعدنا الوظائف على تحقيق نفس الشيء.

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

لنبدأ بالأساسيات أولاً.

1. المتغيرات

يمكننا تحديد المتغيرات الثابتة باستخدام val:

scala> val name = "King"name: String = King

يمكن تعريف المتغيرات المتغيرة وتعديلها باستخدام var:

scala> var name = "King"name: String = King
scala> name = "Arthur"name: String = Arthur

نستخدمها defلتعيين تسمية لقيمة ثابتة يتم تأجيل تقييمها لوقت لاحق. هذا يعني أن قيمة الملصق يتم تقييمها بتكاسل في كل مرة عند الاستخدام.

scala> var name = "King"name: String = King
scala> def alias = namealias: String
scala> aliasres2: String = King

هل لاحظت شيئًا مثيرًا للاهتمام؟

أثناء التحديد alias، لم يتم تعيين أي قيمة alias: Stringلأنه مرتبط بشكل كسول ، عندما نستدعيها. ماذا سيحدث إذا غيرنا قيمة name؟

scala> aliasres5: String = King
scala> name = "Arthur, King Arthur"name: String = Arthur, King Arthur
scala> aliasres6: String = Arthur, King Arthur

2. التحكم في التدفق

نستخدم بيانات تدفق التحكم للتعبير عن منطق قرارنا.

يمكنك كتابة if-elseبيان على النحو التالي:

if(name.contains("Arthur")) { print("Entombed sword")} else { print("You're not entitled to this sword")}

أو يمكنك استخدام while:

var attempts = 0while (attempts < 3) { drawSword() attempts += 1}

3. المجموعات

يميز Scala صراحة بين المجموعات غير القابلة للتغيير مقابل المجموعات القابلة للتغيير - مباشرة من مساحة اسم الحزمة نفسها ( scala.collection.immutableأو scala.collection.mutable).

على عكس المجموعات غير القابلة للتغيير ، يمكن تحديث المجموعات القابلة للتغيير أو توسيعها في مكانها. يتيح لنا ذلك تغيير العناصر أو إضافتها أو إزالتها كأثر جانبي.

لكن إجراء عمليات الإضافة أو الإزالة أو التحديث على المجموعات غير القابلة للتغيير يؤدي إلى إرجاع مجموعة جديدة بدلاً من ذلك.

يتم دائمًا استيراد المجموعات غير القابلة للتغيير تلقائيًا عبر scala._ (والذي يحتوي أيضًا على الاسم المستعار لـ scala.collection.immutable.List).

ومع ذلك ، لاستخدام المجموعات القابلة للتغيير ، تحتاج إلى الاستيراد بشكل صريح scala.collection.mutable.List.

بروح البرمجة الوظيفية ، سنبني أمثلةنا بشكل أساسي على جوانب غير قابلة للتغيير في اللغة ، مع انحرافات طفيفة في الجانب القابل للتغيير.

قائمة

يمكننا إنشاء قائمة بطرق مختلفة:

scala> val names = List("Arthur", "Uther", "Mordred", "Vortigern")
names: List[String] = List(Arthur, Uther, Mordred, Vortigern)

نهج مفيد آخر هو تحديد قائمة باستخدام ::عامل سلبيات . هذا يربط عنصر الرأس مع الذيل المتبقي من القائمة.

scala> val name = "Arthur" :: "Uther" :: "Mordred" :: "Vortigern" :: Nil
name: List[String] = List(Arthur, Uther, Mordred, Vortigern)

وهو ما يعادل:

scala> val name = "Arthur" :: ("Uther" :: ("Mordred" :: ("Vortigern" :: Nil)))
name: List[String] = List(Arthur, Uther, Mordred, Vortigern)

يمكننا الوصول إلى عناصر القائمة مباشرة من خلال فهرسهم. تذكر أن Scala يستخدم الفهرسة الصفرية:

scala> name(2)
res7: String = Mordred

تتضمن بعض الطرق المساعدة الشائعة ما يلي:

list.head، والتي تُرجع العنصر الأول:

scala> name.head
res8: String = Arthur

list.tail، والتي تُرجع ذيل القائمة (التي تشمل كل شيء ما عدا الرأس):

scala> name.tail
res9: List[String] = List(Uther, Mordred, Vortigern)

جلس

Setيسمح لنا بإنشاء مجموعة غير متكررة من الكيانات. Listلا يزيل التكرارات بشكل افتراضي.

scala> val nameswithDuplicates = List("Arthur", "Uther", "Mordred", "Vortigern", "Arthur", "Uther")
nameswithDuplicates: List[String] = List(Arthur, Uther, Mordred, Vortigern, Arthur, Uther)

وهنا يتكرر "آرثر" مرتين وكذلك "أوثر".

لنقم بإنشاء مجموعة بنفس الأسماء. لاحظ كيف أنه يستبعد التكرارات.

scala> val uniqueNames = Set("Arthur", "Uther", "Mordred", "Vortigern", "Arthur", "Uther")
uniqueNames: scala.collection.immutable.Set[String] = Set(Arthur, Uther, Mordred, Vortigern)

يمكننا التحقق من وجود عنصر محدد في المجموعة باستخدام contains():

scala> uniqueNames.contains("Vortigern")res0: Boolean = true

يمكننا إضافة عناصر إلى مجموعة باستخدام طريقة + (التي تأخذ varargsوسيطات متغيرة الطول)

scala> uniqueNames + ("Igraine", "Elsa", "Guenevere")res0: scala.collection.immutable.Set[String] = Set(Arthur, Elsa, Vortigern, Guenevere, Mordred, Igraine, Uther)

وبالمثل يمكننا إزالة العناصر باستخدام -الطريقة

scala> uniqueNames - "Elsa"
res1: scala.collection.immutable.Set[String] = Set(Arthur, Uther, Mordred, Vortigern)

خريطة

Mapهي مجموعة قابلة للتكرار تحتوي على تعيينات من keyالعناصر إلى valueالعناصر ذات الصلة ، والتي يمكن إنشاؤها على النحو التالي:

scala> val kingSpouses = Map( | "King Uther" -> "Igraine", | "Vortigern" -> "Elsa", | "King Arthur" -> "Guenevere" | )
kingSpouses: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere)

يمكن الوصول إلى قيم مفتاح معين في الخريطة على النحو التالي:

scala> kingSpouses("Vortigern")res0: String = Elsa

يمكننا إضافة إدخال إلى الخريطة باستخدام +الطريقة:

scala> kingSpouses + ("Launcelot" -> "Elaine")res0: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere, Launcelot -> Elaine)

لتعديل التعيين الحالي ، نقوم ببساطة بإعادة إضافة قيمة المفتاح المحدثة:

scala> kingSpouses + ("Launcelot" -> "Guenevere")res1: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, Vortigern -> Elsa, King Arthur -> Guenevere, Launcelot -> Guenevere)

لاحظ أنه نظرًا لأن المجموعة غير قابلة للتغيير ، تقوم كل عملية تحرير بإرجاع مجموعة جديدة ( res0، res1) مع تطبيق التغييرات. kingSpousesتبقى المجموعة الأصلية دون تغيير.

4. المدمِجات الوظيفية

الآن بعد أن تعلمنا كيفية تجميع مجموعة من الكيانات معًا ، دعنا نرى كيف يمكننا استخدام الدمج الوظيفي لتوليد تحويلات ذات مغزى في مثل هذه المجموعات.

بكلمات جون هيوز البسيطة:

المُدمج هو وظيفة تقوم ببناء أجزاء البرنامج من أجزاء البرنامج.

An in-depth look at how combinators work is outside of this article’s scope. But, we’ll try to touch upon a high-level understanding of the concept anyhow.

Let’s take an example.

Suppose we want to find names of all queens using the kingSpouses collection map that we created.

We’d want to do something along the lines of examining each entry in the map. If the key has the name of a king, then we’re interested in the name of it’s spouse (i.e. queen).

We shall use the filter combinator on map, which has a signature like:

collection.filter( /* a filter condition method which returns true on matching map entries */)

Overall we shall perform the following steps to find queens:

  • Find the (key, value) pairs with kings’ names as keys.
  • Extract the values (names of queen) only for such tuples.

The filter is a function which, when given a (key, value), returns true / false.

  1. Find the map entries pertaining to kings.

Let’s define our filtering predicate function. Since key_value is a tuple of (key, value), we extract the key using ._1 (and guess what ._2 returns?)

scala> def isKingly(key_value: (String, String)): Boolean = key_value._1.toLowerCase.contains("king")
isKingly: (key_value: (String, String))Boolean

Now we shall use the filter function defined above to filter kingly entries.

scala> val kingsAndQueens = kingSpouses.filter(isKingly)
kingsAndQueens: scala.collection.immutable.Map[String,String] = Map(King Uther -> Igraine, King Arthur -> Guenevere)

2. Extract the names of respective queens from the filtered tuples.

scala> kingsAndQueens.values
res10: Iterable[String] = MapLike.DefaultValuesIterable(Igraine, Guenevere)

Let’s print out the names of queens using the foreach combinator:

scala> kingsAndQueens.values.foreach(println)IgraineGuenevere

Some other useful combinators are foreach, filter, zip, partition, find.

We shall re-visit some of these after having learnt how to define functions and passing functions as arguments to other functions in higher-order functions.

Let’s recap on what we’ve learned:

  • Different ways of defining variables
  • Various control-flow statements
  • بعض الأساسيات حول المجموعات المختلفة
  • نظرة عامة على استخدام الدمج الوظيفي في المجموعات

أتمنى أن تكون قد وجدت هذه المقالة مفيدة. إنها الأولى في سلسلة من المقالات لمتابعة تعلم Scala.

في الجزء الثاني ، سنتعرف على تعريف الفئات والسمات والتغليف والمفاهيم الأخرى الموجهة للكائنات.

لا تتردد في إخباري بتعليقاتك واقتراحاتك حول كيفية تحسين المحتوى. حتى ذلك الحين ، ❤ الترميز.