В этом уроке вы узнаете:
В первые несколько недель вы узнаете об основах синтаксиса и основных идеях, заложенных в язык 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)
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 есть описание Трейтов.
В каких случаях требуется использовать трейты вместо абстрактных классов? Если вы хотите создать тип похожий на интерфейс, то возможно вам покажется непростым занятием решить что использовать: трейт или абстрактный класс. Несколько правил для принятия решения:
trait t(i: Int) {}
; параметр i
в этом коде недопустим.Вы не первый, кто задает такой вопрос. Более подробные ответы вы можете найти на 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)