
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)
Ще одним зручним підходом є визначення списку за допомогою ::
оператора мінусів . Це поєднує елемент head з рештою хвоста списку.
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)
Ми можемо перевірити наявність конкретного елемента в Set, використовуючи 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.
- 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
- Some basics about various collections
- Overview of using functional combinators on collections
I hope you found this article useful. It is first in a series of articles to follow on learning Scala.
In part two, we’ll learn about defining classes, traits, encapsulation and other object-oriented concepts.
Please feel free to let me know your feedback and suggestions on how I can improve the content. Until then, ❤ coding.