LEARN X · ЗА 17 МИН

Scala

Scala за 17 минут: весь язык на одной странице через закомментированный код — val/var, match, коллекции, трейты, case-классы, Option.

Scala — это гибрид объектно-ориентированного и функционального программирования на JVM. Статическая типизация с мощным выводом типов, иммутабельность по умолчанию и невероятно гибкое сопоставление с образцом. Весь язык — ниже, в комментариях к рабочему коду.

Вывод и комментарии

// Это однострочный комментарий

/* Это
   многострочный
   комментарий */

/** Документирующий комментарий (Scaladoc) для классов и методов */

println("Привет, Scala!") // печать со переводом строки -> Привет, Scala!
print("без переноса")      // печать без переноса строки
Console.println(42)        // то же самое, через объект Console

Переменные и типы

В Scala тип почти всегда выводится автоматически, но его можно указать явно после двоеточия.

val x = 10        // val — неизменяемая (immutable) переменная, как final
// x = 20         // ОШИБКА компиляции: переприсвоить val нельзя

var y = 5         // var — изменяемая переменная
y = 7             // ок, var можно переприсваивать

// Явное указание типа: имя: Тип = значение
val n: Int = 42                 // 32-битное целое
val big: Long = 9000000000L     // 64-битное целое
val pi: Double = 3.14           // число с плавающей точкой
val flag: Boolean = true        // true / false
val letter: Char = 'A'          // символ в одинарных кавычках
val name: String = "Аня"        // строка в двойных кавычках

// Вывод типов: Scala сама определяет тип справа
val auto = 3.14   // компилятор выводит Double
val text = "hi"   // компилятор выводит String

println(n.getClass)  // -> int (тип во время выполнения)

Строки

Интерполяция строк — главный способ подставлять значения.

val who = "мир"
val count = 3

// s-интерполятор: подставляет значения через $ и ${...}
println(s"Привет, $who!")           // -> Привет, мир!
println(s"Сумма: ${count + 1}")     // -> Сумма: 4

// f-интерполятор: форматирование как в printf
val price = 9.5
println(f"Цена: $price%.2f руб.")   // -> Цена: 9.50 руб.

// raw-интерполятор: не обрабатывает спецсимволы
println(raw"Строка\nбез переноса")  // -> Строка\nбез переноса

// Многострочная строка через тройные кавычки
val poem = """Первая строка
              Вторая строка"""

// Методы строк
val str = "Scala"
println(str.length)        // -> 5
println(str.toUpperCase)   // -> SCALA
println(str.reverse)       // -> alacS
println(str(0))            // -> S (символ по индексу)
println(str.contains("al"))// -> true
println("a,b,c".split(",").mkString("-")) // -> a-b-c

Операторы и условия

В Scala if и match — это выражения: они возвращают значение.

// Арифметика
println(7 / 2)    // -> 3 (целочисленное деление)
println(7 % 2)    // -> 1 (остаток)
println(7.0 / 2)  // -> 3.5

// Сравнения и логика: < > <= >= == !=  && || !
println(3 < 5 && 5 > 2)   // -> true

// if/else как ВЫРАЖЕНИЕ — возвращает результат
val age = 18
val status = if (age >= 18) "взрослый" else "ребёнок"
println(status)   // -> взрослый

// match — мощное сопоставление с образцом (замена switch)
val day = 3
val text = day match {
  case 1 => "понедельник"
  case 6 | 7 => "выходной"     // несколько значений через |
  case _ => "будний день"      // _ — «всё остальное» (default)
}
println(text)     // -> будний день

Циклы

// for с генератором: 1 to 3 включает 3, 1 until 3 — нет
for (i <- 1 to 3) print(i)      // -> 123
println()
for (i <- 1 until 3) print(i)   // -> 12
println()

// for с шагом и фильтром (guard)
for (i <- 1 to 10 by 2 if i != 5) print(s"$i ") // -> 1 3 7 9
println()

