Страничка курса: https://maxcom.github.io/scala-course-2018/
Слайды, примеры программ и другие файлы доступны в репозитарии https://github.com/maxcom/scala-course-2018. Содержимое будет пополняться по ходу курса.
Для обсуждений и вопросов по курсу используем telegram чат @scalacourse2018 (доступ по ссылке).
Курс читает Максим Валянский
окончил ВМиК в 2001 году
занимаюсь разработкой ПО сколько себя помню
работаю архитектором в компании Solar Security
Мы разрабочики из компании Solar Security.
Компания занимается:
Мы занимаемся "Дозором" – инструментом контроля коммуникаций сотрудников, выявления ранних признаков корпоративного мошенничества и проведения расследований.
https://solarsecurity.ru/products/solar_dozor/
Продукту более 18 лет.
Первые версии написаны на Scheme – функциональном динамическом языке, диалекте Lisp.
В ходе эволюции у нас появилось много компонентов на Java.
Java это отличный runtime, opensource community, библиотеки для интеграции со сторонним ПО.
Но сейчас мы отказались и от Scheme,
и от языка Java в "Дозоре".
(хотя у нас еще много Java кода)
Scheme (mzscheme, racket) сейчас это:
Но мы все еще любим функциональное программирование*
* хотя каждый разработчик по своему понимает что такое ФП
Мы верим что функциональной программирование сделает наш софт качественнее, а разработку – более быстрой и предсказуемой.
Что не так в Java?
Мы выбрали JVM как платформу и остановились на двух языках – Scala и Clojure.
Первый Scala компонент у нас появился осенью 2013 года в качестве эксперимента.
Это была распределенная система хранения бинарных данных - "файловое хранилище".
По результатам вывода в production эксперимент посчитали удачным.
Сейчас довольно большая часть продукта написана на Scala, и у нас много планов по развитию.
Scala — мультипарадигмальный язык программирования, спроектированный кратким и типобезопасным для простого и быстрого создания компонентного программного обеспечения, сочетающий возможности функционального и объектно-ориентированного программирования.
Работающий на JVM и хорошо интегрирующийся с существующим Java кодом
обо всём языке говорить долго
хотя есть что рассказать
рассмотрим базовые возможности языка
и перейдем к практике
После каждого занятия будут предложены задачки по теме и одно большое практическое задание на весь курс.
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello, world!")
}
}
build.sbt -- настройки сборки
project -- еще настройки сборки
project/target -- кеш компилятора и вспомогательные файлы
src -- исходные файлы и ресурсы
src/main -- основной код
src/main/scala -- основной код на Scala
src/test -- исходные файлы и ресурсы тестов
src/test/scala -- код тестов на Scala
target -- результат компиляции
можно присвоить имя результату выражения:
val something = 10 // константа
var something: Int = 10 // переменная
тип выводится автоматически
В Scala почти всё является выражением:
val ageGroup = if (age >= 18) "grownup" else "minor"
val result: Int = {
val one = 1
val two = 2
one + two // "возвращается" последнее значение в блоке
}
def square(x: Int) = x * x
функция - это значение:
val f: (Int ⇒ Int) = square
(её тип - Function1[Int, Int])
val pair: (Int, Int) = (1, 2)
// Tuple2[Int, Int]
деконструкция
val (first, second) = pair
бывают еще "тройки" и более - до 22
Seq[T] – общий тип для коллекций, имеющих определенный порядок (списки, массивы, вектора и т.п.)
ArrayBuffer[T] – аналог ArrayList из Java
Vector[T] – неизменяемый аналог ArrayList
val buffer = ArrayBuffer[Int](1, 2, 3)
buffer += 4 // добавление элемента
buffer(1) // получение элемента
// buffer еще и функция
val f: (Int ⇒ Int) = buffer
У стандартных коллекций много полезных функций
Справка на Vector: scaladoc.
val v = Vector(1, 2, 3, 4)
val (first, second) = v.splitAt(v.length / 2)
// first == Vector(1,2)
// second == Vector(3,4)
Механизм для создания собственных типов для данных.
case class Address(`type`: String, value: String) {
def toStringAddress = s"${`type`}:$value"
}
Это не ООП! Данные не изменяемые, обычно не содержат бизнес-логики.
Собственный тип лучше, чем просто значения:
Address(`type`, value)
против пары
(String, String)
Собственный тип можно заводить и для простых значений, например:
case class UserId(uuid: UUID)
case class GroupId(uuid: UUID)
типы позволяют не путать значения между собой
Что есть в case class?
Можно разобрать класс обратно:
val address = Address("email", "abuse@sportloto.ru")
val Address(_, email) = address
object Address {
def apply(`type`: String, value: String) =
new Address(`type`, value)
def unapply(address: Address): Option[(String, String)] =
Some((address.`type`, address.value))
}
* объект-компаньон, заменяет static декларации Java
* для case классов apply/unapply создаются автоматически
Case классы можно объединить в иерархию
sealed trait Expr
case class Number(value: Int) extends Expr
case class Plus(lhs: Expr, rhs: Expr) extends Expr
case class Minus(lhs: Expr, rhs: Expr) extends Expr
* дальше будет более правильная версия этого примера
def value(expression: Expr): Int = expression match {
case Number(value) ⇒ value
case Plus(lhs, rhs) ⇒ value(lhs) + value(rhs)
case Minus(lhs, rhs) ⇒ value(lhs) - value(rhs)
}
val result = value(Plus(Number(2), Number(2)))
Pattern matching - альтернатива полиморфизму на методах:
trait Expr {
def eval: Int
}
case class Number(value: Int) extends Expr {
override val eval = value
}
case class Plus(lhs: Expr, rhs: Expr) extends Expr {
override def eval = lhs.eval + rhs.eval
}
case class Minus(lhs: Expr, rhs: Expr) extends Expr {
override def eval = lhs.eval - rhs.eval
}
Plus(Number(2), Number(2)).eval
Две модели:
sealed trait Expr extends Product with Serializable
final case class Number(value: Int) extends Expr
final case class Plus(lhs: Expr, rhs: Expr) extends Expr
final case class Minus(lhs: Expr, rhs: Expr) extends Expr
val number: Expr = Number(3)
val expr = Plus(Number(2), Number(2))
val buffer = ArrayBuffer(Number(1), expr)
// не компилируется
buffer += number
потому что тип buffer вот такой:
ArrayBuffer[Product with Serializable with Expr]
запрещаем наследование
помогаем компилятору
более безопасная замена null
val v = Vector(1, 2, 3, 4, 5)
val r: Option[Int] = v.find(x ⇒ x > 2)
// r = Some(3)
плохой вариант работы с Option:
if (r.isDefined) {
println(r.get) // бросает исключение если значения нет
}
// 1
r match {
case Some(k) ⇒ println(k)
case None ⇒ println("None")
}
//
// 2
println(r.getOrElse("None"))
еще варианты рассмотрим на 3-м занятии
try {
1 / 0
} catch {
case ex: ArithmeticException ⇒
println(ex.getMessage)
}
try - тоже выражение, возвращает последнее значение
Тип Try
получим его так:
val result: Try[Int] = Try {
1 / 0
}
import scala.util.{Random, Try}
// fill - функция с двумя блоками параметров
// fill[A](n: Int)(elem: => A)
val vector = Vector.fill(10) {
Try {
1 / Random.nextInt(5)
}
}
vector.count(x ⇒ x.isSuccess)
Могут ли функции возвращать Try?
Да, но это "антипаттерн".
Описание алгоритма: на wikipedia
Неплохая визуализация
(надо выбрать "merge sort")
Deadline - утро среды следующей недели.
Простой вариант - m.valyanskiy@solarsecurity.ru.
Присылайте только исходники; код должен работать!
"Сложный" вариант - используем gitlab.com:
дополнительная часть, если успеем
Напишем несколько тестов для функции сортировки.
Используем фреймворк Specs2
Зачем нужны тесты?
Убедиться, что функция работает.
"Подстраховать" будущие изменения.
Тесты кладут в специальную папку "test".
Они не попадают в production код.
Unit-тесты запускаются в сборочном окружении.
Библиотеки могут создавать синтаксис, не похожий на обычный код.
Например можно вызывать метод без "."
val v = Vector.fill(10)(Random.nextInt(20) - 10)
def positive(i: Int) = i > 0
val (pos, neg) = v.partition(positive)
val (pos2, neg2) = v partition positive
И еще есть другая "магия".
Тесты на Specs2 выглядят вот так:
import org.specs2.mutable.Specification
class SorterTest extends Specification {
"sort function" should {
"preserve vector length" in {
val vector = Vector(5, 10, 343, 43, 1)
Sorter.sort(vector) must have size vector.length
}
}
}
Как запустить тест?
В консоли "sbt test"
В IDEA: в выпадающем меню у класса с тестом - "Run".
В библиотеке много готовых условий для коллекций:
Seq(1, 2, 3) must beSorted
Seq(1, 2, 3) must contain(2)
Seq(2, 4, 1) must containTheSameElementsAs(Seq(1, 4, 2))
(читайте документацию)
Вместе с заданием пишем тесты.
Заготовка - тут: unit-test-demo.