入门笔记
在线调试
https://scastie.scala-lang.org/j1vyIFfrTPeLKLrM97BtIw
// создаём объект, потому что это Scala
object ForLoopDemo {
// определяем функцию main
def main(args: Array[String]) {
// делаем цикл от нуля до 5 включительно, п
еременная цикла — counter
for (counter <- 0 to 5){
// выводим переменную цикла на экран
println(counter);
}
}
}
----------------------------------------------
Scala – строгий статически типизированный JVM-based язык, успешно совмещающий парадигмы объектно-ориентированного и функционального программирования. В языке есть классы, функции высшего порядка, анонимные функции, обобщенное программирование. Использование Java-кода из Scala не вызывает трудностей, синтаксически языки очень близки. В этой статье мы разберем основные элементы языка, достаточные для того, чтобы начать на нем писать.
Настройка окружения
Scala — язык, работающий на JVM, поэтому для работы требует установленную JDK (минимальная версия 1.6). Ее можно взять отсюда. После установки JDK можно приступить к установке самой Scala. Скачать свежую версию можно на официальном сайте. Последняя версия на момент написания статьи — 2.11.6.
Для того, чтобы все корректно работало из командной строки, рекомендуется прописать переменные среды JAVA_HOME
и SCALA_HOME
, а также дополнить переменную PATH
путями к выполняемым файлам. На Linux и MacOS это делается так:
export JAVA_HOME=<путь к каталогу c установленной Java, в котором есть папка 'bin'>
export SCALA_HOME=<путь к каталогу c установленной Scala, в котором есть папка 'bin'>
export PATH=$PATH:$JAVA_HOME/bin:$SCALA_HOME/bin
Для того, чтобы сохранить эти настройки, их надо прописать в ~/.bashrc
или ~/.bash_profile
.
На Windows команда немного другая:
set JAVA_HOME=<путь к каталогу c установленной Java, в котором есть папка 'bin'>
set SCALA_HOME=<путь к каталогу c установленной Scala, в котором есть папка 'bin'>
set PATH=%PATH%;%JAVA_HOME%\bin;%SCALA_HOME%\bin
Прописать эти опции постоянно можно в настройках системы: Control Panel → Advanced System Settings → Environmental Variables.
28 января в 18:30, Онлайн, Беcплатно
После выполнения всех манипуляций можно проверить результат, запустив:
> java -version
java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
> scala -version
Scala code runner version 2.11.6 -- Copyright 2002-2013, LAMP/EPFL
SBT
Простые скрипты и маленькие программы можно, конечно, компилировать и запускать вручную с помощью команд scalac
и scala
. Однако, по мере того, как количество файлов будет расти, ручная компиляция будет становиться все более нудной. Вместо этого используют системы сборки. Для сборки кода на Scala можно использовать стандартные для Java (неофициально) maven, gradle или ant, но сообщество и сами разработчики рекомендуют sbt (simple build tool).
Примечание: если вы устанавливаете sbt, то можете пропустить отдельную установку scala, так как система сборки скачает ее автоматически
Описание процесса сборки находится либо в файле build.sbt
в корне проекта, либо в файлах .scala
в папке project
там же. Само описание – это программа на Scala (которая, в свою очередь, может собираться с помощью sbt как отдельный проект, который… ну, вы поняли).
Синтаксис .sbt
-файла напоминает синтаксис Scala с некоторыми дополнениями и ограничениями. Минимальный build.sbt
выглядит примерно так (пустые строки обязательны):
name := "My Project"
version := "0.1.0"
scalaVersion := "2.11.6"
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "1.6.1" % "test"
)
Исходники помещаются в папку src/main/scala
и src/test/scala
по пути, соответствующем иерархии пакетов (как в Java). Чтобы собрать, протестировать и запустить проект, необходимо в любой поддиректории проекта выполнить следующие команды:
> sbt compile
> sbt test
> sbt run
или через интерактивную консоль:
> sbt
sbt> compile
sbt> test
sbt> run
Последовательное выполнение команд выглядит немного необычно (обратите внимание на точку с запятой в начале — это особенность синтаксиса):
> sbt
sbt> ; compile; test; run
REPL
Отличным помощником в разработке будет REPL (Read-Eval-Print-Loop), или по-другому, интерактивная консоль. Очень удобно проверять в ней небольшие функции, отлаживать код или просто посмотреть возможности языка. Для запуска REPL наберите sbt console
в командной строке. Вы увидите примерно следующее:
> sbt console
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256m; support was removed in 8.0
[info] Set current project to test (in build file:/C:/Users/foxmk/Desktop/test)
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.10.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_25).
Type in expressions to have them evaluated.
Type :help for more information.
scala>
Все! Можно писать команды на Scala и сразу же их выполнять:
scala> 1 + 1
res1: Int = 2
scala> def f(x: Int) = x * 2
f: (x: Int)Int
scala> f(4)
res2: Int = 8
scala> println("Hello!")
Hello!
scala>
Для выхода из REPL можно нажать Ctrl+D. Все примеры на Scala далее можно протестировать в REPL, для вставки больших кусков кода можно воспользоваться командой :paste
.
IDE
Использование IDE для разработки на Scala не обязательно, однако сильно упрощает процесс. Скала — язык со сложной семантикой, поэтому возможности IDE более ограничены, чем, скажем, при разработке на Java. Тем не менее даже простая подсветка несуществующих методов и автодополнение существующих может сильно облегчить жизнь. Самые популярные IDE для Scala — это IntelliJ IDEA и Eclipse. Для IDEA есть плагин от JetBrains, в случае с Eclipse есть ее вариант Scala IDE.
Переменные, значения и типы.
В Scala переменные и значения объявляются ключевым словом val
или var
. val
— это неизменяемая переменная (значение), аналог final
в Java. var
— обычная переменная. Например:
> val x = "Some immutable string"
> var y = "I CAN CHANGE THIS!"
> y = "WHOA! I HAVE CHANGED THIS!"
> x = "OOPS!"
Error:(3, 4) reassignment to val
x = "OOPS!"};}
^
Аналогичный код на Java будет выглядеть так:
public final String x = "Some immutable string";
public String y = "I CAN CHANGE THIS!";
y = "WHOA! I HAVE CHANGED THIS!";
x = "OOPS!"; // Ошибка компиляции
Здесь мы видим сразу несколько приятных особенностей Scala:
- точка с запятой не обязательна (работает автоматический вывод);
- указания типа переменной необязательно (также работает автоматический вывод типов);
- ключевое слово
public
подразумевается по умолчанию.
Типы переменных указываются после имени, через двоеточие. Также в Scala нет, как таковых, примитивных типов (int
, float
, boolean
и т.д.). Их заменяют соответствующие классы Int
, Float
, Boolean
и т.д. Любая переменная — экземпляр какого-либо класса. Иерархия классов начинается с Any
, все классы наследуются от него (аналог Object
в Java).
val i: Int = 0 // Scala
public int i = 0; // Java
val flag: Boolean = true // Scala
public boolean flag = true; // Java
Применение привычных операторов, при этом, на самом деле — вызов метода: a + b
тождественно a.+(b)
. Вариант записи без точки применим к любым методам (с некоторыми ограничениями).
Функции, анонимные функции, методы
Функция в Scala объявляется с помощью ключевого слова def
. Пример объявления и применения функции:
def addOne(a: Int) = a + 1
addOne(1)
// res0: Int = 2
Аналогичный код на Java:
public int addOne(int a) {
return a + 1;
}
Как видно на примере, необязательны не только точка с запятой и указание типа, но и фигурные скобки вокруг единственного выражения и слово return
. Более того, его использование считается плохой практикой. Из функции возвращается значение последней выполненной команды.
На самом деле, функция — это тоже объект. Каждая функция в Scala — это экземпляр класса Function
, у которого есть метод apply
. Поэтому мы вполне можем записать так (знак подчеркивания ставится на место аргумента функции):
val functionValue = addOne _
functionValue.apply(1)
// res1: Int = 2
Вызов метода apply
подразумевается по умолчанию, поэтому использование функций внешне выглядит как в Java:
functionValue(1)
// res2: Int = 2
Все четыре вызова функции идентичны. Представление функций в виде объектов позволяет оперировать с ними, как с остальными объектами: передавать в качестве аргументов, возвращать из других функций, расширять дополнительными методами и т.д., что позволяет Scala полноценно поддерживать парадигму функционального программирования.
Конечно, присутствуют анонимные функции (лямбда-функции). Они объявляются так:
val f = (x: Int) => x + 1
f(1)
// res3: Int = 2
Здесь мы объявляем анонимную функцию, которая принимает один целочисленный аргумент и присвоил ее переменной f
, после чего применяем f
как обычную функцию.
Классы и объекты
Если вы программировали на Java, многие вещи, касающиеся объектно-ориентированного программирования будут вам знакомы. Класс объявляется ключевым словом class
, новый экземпляр — через new
. Методы класса — это функции, объявленные в его теле. Поля класса указываются сразу после имени, как список аргументов. При этом, по умолчанию они объявляются как private val
. То есть, если мы не укажем никаких модификаторов, указанное поле будет доступно только внутри класса и будет неизменяемым. Класс можно сделать абстрактным, добавив abstract
перед объявлением. Основное отличие от Java здесь заключается в отсутствии конструктора. Код, который должен выполняться при создании объекта, пишется прямо в теле класса. Как при этом реализовать несколько конструкторов с различным числом аргументов, мы рассмотрим позже. Пример использования класса:
class Foo(x: Int) {
def bar(y: Int) = x + y
}
val foo = new Foo(1)
// foo: Foo = Foo@69007238
foo.bar(1)
// res4: Int = 2
И аналогичный код на Java:
class Foo {
private int x_;
Foo(int x) {
_x = x;
}
public int bar(int y) {
return x + y;
}
}
Foo foo = new Foo(1)
int result = foo.bar(1)
// result теперь равен 2
Как видим, public
указывать не обязательно, аргументы конструктора доступны во всем классе, локальное приватное поле создавать также не обязательно.
Кроме того, в Scala мы можем объявить сразу объект, без создания класса, с помощью ключевого слова object
. Таким образом реализуется паттерн «Одиночка» (Singleton).
object Singleton(field: String) {
def squareMe(x: Int) = x*x
}
Singleton.squareMe(2)
// res5: Int = 4
Аналог на Java будет куда более многословен.
public class Singleton {
private static Singleton instance = null;
protected Singleton() {
// Exists only to defeat instantiation.
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
public int squareMe(int x) {
return x*x;
}
}
int a = Singleton.getInstance().squareMe(2)
// a теперь равен 4
В этом примере мы пометили конструктор как protected
, чтобы исключить возможность его вызова извне, обращение к объекту будет осуществляться через метод getInstance()
, который при первом своем вызове инициализирует экземпляр класс, а при последующих возвращает уже созданный экземпляр. Кроме того, вполне допустимо существование объекта и класса с одним и тем же именем, при этом они делят область видимости. Поэтому необходимость в директиве static
отпадает — методы, объявленные не в классе, а в объекте ведут себя как статические. Такой объект называется в терминологии Scala «companion object» («объект-компаньон»).
Вернемся к конструкторам. Вспомним, что при применении любого объекта к некоторым аргументам по умолчанию вызывается метод apply
. Этим мы и воспользуемся и напишем класс с несколькими конструкторами, статическими методами, изменяемыми и неизменяемыми полями в идиоматичном для Scala стиле и продублируем этот же код на Java.
Вариант Scala:
object MyUselessClass {
def staticMethod(x: Int) = x + 5
def apply(immutableField: Int): MyUselessClass = new MyUselessClass(immutableField, 2)
def apply(immutableField: Int, mutableField: Int): MyUselessClass = new MyUselessClass(immutableField, mutableField)
def apply(immutableField: Int, mutableField: Int, privateField: Int): MyUselessClass = new MyUselessClass(immutableField, mutableField, privateField)
}
class MyUselessClass(val immutableField: Int, var mutableField: Int, privateField: Int = 8 /*значние по умолчанию*/) {
def instanceMethod() = {
val sumOfFields = immutableField + mutableField + privateField
MyUselessClass.staticMethod(sumOfFields)
}
}
// Первый конструктор, обратите внимание на отсутствие 'new',
// так как это на самом деле вызов метода 'apply'
val myUselessObject = MyUselessClass(1)
// аналогично предыдущему варианту
val myAnotherUselessObject = MyUselessClass.apply(1)
// Третий конструктор
val myThirdUselessObject = MyUselessClass(1, 2, 3)
// Вызов метода
myUselessObject.instanceMethod()
// res6: Int = 16
// Поля доступны также, как методы
myUselessObject.mutableField
// res7: Int = 2
myUselessObject.immutableField
// res8: Int = 1
myUselessObject.mutableField = 9
myUselessObject.mutableField
// res9: Int = 9
// Вызов статического метода
MyUselessClass.staticMethod(3)
// res10: Int = 8
Вариант Java:
public class MyUselessClass {
private int immutableField_;
private int mutableField_;
private int privateField_ = 8;
MyUselessClass(int immutableField) {
immutableField_ = immutableField;
mutableField_ = 2;
}
MyUselessClass(int immutableField, int mutableField) {
immutableField_ = immutableField;
mutableField_ = mutableField;
}
MyUselessClass(int immutableField, int immutableField, int privateField) {
immutableField_ = immutableField;
mutableField_ = mutableField;
privateField_ = privateField;
}
int getImmutableField() {
return immutableField;
}
int getMutableField() {
return mutableField;
}
void setMutableField(int newValue) {
mutableField = newValue;
}
public static int staticMethod(int x) {
return x + 5;
}
public int instanceMethod() {
int sumOfFields = immutableField + mutableField + privateField;
return staticMethod(sumOfFields);
}
}
// Первый конструктор
MyUselessClass myUselessObject = new MyUselessClass(1)
// Третий конструктор
MyUselessClass myAnotherUselessObject = new MyUselessClass(1, 2, 3)
// Вызов метода
myUselessObject.instanceMethod()
// вернет 16
// Поля доступны также, как методы
myUselessObject.getMutableField
// вернет 2
myUselessObject.getImmutableField
// вернет 1
myUselessObject.setMutableField(9)
myUselessObject.getMmutableField
// вернет 9
// Вызов статического метода
MyUselessClass.staticMethod(3)
// вернет 8
Интерефейсы и трейты
Аналогом Java-интерфейса в Scala является трейт (trait). Как ни удивительно, объявляется он с помощью ключевого слова trait
. Как и интерфейсы Java, трейты содержат только объявления методов и допускают множественное наследование. В отличие от интерфейса, в трейте можно описывать поля класса и частично реализовывать методы. Наследование как трейтов, так и абстрактных классов осуществляется с помощью extend
(первый родитель) и with
(последующие родители). Пример использования:
trait FirstTrait {
def foo(x: Int)
}
trait SecondTrait {
def bar(y: Int) = y + 5
}
class ComplexClass extends FirstTrait with SecondTrait {
override def foo(x: Int) = x * 2
}
Ключевое слово override
необязательно, но его использование является хорошей практикой.
Другие особенности и отличия от Java
Как и в Java, в Scala классы, трейты и функции можно параметризовать. Параметры типов пишутся в квадратных скобках после имени класса или функции. Так определяется интерфейс Foo
, который принимает некоторый тип A
и содержит метод bar
? который принимает значение типа A
и некоторого типа B
и возвращает объект типа C
. Конкретные типы A
, B
и C
будут определены в реализации интерфейса.
trait Foo[A] {
def bar[B, C](a: A, b: B): C // метод, который принимает аргументы типа A и B и возвращает тип C
}
Конструкция if
/else
всегда возвращает значение выражения, которое стоит последним ввыполняемом блоке. Скобки вокруг условия обязательны, скобки вокруг тела, в котором только одна инструкция, можно опустить.
val a = if (true == false) {
// true-branch
} else {
// false-branch
}
Блок try
/catch
/finally
выглядит в Scala так:
try {
// some code
} catch {
case e: SomeException => ???
case e: SomeOtherException => ???
} finally {
// some code
}
Циклы while
ничем не отличаются от варианта в Java:
var i = 1000
while (i > 0) {
i -= 1
}
А циклы for
— наоборот, совсем не похожи (о них мы подробнее поговорим в следующей статье):
val numbers = 1 to 1000
for (number <- numbers) {
print(number)
}
Также вы, возможно, могли заметить литерал ???
. Он имеет тип Nothing который является подкласс любого класса. При вызове ???
кидает исключение NotImplemented
. Это примерно аналог undefined
в Python. ???
можно ставить в качестве заглушки вместо тела функции, к написанию которого вы планируете вернуться позже.
Заключение
Итак, мы установили и настроили среду разработки для Scala, посмотрели основные элементы языка, сходства с Java и отличия от нее. Этого вполне должно хватить для того, чтобы начать писать простой и рабочий код. В следующей статье мы подробнее рассмотрим элементы функционального программирования, case-классы, pattern-matching и другие высокоуровневые особенности языка.