// Вложенный for: несколько генераторов сразу
for (i <- 1 to 2; j <- 1 to 2) print(s"($i,$j)") // -> (1,1)(1,2)(2,1)(2,2)
println()

// while — классический цикл
var k = 0
while (k < 3) { print(k); k += 1 } // -> 012
println()

// for-yield: собирает результаты в новую коллекцию
val squares = for (i <- 1 to 4) yield i * i
println(squares)   // -> Vector(1, 4, 9, 16)

Коллекции

По умолчанию коллекции иммутабельны: методы возвращают новую коллекцию, не меняя исходную.

// List — иммутабельный связный список
val list = List(1, 2, 3)
println(list.head)       // -> 1 (первый элемент)
println(list.tail)       // -> List(2, 3) (всё кроме первого)
val list2 = 0 :: list    // :: добавляет в начало -> List(0, 1, 2, 3)
val list3 = list :+ 4    // :+ добавляет в конец -> List(1, 2, 3, 4)

// Vector — иммутабельный массив с быстрым доступом по индексу
val vec = Vector("a", "b", "c")
println(vec(1))          // -> b

// Map — словарь ключ -> значение
val ages = Map("Аня" -> 25, "Боб" -> 30)
println(ages("Аня"))             // -> 25
println(ages.getOrElse("X", 0))  // -> 0 (если ключа нет)
val ages2 = ages + ("Вера" -> 28) // новая Map с добавленной парой

// Set — множество уникальных элементов
val set = Set(1, 2, 2, 3)
println(set)             // -> Set(1, 2, 3) (дубли убраны)
println(set.contains(2)) // -> true

// Array — изменяемый массив фиксированной длины (на JVM)
val arr = Array(10, 20, 30)
arr(0) = 99              // элементы можно менять
println(arr.mkString(",")) // -> 99,20,30

Функции

// def — определение метода: def имя(параметры): ТипРезультата = тело
def add(a: Int, b: Int): Int = a + b
println(add(2, 3))   // -> 5

// Тело из нескольких строк — в фигурных скобках, последнее выражение = результат
def greet(name: String): String = {
  val prefix = "Привет"
  s"$prefix, $name!"
}

// Параметры по умолчанию и именованные аргументы
def power(base: Int, exp: Int = 2): Int = math.pow(base, exp).toInt
println(power(3))            // -> 9 (exp по умолчанию = 2)
println(power(2, exp = 5))   // -> 32

// Анонимные функции (лямбды): (параметры) => тело
val square = (x: Int) => x * x
println(square(4))   // -> 16

// Каррирование: функция возвращает функцию
def multiply(a: Int)(b: Int): Int = a * b
println(multiply(3)(4))      // -> 12

// Частичное применение: фиксируем первый аргумент
val triple = multiply(3) _
println(triple(5))   // -> 15

// Функции высшего порядка (HOF): map, filter, reduce
val nums = List(1, 2, 3, 4, 5)
println(nums.map(_ * 2))         // -> List(2, 4, 6, 8, 10)
println(nums.filter(_ % 2 == 0)) // -> List(2, 4)
println(nums.reduce(_ + _))      // -> 15 (свёртка: 1+2+3+4+5)
println(nums.foldLeft(100)(_ + _)) // -> 115 (с начальным значением)

Классы и объекты

// class — параметры в скобках сразу становятся полями конструктора
class Point(val x: Int, val y: Int) {
  // метод
  def shift(dx: Int, dy: Int): Point = new Point(x + dx, y + dy)
  override def toString: String = s"($x, $y)"
}
val p = new Point(1, 2)
println(p.x)              // -> 1
println(p.shift(3, 3))    // -> (4, 5)

// object — синглтон (один-единственный экземпляр), аналог static
object Counter {
  var total = 0
  def inc(): Unit = { total += 1 }
}
Counter.inc(); Counter.inc()
println(Counter.total)    // -> 2

// case class — класс для данных: автоматически даёт toString,
// сравнение по значению, copy и работу в match
case class User(name: String, age: Int)
val u = User("Аня", 25)    // new не нужен
println(u)                 // -> User(Аня,25)
println(u.age)             // -> 25
val u2 = u.copy(age = 26)  // копия с изменённым полем
println(u == User("Аня", 25)) // -> true (сравнение по значению)

