Scala

Scala #

Переменные #

val x       // константа
def x       // вычисляются каждый раз, когда на них ссылаются
lazy val x  // вычисляется не больше одного раза в момент первого к ней обращения
var x       // "настоящая" переменная, которая может быть переопределена

Области видимости #

{
    val x = "Inner"
    println(x)
}
println(x)   // Error!!1!

Пространства имен #

Идентификаторы: #

  • стабильные
    • package
    • параметр функции
    • val
    • lazy val
    • object
  • нестабильные:
    • def

Импортирование: #

import com.example.Module.{name1, name2}      // нескольких сущностей из пакета
import com.example.Module._                   // все имена из пространства
import com.example.Module.{name1 => newName}  // переименование при импорте
import com.example.Module.{_, name1 => _}     // импортирование всех имен, кроме указанных

Типы #

A <: B      // Тип А является подтипом типа В, когда все значения А могут использоваться как значения типа В.

A <: Any    // Надтип для любого другого типа

A <: AnyRef // Надтип для любых ссылочных типов
A <: AnyVal // Надтип для любых примитивных типов

val x: String = "Some string"
val y: AnyRef = x
val z: Any = y

val a: Int = 4
val b: AnyVal = a
val c: Any = b

Nothing <:  // является подтипом любого типа, в основном используется при генерации ошибок

Примитивные типы: #

  • Целые числа (знаковые)
    • Byte (1 byte)
    • Short (2 byte)
    • Int (4 byte)
    • Long (8 byte)
  • Дробные числа:
    • Float (4 byte)
    • Double (8 byte)
  • Символы
    • Char (2 byte)
  • Булевы значения:
    • Boolean
  • Единичный тип:
    • Unit (значение этого типа константно, используется, например, для типа значения, возвращаемого из функции, если оно нас не интересует)

Числа #

val a = 3       // Int
val b = -5L     // Long
val c = 10.0    // Double
val d: Float    // Явно указанный Float
val e = 1.03e1  // Вариант записи с мантиссой (научная форма записи)

Алгебраические операции с числами: #

val x = -5
val y = 3

x + y   // Сложение
x - y   // Вычитание
x * y   // Умножение
x / y   // Целочисленное деление
x % y   // Взятие остатка
-x      // Унарный минус

Побитовые операции с числами: #

val x = 0xF
val y = 0XA1

x >> y  // побитовый сдвиг вправо
x << y  // -//- влево
x | y   // -//- ИЛИ
x & y   // -//- И
x ^ y   // -//- исключающее ИЛИ
~x      // -//- инверсия

Порядок операций (по возрастанию, определяется по первому символу оператора) #

  1. символы
  2. |
  3. ^
  4. &
  5. = !
  6. <>
  7. :
  8. + -
  9. * / %
  10. все остальные знаки

Ссылочные типы для больших чисел #

val x = BigInt(10)      // Целочисленный тип, содержащий неограниченное количество цифр
val y = BigDecimal(10)  // Дробное число, так же не ограниченное по длине
val z = BigInt("1000")  // Эти типы могу быть инициализированы как числом, так и строчным представлением числа

x pow 100               // Операция возведения в степень для BigInt, не дающая переполнения
x gcd y                 // Нахождение наибольшего общего делителя для двух больших чисел

Булевые значения #

val a = true
val b = false

Операции сравнения: #

val a = 1 > 5   // Больше
val b = 1 < 5   // Меньше
val c = 1 <= 5  // Меньше или равно
val d = 1 >= 5  // Больше или равно
val e = 1 == 5  // Равно
val f = 1 != 5  // Не равно

Логические операции: #

val x = 1 > 5
val y = "Example" contains "a"

x && y  // И
x || y  // ИЛИ
!x      // НЕ

Равенство: #

Сравнивать объекты разных типов можно, но компилятор выдаст предупреждение.

Сравнение по ссылке:

val x = "Te"
val y = "st"
val a = x + y
val b = x + y

a == b  // true, так как значения одинаковые
a eq b  // false, так как ссылки разные

В Java все константные строки интернированы: компилятор производит оптимизацию, в результате чего, одинаковые строковые литералы ссылаются на один и тот же объект в памяти. Поэтому:

val s1 = "foo"
val s2 = "foo"
println(s1 eq s2) // true

Строки #

val name = "Username"               // Объявление строки
val greet = "Hello" + name + "!!!"  // Конкатенация
val greet2 = s"Hello $name!!!"      // Интерполяция
val multiLine =
  """
   multi
   line
   string
  """

Операции: #

val s = "aaabbb"

s.startsWith("aa")
s.endsWith("bb")
s.contains("ab")

s.matches("a+b+")   // regexp

Практически любой объект имеет метод toString:

val x = 2

x.toString
"x is " + x   // При конкатенации приведение к строке происходит автоматически

Методы #

Способ оформления переиспользуемого фрагмента кода.

def plusOne(number: Int): Int = number + 1
 |      |      |     |     |       |
 |      |      |     |     |       +-- тело метода
 |      |      |     |     +-- возвращаемый тип
 |      |      |     +-- тип параметра
 |      |      +-- имя параметра
 |      +-- имя метода
 +-- ключевое слово

// Вызов метода
plusOne(4)  // 5

Тип результата может быть опущен в случае, если он очевидно следует из тела метода. В этом случае тип будет выведен автоматически:

def plusOne(number: Int) = number + 1

Несколько параметров передаются через запятую:

def plusOne(x: Int, y: Int, z: Int): Int = x + y + z

plusOne(1, 2, 3)  // 6

У метода может быть несколько списков параметров. В этом случае при вызове метода входные параметры так же должны быть сгруппированы соответствующим образом:

def plusOne(x: Int, y: Int)(z: Int): Int = x + y + z

plusOne(1, 2)(3)  // 6

Так же у метода может не быть параметров, в этом случае, он воспринимается как “переменная”, значение которой вычисляется каждый раз при обращении (см. начало)

def sixty: Int = 10 * 6

sixty   // 60

Тело метода может быть обернуто в фигурные скобки. Возвращено будет значение последнего выражения.

def plusAndPrint(x: Int, y: Int): Int = {
  val result = x + y
  println(s"$x + $y = $result")
  result
}

plusAndPrint(2, 4)  // prints "2 + 4 = 6", returns 6

Если метод не производит какие-либо действия, не возвращая ничего, то его возвращаемым типом будет Unit.

def plusAndPrint(x: Int, y: Int): Unit = {
  val result = x + y
  println(s"$x + $y = $result")
}

plusAndPrint(2, 4)  // prints "2 + 4 = 6"

Методы могут быть вызваны в телах других методов. В этом случае вложенный метод может ссылаться на параметры внешнего:

def plusMul(q: Int, x: Int, y: int): Int = {
  def mul(u: Int) = q * u
  mul(x) + mul(y)
}

plusMul(10, 2, 3)   // 50

Последний параметр может быть объявлен повторяемым, то есть он может использоваться как коллекция:

def sumAllTimes(u: Int, nums: Int*): Int = u * nums.sum

sumAllTimes(3, 1, 2, 3)   // 3 * (1 + 2 + 3) = 18

Значение по умолчанию и именованные аргументы:

def plus3(x: Int, y: Int = 0, z: Int = 0): Int = 100 * x + 10 * y + z

plus3(1)                    // 100
plus3(1, 2)                 // 120
plus3(1, 2, 3)              // 123

plus3(x = 1)                // 100
plus3(1, z = 2)             // 102
plus3(x = 1, z = 3, y =2)   // 123

При передаче параметра по имени его значение вычисляется не всегда а только тогда, когда в этом действительно есть необходимость. Но стоит отметить, что он будет вычислен столько раз, сколько раз на него будут ссылаться, что может привести к сложно отлаживаемым багам:

def replaceNegative(x: Int, z: => Int): Int = if (x >= 0) x else z

replaceNegative(1, 3 * 3 * 3)   // 1
replaceNegative(-1, 3 * 3 * 3)  // 27

Для рекурсивных функций указание типа обязательно

def sumRange(from: Int, to: Int): Int = {
  if ( to < from ) 0
  else from + sumRange(from + 1, to)
}

sumRange(1, 10) // 55

Функции #

Функция - выражение, которое может быть использовано как метод.

val x: Int => Int = ...
val y: (Int, Int) => Int = ...  // Максимальное количество параметров - 22

Лямбда-абстракция #

Значения функций можно задавать с помощью лямбда-синтаксиса

val x: Int => Int = x => x + 1
val y: (Int, Int) => Int = (x, y) => x + y

Тип параметров можно указывать прямо в лямбда-выражении, в этом случае компилятор попробует вывести тип самостоятельно

val addOne = (x: Int) => x + 1
val plus = (x: Int, y: Int) => x + y

Короткая запись может быть использована в случае, если каждый параметр используется только один раз и вызывается и все параметры вызываются в том же порядке, в котором передаются в функцию:

val addOne: Int => Int =  _ + 1
val plus = (_: Int) + (_: Int)

Эта-конверсия #

Еще один метод объявления функции, путем конвертации метода:

def addOnde(x: Int) = x + 1
val add1 = addOne _

def plus(x: Int, y: Int) = x + y
val pl: (Int, Int) => Int = plus  // подчеркивание можно опустить, если для компилятора очевидно, что в этом месте ожидается функция