В этом уроке вы узнаете:

Обзор лекций

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

Некоторые примеры будем писать прямо в интерпретаторе, другие в исходном файле.

Имея под рукой интерпретатор можно легко исследовать проблемы.

Почему Scala?

Как работает Scala?

Что еще нужно знать о Scala

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

Запускаем интерпретатор

Наберите в консоли sbt console.

$ sbt console

[...]

Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.

scala>

Выражения

scala> 1 + 1
res0: Int = 2

res0 – автоматически создаваемое имя для переменной, которое интерпретатор дает результату вашего выражения. Переменная имеет тип Int и содержит целочисленное значение 2.

Все (ну, почти) в Scala – выражение.

Константы

Вы можете присвоить собственное имя результату выражения.

scala> val two = 1 + 1
two: Int = 2

Для переменной с ключевым словом val вы не можете изменить ранее присвоенное значение.

Переменные

Если вам нужно изменить значение константы, вы должны использовать ключевое слово var

scala> var name = "steve"
name: java.lang.String = steve

scala> name = "marius"
name: java.lang.String = marius

Функции

Вы можете создать функцию с помощью ключевого слова def.

scala> def addOne(m: Int): Int = m + 1
addOne: (m: Int)Int

В Scala, вам нужно точно указывать тип, который принимает переменная в параметрах функции. К счастью, интерпретатор возвращает используемый функцией тип обратно.

scala> val three = addOne(2)
three: Int = 3

Вы можете опускать скобки при использовании функций, если они не имеют аргументов.

scala> def three() = 1 + 2
three: ()Int

scala> three()
res2: Int = 3

scala> three
res3: Int = 3

Анонимные функции

Вы можете создавать анонимные функции.

scala> (x: Int) => x + 1
res2: (Int) => Int = <function1>

Эта функция увеличит на 1 значение, которое было передано в анонимную функцию; значение именуется как x.

scala> res2(1)
res3: Int = 2

Вы можете передавать анонимные функции как параметры или сохранять их в переменных.

scala> val addOne = (x: Int) => x + 1
addOne: (Int) => Int = <function1>

scala> addOne(1)
res4: Int = 2

Если ваша функция состоит из множества выражений, вы можете использовать фигурные скобки {}, чтобы обезопасить себя.

def timesTwo(i: Int): Int = {
  println("hello world")
  i * 2
}

Тоже самое верно и для анонимной функции

scala> { i: Int =>
  println("hello world")
  i * 2
}
res0: (Int) => Int = <function1>

Вы часто будете видеть подобный синтаксис при передачи анонимной функции в качестве параметра.

Частичный вызов функций

Вы можете использовать частичный вызов функций, обозначаемый знаком нижнего подчеркивания(_), этот знак позже будет подменен вызовом функции.

scala> def adder(m: Int, n: Int) = m + n
adder: (m: Int,n: Int)Int
scala> val add2 = adder(2, _:Int)
add2: (Int) => Int = <function1>

scala> add2(3)
res50: Int = 5

Вы можете использовать частичный вызов функций с любым аргументом из списка аргументов, а не только с последним из них, как в примере.

Каррирование функций

Иногда требуется передача каких-то аргументов в вашу функцию прямо сейчас, а других через некоторое время.

Ниже пример функции, которая позволяет умножать два числа. В одном месте вызова функции вы решите, какой из аргументов будет множителем, а позднее вызывая функцию, вы сможете установить множимое.

scala> def multiply(m: Int)(n: Int): Int = m * n
multiply: (m: Int)(n: Int)Int

Вы можете вызвать функцию напрямую с двумя аргументами.

scala> multiply(2)(3)
res0: Int = 6

Вы можете передать первый аргумент, а второй аргумент объявить как частично вызываемый.

scala> val timesTwo = multiply(2) _
timesTwo: (Int) => Int = <function1>

scala> timesTwo(3)
res1: Int = 6

Вы можете взять любую функцию с множеством аргументов и произвести ее каррирование. Давайте попробуем использовать функцию, которую рассматривали раньше, например adder

scala> val curriedAdd = (adder _).curried
curriedAdd: Int => (Int => Int) = <function1>

scala> val addTwo = curriedAdd(2)
addTwo: Int => Int = <function1>

scala> addTwo(4)
res22: Int = 6

См. подробнее о Каррировании

Использование переменного количества аргументов

Существует специальный синтаксис для методов, которые могут принимать параметры одного и того же типа.

def capitalizeAll(args: String*) = {
  args.map { arg =>
    arg.capitalize
  }
}

scala> capitalizeAll("rarity", "applejack")
res2: Seq[String] = ArrayBuffer(Rarity, Applejack)

Классы

Здесь мы объявили класс Calculator:
scala> class Calculator {
     |   val brand: String = "HP"
     |   def add(m: Int, n: Int): Int = m + n
     | }

scala> val calc = new Calculator
calc: Calculator = Calculator@e75a11

scala> calc.add(1, 2)
res1: Int = 3