Трейты

Trait — это как интерфейс, но может содержать реализацию. Класс может подмешивать (mixin) несколько трейтов.

// trait описывает поведение
trait Greeter {
  def name: String                       // абстрактный метод
  def greet(): String = s"Привет, я $name" // метод с реализацией
}

trait Loggable {
  def log(msg: String): Unit = println(s"[LOG] $msg")
}

// extends для первого трейта, with — для остальных (миксины)
class Robot(val name: String) extends Greeter with Loggable

val r = new Robot("R2D2")
println(r.greet())   // -> Привет, я R2D2
r.log("запуск")      // -> [LOG] запуск

Сопоставление с образцом

match умеет извлекать данные из case-классов, проверять типы и применять условия (guards).

case class Animal(name: String, legs: Int)

val a = Animal("паук", 8)

val desc = a match {
  // извлечение полей + guard (условие после if)
  case Animal(n, legs) if legs > 4 => s"$n — многоног ($legs)"
  case Animal(n, 4)               => s"$n — четвероногое"
  case Animal(n, _)               => s"$n — что-то иное"
}
println(desc)   // -> паук — многоног (8)

// Сопоставление по типу
def describe(x: Any): String = x match {
  case i: Int    => s"целое $i"
  case s: String => s"строка '$s'"
  case _: Boolean => "логическое"
  case _         => "неизвестно"
}
println(describe(42))      // -> целое 42
println(describe("hi"))    // -> строка 'hi'

// Разбор списков по образцу
List(1, 2, 3) match {
  case head :: tail => println(s"голова=$head, хвост=$tail")
  case Nil          => println("пустой список")
}
// -> голова=1, хвост=List(2, 3)

Option и обработка отсутствия

Option — типобезопасная замена null. Значение либо есть (Some), либо нет (None).

// Option[T] = Some(значение) | None
val found: Option[Int] = Some(42)
val empty: Option[Int] = None

// getOrElse — достать значение или вернуть запасное
println(found.getOrElse(0))  // -> 42
println(empty.getOrElse(0))  // -> 0

// map применяется только если значение есть
println(found.map(_ * 2))    // -> Some(84)
println(empty.map(_ * 2))    // -> None

// Map.get возвращает Option — безопасный доступ
val ages = Map("Аня" -> 25)
println(ages.get("Аня"))     // -> Some(25)
println(ages.get("Боб"))     // -> None

// Разбор Option через match
def show(o: Option[Int]): String = o match {
  case Some(v) => s"есть: $v"
  case None    => "пусто"
}
println(show(found))   // -> есть: 42
println(show(empty))   // -> пусто

Для понимания

for-comprehension — выразительный способ комбинировать генераторы, фильтры и yield; особенно мощен с Option и коллекциями.

// Комбинация генераторов с фильтром и yield
val pairs = for {
  x <- 1 to 3
  y <- 1 to 3
  if x < y          // фильтр
} yield (x, y)
println(pairs)   // -> Vector((1,2), (1,3), (2,3))

// for над Option: если хоть один None — результат None
val a: Option[Int] = Some(3)
val b: Option[Int] = Some(4)
val sum = for {
  x <- a
  y <- b
} yield x + y
println(sum)     // -> Some(7)

// Конвейер обработки коллекций (pipeline) — читается сверху вниз
val result = List(1, 2, 3, 4, 5, 6)
  .filter(_ % 2 == 0)   // оставить чётные -> List(2, 4, 6)
  .map(_ * 10)          // умножить -> List(20, 40, 60)
  .sum                  // сложить -> 120
println(result)  // -> 120

// groupBy + map: сгруппировать и посчитать
val words = List("кот", "кит", "пёс", "крот")
val byLen = words.groupBy(_.length)
println(byLen)   // -> Map(3 -> List(кот, кит, пёс), 4 -> List(крот))
Поддержать проект