scala> calc.brand
res2: String = "HP"

В примере объявляется метод и поле с ключевым словом val. Методы – это функции, которые имеют доступ к внутренним сущностям класса.

Конструктор

Конструкторы не являются специальными методами, их код находится в классе за пределами определения метода. Давайте расширим наш пример с калькулятором. Будем принимать аргумент конструктора и использовать его для инициализации внутреннего состояния.

class Calculator(brand: String) {
  /**
   * Конструктор.
   */
  val color: String = if (brand == "TI") {
    "blue"
  } else if (brand == "HP") {
    "black"
  } else {
    "white"
  }

  // Метод экземпляра класса.
  def add(m: Int, n: Int): Int = m + n
}

Обратите внимание на два различных способа написания комментариев.

Выражения

Наш пример с калькулятором дает хороший пример того, как Scala ориентирован на выражения (expression-oriented). Значение переменной color было присвоено благодаря if/else выражению. Scala сильно ориентирован на выражения: большинство вещей делается с помощью выражений, а не утверждений.

Функции и методы

Функции и методы в основном взаимозаменяемы. Потому что функции и методы на столько похожи, что вам нет нужды знать что именно вы вызываете - функцию или метод. Когда вы столкнетесь с различиями в функциях и методах вы будете удивлены.

	scala> class C {
     |   var acc = 0
     |   def minc = { acc += 1 }
     |   val finc = { () => acc += 1 }
     | }
defined class C

scala> val c = new C
c: C = C@1af1bd6

scala> c.minc // calls c.minc()

scala> c.finc // returns the function as a value:
res2: () => Unit = 

В то время, как вы вызываете одну "функцию" без скобок, а другую - со скобками, вы можете подумать: Упс, я думал, что знаю как работают функции в Scala, но, как оказалось, нет. Возможно, иногда им требуются скобки? Вы можете понимать функции, но использовать методы.

На практике, вы можете делать великие вещи на Scala не вдаваясь в подробности различия функций и методов. Если вы новичок в Scala и прочитали объяснение различий, то, возможно у вас будут проблемы с ними. Это не значит, что у вас возникнут проблемы с использованием Scala. Это только означает, что различия между функциями и методами достаточно тонкие, что их разъяснение могут погрузить вас в глубинные части языка.

Наследование

class ScientificCalculator(brand: String) extends Calculator(brand) {
  def log(m: Double, base: Double) = math.log(m) / math.log(base)
}

Смотрите также: В Effective Scala указывается на то, что лучше использовать Псевдонимы типов вместо extends, особенно если подкласс практически ничем не отличается от суперкласса. В “Туре по языку Scala” вы найдете более подробное описание Подклассов.

Перегрузка методов

class EvenMoreScientificCalculator(brand: String) extends ScientificCalculator(brand) {
  def log(m: Int): Double = log(m, math.exp(1))
}

Абстрактные классы

Вы можете определять абстрактные классы. Абстракные классы определяют содержат методы, но не реализуют их. Однако, подклассы, наследующиеся от абстрактного класса, содержат реализацию этих методов. Создать экземпляр абстрактного класса запрещено.

	scala> abstract class Shape {
     	|   def getArea():Int    // subclass should define this
     	| }
	defined class Shape
	
	scala> class Circle(r: Int) extends Shape {
	     |   def getArea():Int = { r * r * 3 }
	     | }
	defined class Circle
	
	scala> val s = new Shape
	<console>:8: error: class Shape is abstract; cannot be instantiated
	       val s = new Shape
	               ^
	
	scala> val c = new Circle(2)
	c: Circle = Circle@65c0035b

Трейты

Трейты – это коллекция полей и методов, которые вы можете расширить или примешать к вашему классу.

trait Car {
  val brand: String
}
class BMW extends Car {
  val brand = "BMW"
}

Смотрите также: В Effective Scala есть описание Трейтов.

В каких случаях требуется использовать трейты вместо абстрактных классов? Если вы хотите создать тип похожий на интерфейс, то возможно вам покажется непростым занятием решить что использовать: трейт или абстрактный класс. Несколько правил для принятия решения:

Вы не первый, кто задает такой вопрос. Более подробные ответы вы можете найти на stackoverflow:Scala traits vs abstract classes, Difference between Abstract Class and Trait, и Programming in Scala: To trait, or not to trait?

Типы

Ранее вы могли видеть, что мы определили функцию, принимающая тип Int, который является одним из видов Number. Функция может быть объявлена как обобщенная (generic) и после этого может работать с любым типом. Когда объявлена такая функция, вы увидите параметр-тип размещенный внутри квадратных скобок. Трейт, описанный ниже, описывает кеш с параметрами-типами для ключа и значения этого кеша [K, V]:

	  trait Cache[K, V] {
	    def get(key: K): V
	    def put(key: K, value: V)
	    def delete(key: K)
	  }
	
Методы так же могут иметь параметры-типы.
		def remove[K](key: K)