diff --git a/_data/translations.yml b/_data/translations.yml index 3641a04203..266b039905 100644 --- a/_data/translations.yml +++ b/_data/translations.yml @@ -1,2 +1,2 @@ tour: - languages: [ba, es, ko, pt-br, pl, zh-cn, th] + languages: [ba, es, ko, pt-br, pl, zh-cn, th, ru] diff --git a/_ru/tour/abstract-type-members.md b/_ru/tour/abstract-type-members.md new file mode 100644 index 0000000000..cb2a2ecc1e --- /dev/null +++ b/_ru/tour/abstract-type-members.md @@ -0,0 +1,77 @@ +--- +layout: tour +title: Члены Абстрактного Типа + +discourse: true + +partof: scala-tour +num: 23 +language: ru +next-page: compound-types +previous-page: inner-classes +topics: abstract type members +prerequisite-knowledge: variance, upper-type-bound + +--- + +Абстрактные типы, такие как трейты и абстрактные классы, могут содержать членов абстрактного типа. +Абстрактный означает, что только конкретный экземпляр определяет, каким именно будет тип. +Вот пример: + +```tut +trait Buffer { + type T + val element: T +} +``` +Здесь мы определили абстрактный тип `T`, который используется для описания типа члена `element`. Мы можем расширить его в абстрактном классе, добавив верхнюю границу нового типа `U` связанного с `T`, делая описание типа более конкретным. + +```tut +abstract class SeqBuffer extends Buffer { + type U + type T <: Seq[U] + def length = element.length +} +``` +Обратите внимание, как мы можем использовать новый абстрактный тип `U` в качестве верхней границы типа. Класс `SeqBuffer` позволяет хранить в буфере только последовательности, указывая, что тип `T` должен быть подтипом `Seq[U]` для нового абстрактного типа `U`. + +[Трейты](traits.html) или [классы](classes.html) с членами абстрактного типа часто используются в сочетании с анонимными экземплярами классов. Чтобы проиллюстрировать это рассмотрим программу, которая имеет дело с буфером, который ссылается на список целых чисел: + +```tut +abstract class IntSeqBuffer extends SeqBuffer { + type U = Int +} + + +def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer = + new IntSeqBuffer { + type T = List[U] + val element = List(elem1, elem2) + } +val buf = newIntSeqBuf(7, 8) +println("length = " + buf.length) +println("content = " + buf.element) +``` +Здесь класс `newIntSeqBuf` создает экземпляры `IntSeqBuffer`, используя анонимную реализацию класса `IntSeqBuffer` (т.е. `new IntSeqBuffer`), устанавливая тип `T` как `List[Int]`. + +Мы можем вывести тип класса из типа его членов и наоборот. Приведем версию кода, в которой выводится тип класса из типа его члена: + +```tut +abstract class Buffer[+T] { + val element: T +} +abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] { + def length = element.length +} + +def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] = + new SeqBuffer[Int, List[Int]] { + val element = List(e1, e2) + } + +val buf = newIntSeqBuf(7, 8) +println("length = " + buf.length) +println("content = " + buf.element) +``` + +Обратите внимание, что здесь необходимо использовать [вариантность в описании типа](variances.html) (`+T <: Seq[U]`) для того, чтобы скрыть конкретный тип реализации списка, возвращаемого из метода `newIntSeqBuf`. \ No newline at end of file diff --git a/_ru/tour/annotations.md b/_ru/tour/annotations.md new file mode 100644 index 0000000000..2016cd1603 --- /dev/null +++ b/_ru/tour/annotations.md @@ -0,0 +1,129 @@ +--- +layout: tour +title: Аннотации + +discourse: true + +partof: scala-tour + +num: 32 +language: ru +next-page: default-parameter-values +previous-page: by-name-parameters + +--- + +Аннотации используются для передачи метаданных при объявлении. Например, аннотация `@deprecated` перед объявлением метода, заставит компилятор вывести предупреждение, если этот метод будет использован. +``` +object DeprecationDemo extends App { + @deprecated("deprecation message", "release # which deprecates method") + def hello = "hola" + + hello +} +``` +Такой код скомпилируется, но компилятор выдаст предупреждение: "there was one deprecation warning". + +Аннотация применяется к первому идущему после нее объявлению или определению. Допускается использование сразу нескольких аннотаций следующих друг за другом. Порядок, в котором приводятся аннотации, не имеет значения. + +## Аннотации, обеспечивающие корректность работы кода +Некоторые аннотации приводят к невозможности компиляции, если условие (условия) не выполняется. Например, аннотация `@tailrec` гарантирует, что метод является [хвостовой рекурсией](https://ru.wikipedia.org/wiki/%D0%A5%D0%B2%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D0%B0%D1%8F_%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F). Хвостовая рекурсия помогает держать потребление памяти на постоянном уровне. Вот как она используется в методе, который вычисляет факториал: + +```tut +import scala.annotation.tailrec + +def factorial(x: Int): Int = { + + @tailrec + def factorialHelper(x: Int, accumulator: Int): Int = { + if (x == 1) accumulator else factorialHelper(x - 1, accumulator * x) + } + factorialHelper(x, 1) +} +``` +Метод `factorialHelper` имеет аннотацию `@tailrec`, которая гарантирует, что метод действительно является хвостовой рекурсией. Если бы мы изменили реализацию `factorialHelper` так как указано далее, то компиляция бы провалилась: +``` +import scala.annotation.tailrec + +def factorial(x: Int): Int = { + @tailrec + def factorialHelper(x: Int): Int = { + if (x == 1) 1 else x * factorialHelper(x - 1) + } + factorialHelper(x) +} +``` +Мы бы получили сообщение "Recursive call not in tail position"(Рекурсивный вызов не в хвостовой позиции). + + +## Аннотации, влияющие на генерацию кода +Некоторые аннотации типа `@inline` влияют на сгенерированный код (т.е. в результате сам код вашего jar-файл может отличаться). Такая аннотация означает вставку всего кода в тело метода вместо вызова. Полученный байт-код длиннее, но, надеюсь, работает быстрее. Использование аннотации `@inline` не гарантирует, что метод будет встроен, но заставит компилятор сделать это, если и только если будут соблюдены некоторые разумные требования к размеру сгенерированного кода. + +### Java аннотации ### +Есть некоторые отличий синтаксиса аннотаций, если пишется Scala код, который взаимодействует с Java. + +**Примечание:**Убедитесь, что вы используете опцию `-target:jvm-1.8` с аннотациями Java. + +Java имеет определяемые пользователем метаданные в виде [аннотаций](https://docs.oracle.com/javase/tutorial/java/annotations/). Ключевой особенностью аннотаций является то, что они задаются в виде пар ключ-значение для инициализации своих элементов. Например, если нам нужна аннотация для отслеживания источника какого-то класса, мы можем определить её как + +``` +@interface Source { + public String URL(); + public String mail(); +} +``` + +А затем использовать следующим образом + +``` +@Source(URL = "http://coders.com/", + mail = "support@coders.com") +public class MyClass extends HisClass ... +``` + +Использование аннотации в Scala похоже на вызов конструктора. Для создания экземпляра из Java аннотации необходимо использовать именованные аргументы: + +``` +@Source(URL = "http://coders.com/", + mail = "support@coders.com") +class MyScalaClass ... +``` + +Этот синтаксис достаточно перегруженный, если аннотация содержит только один элемент (без значения по умолчанию), поэтому, если имя указано как `value`, оно может быть применено в Java с помощью конструктора-подобного синтаксиса: + +``` +@interface SourceURL { + public String value(); + public String mail() default ""; +} +``` + +А затем можно использовать следующим образом + +``` +@SourceURL("http://coders.com/") +public class MyClass extends HisClass ... +``` + +В этом случае Scala предоставляет такую же возможность + +``` +@SourceURL("http://coders.com/") +class MyScalaClass ... +``` + +Элемент `mail` был указан со значением по умолчанию, поэтому нам не нужно явно указывать его значение. Мы не можем смешивать эти два стиля в Java: + +``` +@SourceURL(value = "http://coders.com/", + mail = "support@coders.com") +public class MyClass extends HisClass ... +``` + +Scala обеспечивает большую гибкость в этом отношении + +``` +@SourceURL("http://coders.com/", + mail = "support@coders.com") + class MyScalaClass ... +``` diff --git a/_ru/tour/automatic-closures.md b/_ru/tour/automatic-closures.md new file mode 100644 index 0000000000..7de83279d8 --- /dev/null +++ b/_ru/tour/automatic-closures.md @@ -0,0 +1,63 @@ +--- +layout: tour +title: Конструкция Автоматического Замыкания Зависимого Типа + +discourse: true +language: ru +partof: scala-tour +num: 14 +--- + +Scala допускает использование в качестве параметров методов имена беспараметрических функций. При вызове такого метода фактические параметры для беспараметрических функций не вычисляются, а передается функция с нулем аргументов, которая захватывает вычисление соответствующего параметра (так называемый *вызов по имени*). + +Следующий код демонстрирует этот механизм: + + object TargetTest1 extends Application { + def whileLoop(cond: => Boolean)(body: => Unit): Unit = + if (cond) { + body + whileLoop(cond)(body) + } + var i = 10 + whileLoop (i > 0) { + println(i) + i -= 1 + } + } + +Функция whileLoop принимает два параметра `cond` и `body`. При использовании функции значения этих параметров не вычисляются. Но всякий раз, когда параметры используются в теле `whileLoop`, их значение будет вычисляться заново через использование автоматически созданных неявно вызываемых функций. Таким образом, наш метод `whileLoop` реализует Java-подобный цикл while-loop со схемой рекурсивной реализации. + +Мы можем комбинировать использование [инфиксных/постфиксных операторов](operators.html) с этим механизмом для создания более сложных выражений (с хорошим синтаксисом). + +Вот реализация loop-unless выражения: + + object TargetTest2 extends Application { + def loop(body: => Unit): LoopUnlessCond = + new LoopUnlessCond(body) + protected class LoopUnlessCond(body: => Unit) { + def unless(cond: => Boolean) { + body + if (!cond) unless(cond) + } + } + var i = 10 + loop { + println("i = " + i) + i -= 1 + } unless (i == 0) + } +Функция `loop` принимает только тело цикла и возвращает экземпляр класса `LoopUnlessCond` (который захватывает это тело цикла). Обратите внимание, что тело еще не вычислено. Класс `LoopUnlessCond` имеет метод `unless`, который мы можем использовать как *инфиксный оператор*. Таким образом, мы получаем вполне естественный синтаксис для нашего нового цикла: `loop { < выражение > } unless ( < условие > )`. + + +Ниже приведен вывод выполнения `TargetTest2`: + + i = 10 + i = 9 + i = 8 + i = 7 + i = 6 + i = 5 + i = 4 + i = 3 + i = 2 + i = 1 diff --git a/_ru/tour/basics.md b/_ru/tour/basics.md new file mode 100644 index 0000000000..bf0257a403 --- /dev/null +++ b/_ru/tour/basics.md @@ -0,0 +1,322 @@ +--- +layout: tour +title: Основы + +discourse: true + +partof: scala-tour + +num: 2 +language: ru +next-page: unified-types +previous-page: tour-of-scala + +--- + +На этой странице мы расскажем об основах Scala. + +## Попробовать Scala в браузере. + +Вы можете запустить Scala в браузере с помощью ScalaFiddle. + +1. Зайдите на [https://scalafiddle.io](https://scalafiddle.io). +2. Вставьте `println("Hello, world!")` в левую панель. +3. Нажмите кнопку "Run". Вывод отобразится в правой панели. + +Это простой способ поэкспериментировать со Scala кодом без всяких настроек. + +Большинство примеров кода в этой документации также интегрированы с ScalaFiddle, +поэтому вы можете поэкспериментировать с ними, просто нажав кнопку Run. + +## Выражения + +Выражения - это вычислимые утверждения. +``` +1 + 1 +``` +Вы можете выводить результаты выражений используя `println`. + +{% scalafiddle %} +```tut +println(1) // 1 +println(1 + 1) // 2 +println("Hello!") // Hello! +println("Hello," + " world!") // Hello, world! +``` +{% endscalafiddle %} + +### Значения + +Результаты выражений можно присваивать именам с помощью ключевого слова `val`. + +```tut +val x = 1 + 1 +println(x) // 2 +``` + +Названные результаты, такие как `x` в примере, называются значениями. +Вызов значения не приводит к его повторному вычислению. + +Значения не изменяемы и не могут быть переназначены. + +```tut:fail +x = 3 // Не компилируется. +``` + +Типы значений могут быть выведены автоматически, но можно и явно указать тип, как показано ниже: + +```tut +val x: Int = 1 + 1 +``` + +Обратите внимание, что объявление типа `Int` происходит после идентификатора `x`, следующим за `:`. + +### Переменные + +Переменные похожи на значения константы, за исключением того, что их можно присваивать заново. Вы можете объявить переменную с помощью ключевого слова `var`. + +```tut +var x = 1 + 1 +x = 3 // Компилируется потому что "x" объявлен с ключевым словом "var". +println(x * x) // 9 +``` + +Как и в случае со значениями, вы можете явно указать тип, если захотите: + +```tut +var x: Int = 1 + 1 +``` + + +## Блоки + +Вы можете комбинировать выражения, окружая их `{}`. Мы называем это блоком. + +Результат последнего выражения в блоке - будет результатом всего блока в целом. + +```tut +println({ + val x = 1 + 1 + x + 1 +}) // 3 +``` + +## Функции + +Функции - это выражения, которые принимают параметры. + +Вы можете определить анонимную функцию (т.е. без имени), которая возвращает переданное число прибавив к нему единицу: + +```tut +(x: Int) => x + 1 +``` + +Слева от `=>` находится список параметров. Справа - выражение, связанное с параметрами. + +Вы также можете назвать функции. + +{% scalafiddle %} +```tut +val addOne = (x: Int) => x + 1 +println(addOne(1)) // 2 +``` +{% endscalafiddle %} + +Функции могут принимать множество параметров. + +{% scalafiddle %} +```tut +val add = (x: Int, y: Int) => x + y +println(add(1, 2)) // 3 +``` +{% endscalafiddle %} + +Или вообще не принимать никаких параметров. + +```tut +val getTheAnswer = () => 42 +println(getTheAnswer()) // 42 +``` + +## Методы + +Методы выглядят и ведут себя очень похоже на функции, но между ними есть несколько принципиальных различий. + +Методы задаются ключевым словом `def`. За `def` следует имя, список параметров, возвращаемый тип и тело. + +{% scalafiddle %} +```tut +def add(x: Int, y: Int): Int = x + y +println(add(1, 2)) // 3 +``` +{% endscalafiddle %} + +Обратите внимание, как объявлен возвращаемый тип сразу _после_ списка параметров и двоеточия `: Int`. + +Методы могут принимать несколько списков параметров. + +{% scalafiddle %} +```tut +def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier +println(addThenMultiply(1, 2)(3)) // 9 +``` +{% endscalafiddle %} + +Или вообще ни одного списка параметров. + +```tut +def name: String = System.getProperty("user.name") +println("Hello, " + name + "!") +``` + +Есть некоторые отличия от функций, но пока что их можно рассматривать как нечто похожее. + +Методы также могут иметь многострочные выражения. +```tut +def getSquareString(input: Double): String = { + val square = input * input + square.toString +} +``` +Последнее выражение в теле становится возвращаемым значением метода. (У Scala есть ключевое слово `return` , но оно практически не используется.) + +## Классы + +Вы можете объявлять классы используя ключевое слово `class`, за которым следует его имя и параметры конструктора. + +```tut +class Greeter(prefix: String, suffix: String) { + def greet(name: String): Unit = + println(prefix + name + suffix) +} +``` +Возвращаемый тип метода `greet` это `Unit`, используется тогда, когда не имеет смысла что-либо возвращать. Аналогично `void` в Java и C. (Поскольку каждое выражение Scala должно иметь какое-то значение, то при отсутствии возвращающегося значения, возвращается экземпляр типа Unit. Явным образом его можно задать как `()`, он не несет какой-либо информации.) + +Вы можете создать экземпляр класса используя ключевое слово `new`. + +```tut +val greeter = new Greeter("Hello, ", "!") +greeter.greet("Scala developer") // Hello, Scala developer! +``` + +Позже мы рассмотрим классы [подробнее](classes.html). + +## Классы образцы (Case Class) + +В Scala есть специальный тип класса, который называется классом образцом (case class). По умолчанию такие классы неизменны и сравниваются по значению из конструктора. Вы можете объявлять классы образцы с помощью ключевых слов `case class`. + +```tut +case class Point(x: Int, y: Int) +``` + +Можно создавать экземпляры класса образца без использования ключевого слова `new`. + +```tut +val point = Point(1, 2) +val anotherPoint = Point(1, 2) +val yetAnotherPoint = Point(2, 2) +``` + +Они сравниваются по значению. + +```tut +if (point == anotherPoint) { + println(point + " and " + anotherPoint + " are the same.") +} else { + println(point + " and " + anotherPoint + " are different.") +} // Point(1,2) и Point(1,2) одни и те же. + +if (point == yetAnotherPoint) { + println(point + " and " + yetAnotherPoint + " are the same.") +} else { + println(point + " and " + yetAnotherPoint + " are different.") +} // Point(1,2) и Point(2,2) разные. +``` + +Есть еще много деталей, которые мы бы хотели рассказать про классы образцы, мы уверены, что вы влюбитесь в них! Обязательно рассмотрим их [позже](case-classes.html). + +## Объекты + +Объекты задаются и существуют в единственным экземпляре. Вы можете думать о них как об одиночках (сингэлтонах) своего собственного класса. + +Вы можете задать объекты при помощи ключевого слова `object`. + +```tut +object IdFactory { + private var counter = 0 + def create(): Int = { + counter += 1 + counter + } +} +``` + +Вы можете сразу получить доступ к объекту, ссылаясь на его имя. + +```tut +val newId: Int = IdFactory.create() +println(newId) // 1 +val newerId: Int = IdFactory.create() +println(newerId) // 2 +``` + +Позже мы рассмотрим объекты [подробнее](singleton-objects.html). + +## Трейты + +Трейты - как типы описывают характеристики классов, в нем могут объявляться определенные поля и методы. Трейты можно комбинировать. + +Объявить трейт можно с помощью ключевого слова `trait`. + +```tut +trait Greeter { + def greet(name: String): Unit +} +``` + +Трейты также могут иметь реализации методов и полей, которые предполагается использовать умолчанию. + +{% scalafiddle %} +```tut +trait Greeter { + def greet(name: String): Unit = + println("Hello, " + name + "!") +} +``` + +Вы можете наследовать свойства трейтов используя ключевое словом `extends` и переопределять реализацию с помощью ключевого слова `override`. + +```tut +class DefaultGreeter extends Greeter + +class CustomizableGreeter(prefix: String, postfix: String) extends Greeter { + override def greet(name: String): Unit = { + println(prefix + name + postfix) + } +} + +val greeter = new DefaultGreeter() +greeter.greet("Scala developer") // Hello, Scala developer! + +val customGreeter = new CustomizableGreeter("How are you, ", "?") +customGreeter.greet("Scala developer") // How are you, Scala developer? +``` +{% endscalafiddle %} + +Здесь `DefaultGreeter` наследуется только от одного трейта, но можно наследоваться от нескольких. + +Позже мы рассмотрим трейты [подробнее](traits.html). + +## Главный метод + +Главный метод является отправной точкой в программе. +Для Виртуальной Машины Java требуется, чтобы главный метод назывался `main` и принимал один аргумент, массив строк. + +Используя объект, можно задать главный метод следующим образом: + +```tut +object Main { + def main(args: Array[String]): Unit = + println("Hello, Scala developer!") +} +``` diff --git a/_ru/tour/by-name-parameters.md b/_ru/tour/by-name-parameters.md new file mode 100644 index 0000000000..9cd798b2ee --- /dev/null +++ b/_ru/tour/by-name-parameters.md @@ -0,0 +1,42 @@ +--- +layout: tour +title: Вызов по имени + +discourse: true + +partof: scala-tour + +num: 31 +language: ru +next-page: annotations +previous-page: operators + +--- + +_Вызов параметров по имени_ - это когда значение параметра вычисляется только в момент вызова параметра. Этот способ противоположен _вызову по значению_. Чтоб вызов параметра был по имени, необходимо просто указать `=>` перед его типом. +```tut +def calculate(input: => Int) = input * 37 +``` +Преимущество вызова параметров по имени заключается в том, что они не вычисляются если не используются в теле функции. С другой стороны плюсы вызова параметров по значению в том, что они вычисляются только один раз. + +Вот пример того, как мы можем реализовать условный цикл: + +```tut +def whileLoop(condition: => Boolean)(body: => Unit): Unit = + if (condition) { + body + whileLoop(condition)(body) + } + +var i = 2 + +whileLoop (i > 0) { + println(i) + i -= 1 +} // выведет 2 1 +``` +Метод `whileLoop` использует несколько списков параметров - условие и тело цикла. Если `condition` является верным, выполняется `body`, а затем выполняется рекурсивный вызов whileLoop. Если `condition` является ложным, то тело никогда не вычисляется, тк у нас стоит `=>` перед типом `body`. + +Теперь, когда мы передаем `i > 0` как наше условие `condition` и `println(i); i-= 1` как тело `body`, код ведет себя также как обычный цикл в большинстве языков программирования. + +Такая возможность откладывать вычисления параметра до его использования может помочь повысить производительность, отсекая не нужные вычисления при определенных условиях. diff --git a/_ru/tour/case-classes.md b/_ru/tour/case-classes.md new file mode 100644 index 0000000000..13c3622587 --- /dev/null +++ b/_ru/tour/case-classes.md @@ -0,0 +1,59 @@ +--- +layout: tour +title: Классы Образцы + +discourse: true + +partof: scala-tour + +num: 11 +language: ru +next-page: pattern-matching +previous-page: multiple-parameter-lists +prerequisite-knowledge: classes, basics, mutability + +--- + +Классы образцы (Case classes) похожи на обычные классы с несколькими ключевыми отличиями, о которых мы поговорим ниже. Классы образцы хороши для моделирования неизменяемых данных. На следующей странице обзора вы увидите, насколько они полезны для участия в [сопоставлении с примером](pattern-matching.html). + +## Объявление класса образца +Минимальный вариант объявления класса образца: указание ключевого слова `case class`, название и список параметров (которые могут быть пустыми). Пример: +```tut +case class Book(isbn: String) + +val frankenstein = Book("978-0486282114") +``` +Обратите внимание, что ключевое слово `new` не было использовано для создания экземпляра класса `Book`. Это связано с тем, что классы образцы по умолчанию имеют объект компаньон с методом `apply`, который берет на себя заботу о создании экземпляра класса. + +При создании класса образца с параметрами, эти параметры являются публичными и неизменяемыми. +``` +case class Message(sender: String, recipient: String, body: String) +val message1 = Message("guillaume@quebec.ca", "jorge@catalonia.es", "Ça va ?") + +println(message1.sender) // prints guillaume@quebec.ca +message1.sender = "travis@washington.us" // эта строка не компилируется +``` +Вы не можете переназначить `message1.sender`, потому что это `val` (т.е. константа). Возможно использовать `var`s в классах образцах, но это не рекомендуется. + +## Сравнение +Классы образцы сравниваются по структуре, а не по ссылкам: +``` +case class Message(sender: String, recipient: String, body: String) + +val message2 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?") +val message3 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va?") +val messagesAreTheSame = message2 == message3 // true +``` +Даже если `message2` и `message3` ссылаются на разные объекты, значения каждого из них равна. + +## Копирование +Вы можете создать копию экземпляра класса образца, просто воспользовавшись методом `copy`. При этом по желанию можно изменить аргументы конструктора. +``` +case class Message(sender: String, recipient: String, body: String) +val message4 = Message("julien@bretagne.fr", "travis@washington.us", "Me zo o komz gant ma amezeg") +val message5 = message4.copy(sender = message4.recipient, recipient = "claire@bourgogne.fr") +message5.sender // travis@washington.us +message5.recipient // claire@bourgogne.fr +message5.body // "Me zo o komz gant ma amezeg" +``` +Получатель `message4` использует в качестве отправителя `message5`, кроме параметра `body` который был скопирован из `message4`. diff --git a/_ru/tour/classes.md b/_ru/tour/classes.md new file mode 100644 index 0000000000..0b5a7e0bec --- /dev/null +++ b/_ru/tour/classes.md @@ -0,0 +1,110 @@ +--- +layout: tour +title: Классы + +discourse: true + +partof: scala-tour + +num: 4 +language: ru +next-page: traits +previous-page: unified-types +topics: classes +prerequisite-knowledge: no-return-keyword, type-declaration-syntax, string-interpolation, procedures + +--- + +Классы в Scala являются основами для создания объектов. Они могут содержать методы, константы, переменные, типы, объекты, трейты и классы, которые в совокупности называются _членами_. Типы, объекты и трейты будут рассмотрены позже в ходе нашего обзора. + +## Объявление класса +Минимальное объявление класса - это просто ключевое слово `class` и его имя. Имена классов должны быть написаны с заглавной буквы. +```tut +class User + +val user1 = new User +``` +Ключевое слово `new` используется для создания экземпляра класса. `User` имеет конструктор по умолчанию, который не принимает аргументов, так как конструктор не был определен. Однако обычно используется и конструктор, и тело класса. Пример объявления класса Point приведен ниже: + +```tut +class Point(var x: Int, var y: Int) { + + def move(dx: Int, dy: Int): Unit = { + x = x + dx + y = y + dy + } + + override def toString: String = + s"($x, $y)" +} + +val point1 = new Point(2, 3) +point1.x // 2 +println(point1) // prints (2, 3) +``` + +В этом классе у `Point` есть четыре члена: переменные `x` и `y` и методы `move` и `toString`. +В отличие от многих других языков, основной конструктор находится в сигнатуре класса `(var x: Int, var y: Int)`. Метод `move` принимает два целочисленных аргумента и возвращает значение Unit `()` - это пустое множество, которое не содержит никакой информации. Примерно соответствует `void` в Java-подобных языках. С другой стороны, `toString` не принимает никаких аргументов, а возвращает значение `String`. Поскольку `toString` переопределяет `toString` из [`AnyRef`](unified-types.html), он помечается ключевым словом `override`. + +## Конструкторы + +Конструкторы могут иметь необязательные параметры, если указать их значения по умолчанию как в примере: + +```tut +class Point(var x: Int = 0, var y: Int = 0) + +val origin = new Point // x и y оба равны 0 +val point1 = new Point(1) +println(point1.x) // выводит 1 + +``` + +В этой версии класса `Point`, `x` и `y` имеют значение по умолчанию `0`, поэтому аргументов не требуется. Однако, поскольку конструктор считывает аргументы слева направо, если вы просто хотите передать значение `y`, то вам нужно будет указать задаваемый параметр. +``` +class Point(var x: Int = 0, var y: Int = 0) +val point2 = new Point(y=2) +println(point2.y) // выводит 2 +``` + +Что также является хорошей практикой для повышения ясности кода. + +## Скрытые члены и синтаксис Геттер/Сеттер (получатель/установщик значений) +По умолчанию члены класса являются открытыми для внешнего доступа (публичными). Используйте модификатор `private`, чтобы скрыть их от внешнего доступа. +```tut +class Point { + private var _x = 0 + private var _y = 0 + private val bound = 100 + + def x = _x + def x_= (newValue: Int): Unit = { + if (newValue < bound) _x = newValue else printWarning + } + + def y = _y + def y_= (newValue: Int): Unit = { + if (newValue < bound) _y = newValue else printWarning + } + + private def printWarning = println("WARNING: Out of bounds") +} + +val point1 = new Point +point1.x = 99 +point1.y = 101 // выводит предупреждение (printWarning) +``` +В данной версии класса `Point` данные хранятся в скрытых переменных `_x` и `_y`. Существуют методы `def x` и `def y` для доступа к скрытым данным. Методы `def x_=` и `def y_=` (сеттеры) предназначены для проверки и установки значения `_x` и `_y`. Обратите внимание на специальный синтаксис для сеттеров: метод `_=` применяется к имени геттера. + +Первичные параметры конструктора с параметрами `val` и `var` являются общедоступными. Однако, поскольку `val` - это константа, то нельзя писать следующее. +``` +class Point(val x: Int, val y: Int) +val point = new Point(1, 2) +point.x = 3 // <-- не компилируется +``` + +Параметры без `val` или `var` являются скрытыми от внешнего доступа и видимы только внутри класса. +``` +class Point(x: Int, y: Int) +val point = new Point(1, 2) +point.x // <-- не компилируется +``` diff --git a/_ru/tour/compound-types.md b/_ru/tour/compound-types.md new file mode 100644 index 0000000000..988900cab4 --- /dev/null +++ b/_ru/tour/compound-types.md @@ -0,0 +1,54 @@ +--- +layout: tour +title: Составные Типы + +discourse: true + +partof: scala-tour + +num: 24 +language: ru +next-page: self-types +previous-page: abstract-type-members + +--- + +Иногда необходимо выразить, то что тип объекта является подтипом нескольких других типов. В Scala это можно выразить с помощью *составных типов*, которые являются объединением нескольких типов объектов. + +Предположим, у нас есть два трейта: `Cloneable` и `Resetable`: + +```tut +trait Cloneable extends java.lang.Cloneable { + override def clone(): Cloneable = { + super.clone().asInstanceOf[Cloneable] + } +} +trait Resetable { + def reset: Unit +} +``` + +Теперь предположим, что мы хотим написать функцию `cloneAndReset`, которая берет объект, клонирует его и сбрасывает (Reset) состояние исходного объекта: + +``` +def cloneAndReset(obj: ?): Cloneable = { + val cloned = obj.clone() + obj.reset + cloned +} +``` + +Возникает вопрос, какой тип параметр `obj` должна принимать наша объединённая функция. Если это `Cloneable`, то объект может использовать метод `clone`, но не `reset`; если это `Resetable` мы можем использовать метод `reset`, но нет операции `clone`. Чтобы избежать приведения типа в такой ситуации, мы можем указать, что тип `obj` является и `Cloneable`, и `Resetable`. Этот совместный тип в Scala записывается как: `Cloneable with Resetable`. + +Вот обновленная функция: + +``` +def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { + //... +} +``` + +Составные типы могут состоять из нескольких типов объектов, и они могут содержать единый доработанный объект, в котором будут доработаны характеристики существующих членов объекта. +Общая форма записи: `A with B with C ... { доработанный объект }` + +Пример использования таких доработок приведен на странице об [объединении классов с примесями](mixin-class-composition.html). diff --git a/_ru/tour/default-parameter-values.md b/_ru/tour/default-parameter-values.md new file mode 100644 index 0000000000..7bf61e5430 --- /dev/null +++ b/_ru/tour/default-parameter-values.md @@ -0,0 +1,49 @@ +--- +layout: tour +title: Значения Параметров По умолчанию + +discourse: true + +partof: scala-tour + +num: 33 +language: ru +next-page: named-arguments +previous-page: annotations +prerequisite-knowledge: named-arguments, function syntax + +--- + +Scala предоставляет возможность задавать значения параметров по умолчанию, что позволяет лишний раз не указывать параметры. + +```tut +def log(message: String, level: String = "INFO") = println(s"$level: $message") + +log("System starting") // выведет "INFO: System starting" +log("User not found", "WARNING") // выведет "WARNING: User not found" +``` + +У параметра `level` есть значение по умолчанию, поэтому он необязателен. В последней строке аргумент `"WARNING"` переназначает аргумент по умолчанию `"INFO"`. Вместо того чтоб использовать перегруженные методы в Java, вы можете просто указать дополнительные параметры как параметры по умолчанию для достижения того же эффекта. Однако, если при вызове пропущен хотя бы один аргумент, все остальные аргументы должны вызываться с указанием конкретного имени аргумента. + +```tut +class Point(val x: Double = 0, val y: Double = 0) + +val point1 = new Point(y = 1) +``` +Так мы можем указать что `y = 1`. + +Обратите внимание, что параметры по умолчанию в Scala, при вызове из Java кода, являются обязательными: + +```tut +// Point.scala +class Point(val x: Double = 0, val y: Double = 0) +``` + +```java +// Main.java +public class Main { + public static void main(String[] args) { + Point point = new Point(1); // не скомпилируется + } +} +``` diff --git a/_ru/tour/extractor-objects.md b/_ru/tour/extractor-objects.md new file mode 100644 index 0000000000..d3824e69c3 --- /dev/null +++ b/_ru/tour/extractor-objects.md @@ -0,0 +1,65 @@ +--- +layout: tour +title: Объект Экстрактор + +discourse: true + +partof: scala-tour + +num: 16 +language: ru +next-page: for-comprehensions +previous-page: regular-expression-patterns + +--- + +Объект Экстрактор (объект распаковщик или extractor object) - это объект с методом `unapply`. В то время как метод `apply` обычно действует как конструктор, который принимает аргументы и создает объект, метод `unapply` действует обратным образом, он принимает объект и пытается извлечь и вернуть аргументы из которых он (возможно) был создан. Чаще всего этот метод используется в функциях сопоставления с примером и в частично определенных функциях. + +```tut +import scala.util.Random + +object CustomerID { + + def apply(name: String) = s"$name--${Random.nextLong}" + + def unapply(customerID: String): Option[String] = { + val stringArray: Array[String] = customerID.split("--") + if (stringArray.tail.nonEmpty) Some(stringArray.head) else None + } +} + +val customer1ID = CustomerID("Sukyoung") // Sukyoung--23098234908 +customer1ID match { + case CustomerID(name) => println(name) // выведет Sukyoung + case _ => println("Could not extract a CustomerID") +} +``` +Метод `apply` создает `CustomerID` из строки `name`. `unapply` делает обратное, чтобы вернуть `name` обратно. Когда мы вызываем `CustomerID("Sukyoung")`, это сокращенный синтаксис вызова `CustomerID.apply("Sukyoung")`. Когда мы вызываем `case CustomerID(name) => println(name)`, мы на самом деле вызываем метод `unapply`. + +При объявлении нового значения можно использовать пример, в котором значение для инициализации переменной получается через извлечение, используя метод `unapply`. + +```tut +val customer2ID = CustomerID("Nico") +val CustomerID(name) = customer2ID +println(name) // выведет Nico +``` + +Что эквивалентно `val name = CustomerID.unapply(customer2ID).get`. + +```tut +val CustomerID(name2) = "--asdfasdfasdf" +``` + +Если совпадений нет, то бросается `scala.MatchError`: + +```tut:fail +val CustomerID(name3) = "-asdfasdfasdf" +``` + +Возвращаемый тип `unapply` выбирается следующим образом: + +* Если это всего лишь тест, возвращается `Boolean`. Например `case even()`. +* Если в результате найдено одно значение типа `T`, то возвращается `Option[T]`. +* Если вы хотите получить несколько значений `T1,..., Tn`, то ответ необходимо группировать в дополнительный кортеж `Option[(T1,..., Tn)]`. + +Иногда количество извлекаемых значений не является фиксированным. Если в зависимости от входа мы хотим вернуть произвольное количество значений, то для этого случая мы можем определить экстрактор методом `unapplySeq`, который возвращает `Option[Seq[T]]`. Характерным примером такого подхода является разложение `List` с помощью `case List(x, y, z) =>` и разложение `String` с помощью регулярного выражения `Regex`, такого как `case r(name, remainingFields @ _*) =>`. diff --git a/_ru/tour/for-comprehensions.md b/_ru/tour/for-comprehensions.md new file mode 100644 index 0000000000..5c79ca062c --- /dev/null +++ b/_ru/tour/for-comprehensions.md @@ -0,0 +1,66 @@ +--- +layout: tour +title: Сложные for-выражения + +discourse: true + +partof: scala-tour + +num: 17 +language: ru +next-page: generic-classes +previous-page: extractor-objects + +--- + +Scala предлагает простую запись для выражения *последовательных преобразований*. Эти преобразования можно упростить используя специальный синтаксис `for выражения` (for comprehension), который записывается как `for (enumerators) yield e`, где `enumerators` относятся к списку перечислителей, разделенных точкой с запятой. Где отдельный такой "перечислитель" (*enumerator*) является либо генератором, который вводит новые переменные, либо фильтром. For-выражение вычисляет тело `e` (которое связанно с тем что генерирует *enumerator*) и возвращает последовательность вычислений. + +Вот пример: + +```tut +case class User(name: String, age: Int) + +val userBase = List(User("Travis", 28), + User("Kelly", 33), + User("Jennifer", 44), + User("Dennis", 23)) + +val twentySomethings = for (user <- userBase if (user.age >=20 && user.age < 30)) + yield user.name // т. е. добавить результат к списку + +twentySomethings.foreach(name => println(name)) // выводит "Travis Dennis" +``` + `for`-выражение, используется с оператором `yield`, на самом деле создает `List`. Потому что мы указали `yield user.name` (то есть вывести имя пользователя), получаем `List[String]`. `user <- userBase` и есть наш генератор, а `if (user.age >=20 && user.age < 30)` - это фильтр который отфильтровывает пользователей, не достигших 20-летнего возраста. + +Ниже приведен более сложный пример использования двух генераторов. Он вычисляет все пары чисел между `0` и `n-1`, сумма которых равна заданному значению `v`: + +```tut +def foo(n: Int, v: Int) = + for (i <- 0 until n; + j <- i until n if i + j == v) + yield (i, j) + +foo(10, 10) foreach { + case (i, j) => + println(s"($i, $j) ") // выводит (1, 9) (2, 8) (3, 7) (4, 6) (5, 5) +} + +``` +Здесь `n == 10` и `v == 10`. На первой итерации `i == 0` и `j == 0` так `i + j != v` и поэтому ничего не выдается. `j` увеличивается еще в 9 раз, прежде чем `i` увеличивается до `1`. Без фильтра `if` будет просто напечатано следующее: +``` + +(0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (0, 5) (0, 6) (0, 7) (0, 8) (0, 9) (1, 1) ... +``` + +Обратите внимание, что for-выражение не ограничивается только работой со списками. Каждый тип данных, поддерживающий операции `withFilter`, `map`, and `flatMap` (с соответствующими типами), может быть использован в for-выражении. + +Вы можете обойтись без `yield` в for-выражении. В таком случае, результатом будет `Unit`. Это может быть полезным для выполнения кода основанного на побочных эффектах. Вот программа, эквивалентная предыдущей, но без использования `yield`: + +```tut +def foo(n: Int, v: Int) = + for (i <- 0 until n; + j <- i until n if i + j == v) + println(s"($i, $j)") + +foo(10, 10) +``` diff --git a/_ru/tour/generic-classes.md b/_ru/tour/generic-classes.md new file mode 100644 index 0000000000..30587567ca --- /dev/null +++ b/_ru/tour/generic-classes.md @@ -0,0 +1,59 @@ +--- +layout: tour +title: Обобщенные Классы + +discourse: true + +partof: scala-tour + +num: 18 +language: ru +next-page: variances +previous-page: for-comprehensions +assumed-knowledge: classes unified-types + +--- +Обобщенные классы (Generic classes) - это классы, обладающие параметрическим полиморфизмом (т. е. классы, которые изменяют свое поведение в зависимости от приписываемого им типа. Этот тип указывается в квадратных скобках `[]` сразу после имени класса). Они особенно полезны для создания коллекций. + +## Объявление обобщенного класса +Для объявления обобщенного класса необходимо после имени добавить тип в квадратных скобках `[]` как еще один параметр класса. По соглашению обычно используют заглавные буквы `A`, хотя можно использовать любые имена. +```tut +class Stack[A] { + private var elements: List[A] = Nil + def push(x: A) { elements = x :: elements } + def peek: A = elements.head + def pop(): A = { + val currentTop = peek + elements = elements.tail + currentTop + } +} +``` +Данная реализация класса `Stack` принимает в качестве параметра любой тип `A`. Это означает что список, `var elements: List[A] = Nil`, может хранить только элементы типа `A`. Процедура `def push` принимает только объекты типа `A` (примечание: `elements = x :: elements` переназначает `elements` в новый список, созданный путем добавления `x`а к текущим `elements`). + +## Использование + +Чтобы использовать обобщенный класс, поместите конкретный тип в квадратные скобки вместо `A`. +``` +val stack = new Stack[Int] +stack.push(1) +stack.push(2) +println(stack.pop) // выведет 2 +println(stack.pop) // выведет 1 +``` +Экземпляр `stack` может принимать только Intы. Однако, если тип имеет подтипы, то они также могут быть приняты: +``` +class Fruit +class Apple extends Fruit +class Banana extends Fruit + +val stack = new Stack[Fruit] +val apple = new Apple +val banana = new Banana + +stack.push(apple) +stack.push(banana) +``` +Классы `Apple` и `Banana` наследуются от `Fruit` так, что мы можем засунуть экземпляры `Apple` и `Banana` в пачку `Fruit`. + +_Примечание: подтипы обобщенных типов - *инвариантны*. Это означает, что если у нас есть стэк символов типа `Stack[Char]`, то он не может быть использован как стек интов типа `Stack[Int]`. Это нежелательное поведение, потому как позволило бы нам добавлять в стек символов целые числа. В заключение, `Stack[A]` является подтипом `Stack[B]` тогда и только тогда, когда `B = A`. Поскольку это может быть довольно строгим ограничением, Scala предлагает [механизм вариативного описания параметров типа](variances.html) для контроля за поведением подтипов._ diff --git a/_ru/tour/higher-order-functions.md b/_ru/tour/higher-order-functions.md new file mode 100644 index 0000000000..6df55532e9 --- /dev/null +++ b/_ru/tour/higher-order-functions.md @@ -0,0 +1,109 @@ +--- +layout: tour +title: Функции Высшего Порядка + +discourse: true + +partof: scala-tour + +num: 8 +language: ru +next-page: nested-functions +previous-page: mixin-class-composition + +--- + +Функции высшего порядка могут принимать другие функции в качестве параметров или возвращать функцию в качестве результата +Такое возможно поскольку функции являются объектами первого класса в Scala. +На текущем этапе терминология может казаться немного запутанной, мы используем следующую фразу "функция высшего порядка" как для методов, так и для функций, которые могут принимать другие функции в качестве параметров, или возвращать функции в качестве результата. + +Одним из наиболее распространенных примеров функции высшего порядка +является функция `map`, которая доступна в коллекциях Scala. +```tut +val salaries = Seq(20000, 70000, 40000) +val doubleSalary = (x: Int) => x * 2 +val newSalaries = salaries.map(doubleSalary) // List(40000, 140000, 80000) +``` +`doubleSalary` - это функция, которая принимает один Int `x` и возвращает `x * 2`. В общем случае, кортеж (список имен в скобках) слева от стрелки `=>` - это список параметров, а значение выражения следует справа. Это же значение возвращается в качестве результата. В строке 3 к каждому элементу списка зарплат (salaries) применяется функция `doubleSalary`. + +Чтобы сократить код, мы можем сделать функцию анонимной и передать ее напрямую в качестве аргумента в map: +``` +val salaries = Seq(20000, 70000, 40000) +val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) +``` +Обратите внимание, что в приведенном выше примере `x`не объявлен как `Int`. Это потому, что компилятор может вывести тип, основываясь на типе который ожидает функция map. Еще более элегантным способом написания этого же кода было бы таким: + +```tut +val salaries = Seq(20000, 70000, 40000) +val newSalaries = salaries.map(_ * 2) +``` +Поскольку компилятор Scala уже знает тип параметров (Int), вам нужно только указать правую часть функции. Единственное условие заключается в том, что вместо имени параметра необходимо использовать `_` (в предыдущем примере это было `x`). + +## Преобразование методов в функции +Также возможно передавать методы в качестве аргументов функциям более высокого порядка, поскольку компилятор Scala может преобразовать метод в функцию. +``` +case class WeeklyWeatherForecast(temperatures: Seq[Double]) { + + private def convertCtoF(temp: Double) = temp * 1.8 + 32 + + def forecastInFahrenheit: Seq[Double] = temperatures.map(convertCtoF) // <-- передается метод convertCtoF +} +``` +Здесь метод `convertCtoF` передается в `forecastInFahrenheit`. Это возможно, потому что компилятор преобразовывает `convertCtoF` в функцию `x => ConvertCtoF(x)` (примечание: `x` будет сгенерированным именем, которое гарантированно будет уникальным в рамках своей области видимости). + +## Функции, которые принимают функции +Одной из причин использования функций высшего порядка является сокращение избыточного кода. Допустим, вам нужны какие-то методы, которые могли бы повышать чью-то зарплату по разным условиям. Без создания функции высшего порядка это могло бы выглядеть примерно так: + +```tut +object SalaryRaiser { + + def smallPromotion(salaries: List[Double]): List[Double] = + salaries.map(salary => salary * 1.1) + + def greatPromotion(salaries: List[Double]): List[Double] = + salaries.map(salary => salary * math.log(salary)) + + def hugePromotion(salaries: List[Double]): List[Double] = + salaries.map(salary => salary * salary) +} +``` + +Обратите внимание, что каждый из этих трех методов отличается только коэффициентом умножения. Для упрощения можно перенести повторяющийся код в функцию высшего порядка: + +```tut +object SalaryRaiser { + + private def promotion(salaries: List[Double], promotionFunction: Double => Double): List[Double] = + salaries.map(promotionFunction) + + def smallPromotion(salaries: List[Double]): List[Double] = + promotion(salaries, salary => salary * 1.1) + + def bigPromotion(salaries: List[Double]): List[Double] = + promotion(salaries, salary => salary * math.log(salary)) + + def hugePromotion(salaries: List[Double]): List[Double] = + promotion(salaries, salary => salary * salary) +} +``` + +Новый метод, `promotion`, берет зарплату и функцию типа `Double => Double` (т.е. функция, которая берет Double и возвращает Double) и возвращает их произведение. + +## Функции, возвращающие функции + +Есть определенные случаи, когда вы хотите сгенерировать функцию. Вот пример метода, который возвращает функцию. + +```tut +def urlBuilder(ssl: Boolean, domainName: String): (String, String) => String = { + val schema = if (ssl) "https://" else "http://" + (endpoint: String, query: String) => s"$schema$domainName/$endpoint?$query" +} + +val domainName = "www.example.com" +def getURL = urlBuilder(ssl=true, domainName) +val endpoint = "users" +val query = "id=1" +val url = getURL(endpoint, query) // "https://www.example.com/users?id=1": String +``` + +Обратите внимание, что возвращаемый тип urlBuilder`(String, String) => String`. Это означает, что возвращаемая анонимная функция принимает две строки и возвращает строку. В нашем случае возвращаемая анонимная функция `(endpoint: String, query: String) => s"https://www.example.com/$endpoint?$query"`. \ No newline at end of file diff --git a/_ru/tour/implicit-conversions.md b/_ru/tour/implicit-conversions.md new file mode 100644 index 0000000000..1040f47f9b --- /dev/null +++ b/_ru/tour/implicit-conversions.md @@ -0,0 +1,63 @@ +--- +layout: tour +title: Неявные Преобразования + +discourse: true + +partof: scala-tour + +num: 27 +language: ru +next-page: polymorphic-methods +previous-page: implicit-parameters + +--- + +Неявные преобразование типа `S` к типу `T` задается неявным значением функционального типа `S =>T`, или неявным методом, который способен преобразовывать к значению требуемого типа. + +Неявное преобразование применяются в двух случаях: + +* Если выражение `e` типа `S` не подходит под ожидаемый тип выражения `T`. +* Если мы выбирая член `e.m`, где `e` является представителем типа `S`, при этом выбранное имя `m` не найдено среди доступных селекторов принадлежащих типу `S`. + +В первом случае выполняется поиск приведения `c`, которое можно применить к `e` чтоб тип результата стал соответствовать ожидаемому `T`. +Во втором случае выполняется поиск преобразования `c`, которое применимо к `e` и результат которого бы содержал член с именем `m`. + +Если неявный метод `List[A] => Ordered[List[A]]` находится в области видимости, также как и неявный метод `Int => Ordered[Int]`, то следующая операция с двумя списками типа `List[Int]` является допустимой: + +``` +List(1, 2, 3) <= List(4, 5) +``` + +Неявный метод `Int => Ordered[Int]` предоставляется автоматически через `scala.Predef.intWrapper`. Ниже приведен пример объявления неявного метода `List[A] => Ordered[List[A]]`. + +```tut +import scala.language.implicitConversions + +implicit def list2ordered[A](x: List[A]) + (implicit elem2ordered: A => Ordered[A]): Ordered[List[A]] = + new Ordered[List[A]] { + //заменить на более полезную реализацию + def compare(that: List[A]): Int = 1 + } +``` + +Неявно импортируемый объект `scala.Predef` объявляет ряд псевдонимов для часто используемым типов (например, `scala.collection.immutable.Map` использует псевдоним `Map`) и методов (например, `assert`), а также делает доступным целую серию неявных преобразований. + +Например, при вызове Java метода, который ожидает `java.lang.Integer`, вместо него вы можете свободно использовать `scala.Int`. Потому что Predef включает в себя следующие неявные преобразования: + +```tut +import scala.language.implicitConversions + +implicit def int2Integer(x: Int) = + java.lang.Integer.valueOf(x) +``` + +Компилятор предупреждает при компиляции об обнаружении неявных преобразований, тк неявные преобразования могут иметь разные подводные камни (особенно если использовать их без разбора). + +Чтоб отключить предупреждения выполните одно из следующих действий: + +* Импортируйте `scala.language.implicitConversions` в области видимости, где объявлены неявные преобразования. +* Вызывайте компилятор с ключом `-language:implicitConversions`. + +В таком случае при преобразовании компилятором не будет выдаваться никаких предупреждений. diff --git a/_ru/tour/implicit-parameters.md b/_ru/tour/implicit-parameters.md new file mode 100644 index 0000000000..e9f7756e15 --- /dev/null +++ b/_ru/tour/implicit-parameters.md @@ -0,0 +1,73 @@ +--- +layout: tour +title: Неявные Параметры + +discourse: true + +partof: scala-tour + +num: 26 +language: ru +next-page: implicit-conversions +previous-page: self-types + +--- + +Метод может иметь список _неявных_ параметров, помеченный ключевым словом _implicit_ в начале списка параметров. Если параметры в этом списке не передаются как обычно, то Scala будет искать, где можно получить неявное значение требуемого типа, и если найдет, то передаст его автоматически. + +Места, где Scala будет искать эти параметры, делятся на две категории: + +* Скала сначала будет искать неявные параметры, доступ к которым можно получить напрямую (без префикса) в месте вызова метода в котором запрошены неявные параметры. +* Затем он ищет членов, помеченных как implicit во всех объектах компаньонах, связанных с типом неявного параметра. + +Более подробное руководство, о том где scala ищет неявные значения можно найти в [FAQ](//docs.scala-lang.org/tutorials/FAQ/finding-implicits.html) + +В следующем примере мы определяем метод `sum`, который вычисляет сумму элементов списка, используя операции `add` и `unit` моноида. Обратите внимание, что неявные значения не могут находится выше уровнем. + +```tut +abstract class Monoid[A] { + def add(x: A, y: A): A + def unit: A +} + +object ImplicitTest { + implicit val stringMonoid: Monoid[String] = new Monoid[String] { + def add(x: String, y: String): String = x concat y + def unit: String = "" + } + + implicit val intMonoid: Monoid[Int] = new Monoid[Int] { + def add(x: Int, y: Int): Int = x + y + def unit: Int = 0 + } + + def sum[A](xs: List[A])(implicit m: Monoid[A]): A = + if (xs.isEmpty) m.unit + else m.add(xs.head, sum(xs.tail)) + + def main(args: Array[String]): Unit = { + println(sum(List(1, 2, 3))) // использует intMonoid неявно + println(sum(List("a", "b", "c"))) // использует stringMonoid неявно + } +} +``` + +`Monoid` определяет здесь операцию под названием `add`, которая сочетает два элемента типа `A` и возвращает сумму типа `A`, операция `unit` позволяет вернуть отдельный (специфичный) элемент типа `A`. + +Чтобы показать, как работают неявные параметры, сначала определим моноиды `stringMonoid` и `intMonoid` для строк и целых чисел, соответственно. Ключевое слово `implicit` указывает на то, что этот объект может быть использован неявно. + +Метод `sum` принимает `List[A]` и возвращает `A`, который берет начальное `A` из `unit` и объединяет каждое следующее `A` в списке используя `add` метод. Указание параметра `m` в качестве неявного параметра подразумевает, что `xs` параметр будет обеспечен тогда, когда при вызове параметра метода Scala сможет найти неявный `Monoid[A]` чтоб его передать в качестве параметра `m`. + +В нашем `main` методе мы вызываем `sum` дважды и предоставляем только `xs` параметр. Теперь Scala будет искать неявное значение в указанных ранее областях видимости. Первый вызов `sum` проходит с использованием `List[Int]` в качестве `xs`, это означает, что элемент `A` имеет тип `Int`. Неявный список параметров с `m` опущен, поэтому Scala будет искать неявное значение типа `Monoid[Int]`. Первое правило поиска гласит + +> Скала сначала будет искать неявные параметры, доступ к которым можно получить напрямую (без префикса) в месте вызова метода в котором запрошены неявные параметры. + +`intMonoid` - это задание неявного значения, доступ к которому можно получить непосредственно в `main`. Оно имеет подходящий тип, поэтому передается методу `sum` автоматически. + +Второй вызов `sum` проходит используя `List[String]`, что означает, что `A` - это `String`. Неявный поиск будет идти так же, как и в случае с `Int`, но на этот раз будет найден `stringMonoid`, и передан автоматически в качестве `m`. + +Программа выведет на экран +``` +6 +abc +``` diff --git a/_ru/tour/inner-classes.md b/_ru/tour/inner-classes.md new file mode 100644 index 0000000000..64fca34b24 --- /dev/null +++ b/_ru/tour/inner-classes.md @@ -0,0 +1,81 @@ +--- +layout: tour +title: Внутренние классы + +discourse: true + +partof: scala-tour + +num: 22 +language: ru +next-page: abstract-type-members +previous-page: lower-type-bounds + +--- + +В Scala классам можно иметь в качестве членов другие классы. В отличие от Java-подобных языков, где такие внутренние классы являются членами окружающего класса, в Scala такие внутренние классы привязаны к содержащему его объекту. Предположим, мы хотим, чтобы компилятор не позволял нам на этапе компиляции смешивать узлы этого графа. Для решения этой задачи нам подойдут типы, зависящие от своего расположения. + +Чтобы проиллюстрировать суть подхода, мы быстро набросаем реализацию такого графа: + +```tut +class Graph { + class Node { + var connectedNodes: List[Node] = Nil + def connectTo(node: Node) { + if (connectedNodes.find(node.equals).isEmpty) { + connectedNodes = node :: connectedNodes + } + } + } + var nodes: List[Node] = Nil + def newNode: Node = { + val res = new Node + nodes = res :: nodes + res + } +} +``` +Данная программа представляет собой граф в составленного из списка узлов (`List[Node]`). Каждый узел имеет список других узлов, с которым он связан (`connectedNodes`). Класс `Node` является _зависимым от месторасположения типом_, поскольку он вложен в `Class Graph`. Поэтому все узлы в `connectedNodes` должны быть созданы с использованием `newNode` из одного и того же экземпляра `Graph`. + +```tut +val graph1: Graph = new Graph +val node1: graph1.Node = graph1.newNode +val node2: graph1.Node = graph1.newNode +val node3: graph1.Node = graph1.newNode +node1.connectTo(node2) +node3.connectTo(node1) +``` +Мы явно объявили тип `node1`, `node2` и `node3` как `graph1.Node` для ясности, хотя компилятор мог определить это самостоятельно. Это потому, что когда мы вызываем `graph1.newNode`, вызывающий `new Node`, метод использует экземпляр `Node`, специфичный экземпляру `graph1`. + +Если у нас есть два графа, то система типов Scala не позволит смешивать узлы, определенные в рамках одного графа, с узлами другого, так как узлы другого графа имеют другой тип. +Вот некорректная программа: + +``` +val graph1: Graph = new Graph +val node1: graph1.Node = graph1.newNode +val node2: graph1.Node = graph1.newNode +node1.connectTo(node2) // работает +val graph2: Graph = new Graph +val node3: graph2.Node = graph2.newNode +node1.connectTo(node3) // не работает! +``` +Тип `graph1.Node` отличается от типа `graph2.Node`. В Java последняя строка в предыдущем примере программы была бы правильной. Для узлов обоих графов Java будет присваивать один и тот же тип `Graph.Node`, т.е. `Node` имеет префикс класса `Graph`. В Скале такой тип также может быть выражен, он записывается `Graph#Node`. Если мы хотим иметь возможность соединять узлы разных графов, то вам нужно изменить описание первоначальной реализации графов следующим образом: + +```tut +class Graph { + class Node { + var connectedNodes: List[Graph#Node] = Nil + def connectTo(node: Graph#Node) { + if (connectedNodes.find(node.equals).isEmpty) { + connectedNodes = node :: connectedNodes + } + } + } + var nodes: List[Node] = Nil + def newNode: Node = { + val res = new Node + nodes = res :: nodes + res + } +} +``` diff --git a/_ru/tour/lower-type-bounds.md b/_ru/tour/lower-type-bounds.md new file mode 100644 index 0000000000..fe42c61630 --- /dev/null +++ b/_ru/tour/lower-type-bounds.md @@ -0,0 +1,70 @@ +--- +layout: tour +title: Нижнее Ограничение Типа + +discourse: true + +partof: scala-tour + +num: 21 +language: ru +next-page: inner-classes +previous-page: upper-type-bounds +prerequisite-knowledge: upper-type-bounds, generics, variance + +--- + +В то время как [верхнее ограничение типа](upper-type-bounds.html) ограничивает тип до подтипа стороннего типа, *нижнее ограничение типа* объявляют тип супертипом стороннего типа. Термин `B >: A` выражает, то что параметр типа `B` или абстрактный тип `B` относится к супертипу типа `A`. В большинстве случаев `A` будет задавать тип класса, а `B` задавать тип метода. + +Вот пример, где это полезно: + +```tut:fail +trait Node[+B] { + def prepend(elem: B): Node[B] +} + +case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { + def prepend(elem: B): ListNode[B] = ListNode(elem, this) + def head: B = h + def tail: Node[B] = t +} + +case class Nil[+B]() extends Node[B] { + def prepend(elem: B): ListNode[B] = ListNode(elem, this) +} +``` + +В данной программе реализован связанный список. `Nil` представляет пустой список. Класс `ListNode` - это узел, который содержит элемент типа `B` (`head`) и ссылку на остальную часть списка (`tail`). Класс `Node` и его подтипы ковариантны, потому что у нас указанно `+B`. + +Однако эта программа _не компилируется_, потому что параметр `elem` в `prepend` имеет тип `B`, который мы объявили *ко*вариантным. Так это не работает, потому что функции *контр*вариантны в типах своих параметров и *ко*вариантны в типах своих результатов. + +Чтобы исправить это, необходимо перевернуть вариантность типа параметра `elem` в `prepend`. Для этого мы вводим новый тип для параметра `U`, у которого тип `B` указан в качестве нижней границы типа. + +```tut +trait Node[+B] { + def prepend[U >: B](elem: U): Node[U] +} + +case class ListNode[+B](h: B, t: Node[B]) extends Node[B] { + def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this) + def head: B = h + def tail: Node[B] = t +} + +case class Nil[+B]() extends Node[B] { + def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this) +} +``` + +Теперь мы можем сделать следующее: +```tut +trait Bird +case class AfricanSwallow() extends Bird +case class EuropeanSwallow() extends Bird + + +val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil()) +val birdList: Node[Bird] = africanSwallowList +birdList.prepend(new EuropeanSwallow) +``` +`Node[Bird]` может быть присвоен `africanSwallowList` , но затем может добавлять и `EuropeanSwallow`. diff --git a/_ru/tour/mixin-class-composition.md b/_ru/tour/mixin-class-composition.md new file mode 100644 index 0000000000..4c528ca3c9 --- /dev/null +++ b/_ru/tour/mixin-class-composition.md @@ -0,0 +1,83 @@ +--- +layout: tour +title: Композиция классов с примесями + +discourse: true + +partof: scala-tour + +num: 7 +language: ru +next-page: higher-order-functions +previous-page: tuples +prerequisite-knowledge: inheritance, traits, abstract-classes, unified-types + +--- +Примеси (Mixin) - это трейты, которые используются для создания класса. + +```tut +abstract class A { + val message: String +} +class B extends A { + val message = "I'm an instance of class B" +} +trait C extends A { + def loudMessage = message.toUpperCase() +} +class D extends B with C + +val d = new D +println(d.message) // I'm an instance of class B +println(d.loudMessage) // I'M AN INSTANCE OF CLASS B +``` +У класса `D` есть суперкласс `B` и примесь `C`. Классы могут иметь только один суперкласс, но много примесей (используя ключевыое слово `extends` и `with` соответственно). Примеси и суперкласс могут иметь один и тот же супертип. + +Теперь давайте рассмотрим более интересный пример, начиная с абстрактного класса: + +```tut +abstract class AbsIterator { + type T + def hasNext: Boolean + def next(): T +} +``` +Класс имеет абстрактный тип `T` и методы стандартного итератора. + +Далее создаем конкретную реализацию класса (все абстрактные члены `T`, `hasNext`, и `next` должны быть реализованы): + +```tut +class StringIterator(s: String) extends AbsIterator { + type T = Char + private var i = 0 + def hasNext = i < s.length + def next() = { + val ch = s charAt i + i += 1 + ch + } +} +``` +`StringIterator` принимает `String` и может быть использован для обхода по строке (например, чтоб проверить содержит ли строка определенный символ). + +Теперь давайте создадим трейт который тоже наследуется от `AbsIterator`. + +```tut +trait RichIterator extends AbsIterator { + def foreach(f: T => Unit): Unit = while (hasNext) f(next()) +} +``` +У этого трейта реализован метод `foreach` который постоянно вызывает переданную ему функцию `f: T => Unit` на каждом новом элементе (`next()`) до тех пор пока в итераторе содержатся элементы (`while (hasNext)`). Поскольку `RichIterator` это трейт, ему не нужно реализовывать членов абстрактного класса `AbsIterator`. + +Мы бы хотели объединить функциональность `StringIterator` и `RichIterator` в один класс. + +```tut +object StringIteratorTest extends App { + class RichStringIter extends StringIterator("Scala") with RichIterator + val richStringIter = new RichStringIter + richStringIter foreach println +} +``` +Новый класс `RichStringIter` включает `StringIterator` как суперкласс и `RichIterator` как примесь. + +Используя только одиночное наследование мы бы не могли добиться того же уровня гибкости. diff --git a/_ru/tour/multiple-parameter-lists.md b/_ru/tour/multiple-parameter-lists.md new file mode 100644 index 0000000000..d1938cb4b0 --- /dev/null +++ b/_ru/tour/multiple-parameter-lists.md @@ -0,0 +1,80 @@ +--- +layout: tour +title: Множественные списки параметров (Каррирование) + +discourse: true + +partof: scala-tour + +num: 10 +language: ru +next-page: case-classes +previous-page: nested-functions + +--- + +Методы могут объявляться с несколькими списками параметров. При этом когда такой метод вызывается с меньшим количеством списков параметров, это приводит к созданию новой функции, которая ожидает на вход не достающий список параметров. Формально это называется [частичное применение](https://en.wikipedia.org/wiki/Partial_application). + +Рассмотрим такие примеры из класса [Traversable](/overviews/collections/trait-traversable.html) коллекции Scala: + +``` +def foldLeft[B](z: B)(op: (B, A) => B): B +``` + +`foldLeft` применяет бинарный оператор `op` к начальному значению `z` и ко всем остальным элементам этого класса слева направо. Ниже приведен пример его использования. + +Начиная с начального значения 0, `foldLeft` применяет функцию `(m, n) => m + n` к каждому элементу списка и предыдущему накопленному значению. + +```tut +val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +val res = numbers.foldLeft(0)((m, n) => m + n) +print(res) // 55 +``` + +Множественные списки параметров имеют избыточный синтаксис, поэтому их следует использоваться экономно. Можем предложить следующие варианты для использования множественных списков (каррирования): + +#### Отдельный функциональный параметр + Функцию `op` можно выделить в отдельный функциональный параметр у `foldLeft`, благодаря такому выделению становится возможен более элегантный стиль передачи анонимной функции в метод. Без такого выделения код выглядел бы следующим образом: +``` +numbers.foldLeft(0, {(m: Int, n: Int) => m + n}) +``` + + Обратите внимание, что использование отдельного функционального параметра позволяет нам использовать автоматическое выведение типа для него, что делает код еще более кратким, это было бы невозможно без каррирования. + +``` +numbers.foldLeft(0)(_ + _) +``` + Если в утверждении `numbers.foldLeft(0)(_ + _)` зафиксировать отдельный параметр `z`, мы получим частично определенную функцию, которую можно переиспользовать, как показано ниже: +```tut +val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +val numberFunc = numbers.foldLeft(List[Int]())_ // z = Empty.List[Int] + +val squares = numberFunc((xs, x) => xs:+ x*x) +print(squares.toString()) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) + +val cubes = numberFunc((xs, x) => xs:+ x*x*x) +print(cubes.toString()) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000) +``` + + `foldLeft` и `foldRight` может быть использован в любой из следующих вариаций, +```tut +val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + +numbers.foldLeft(0)((sum, item) => sum + item) // Общая Форма +numbers.foldRight(0)((sum, item) => sum + item) // Общая Форма + +numbers.foldLeft(0)(_+_) // Форма с каррированием +numbers.foldRight(0)(_+_) // Форма с каррированием + +(0 /: numbers)(_+_) // Используется вместо foldLeft +(numbers :\ 0)(_+_) // Используется вместо foldRight +``` + + +#### Неявные параметры + Чтоб указать что параметр используется неявно (`implicit`) необходимо задавать несколько списков параметров. Примером может служить следующее: + +``` +def execute(arg: Int)(implicit ec: ExecutionContext) = ??? +``` + diff --git a/_ru/tour/named-arguments.md b/_ru/tour/named-arguments.md new file mode 100644 index 0000000000..defb9d4282 --- /dev/null +++ b/_ru/tour/named-arguments.md @@ -0,0 +1,34 @@ +--- +layout: tour +title: Именованные Аргументы + +discourse: true + +partof: scala-tour + +num: 34 +language: ru +next-page: packages-and-imports +previous-page: default-parameter-values +prerequisite-knowledge: function-syntax + +--- + +При вызове методов можно конкретно указывать название задаваемого аргумента следующим образом: + +```tut +def printName(first: String, last: String): Unit = { + println(first + " " + last) +} + +printName("John", "Smith") // Prints "John Smith" +printName(first = "John", last = "Smith") // Prints "John Smith" +printName(last = "Smith", first = "John") // Prints "John Smith" +``` +Обратите внимание, что при указании имени параметра, порядок аргумента может быть изменен. Однако если какие-то аргументы именованного, а другие нет, то аргументы без имени должны стоять на первом месте и располагаться в том порядке, в котором описаны параметры метода. + +```tut:fail +printName(last = "Smith", "john") // ошибка: позиция после именованного аргумента +``` + +Обратите внимание, что именованные аргументы не работают при вызове Java методов. diff --git a/_ru/tour/nested-functions.md b/_ru/tour/nested-functions.md new file mode 100644 index 0000000000..ef082c36e4 --- /dev/null +++ b/_ru/tour/nested-functions.md @@ -0,0 +1,38 @@ +--- +layout: tour +title: Вложенные Методы + +discourse: true + +partof: scala-tour + +num: 9 +language: ru +next-page: multiple-parameter-lists +previous-page: higher-order-functions + +--- + +В Scala возможно объявление метода вкладывать в тело другого метода. Это реализовано в следующем примере, в котором метод `factorial` используется для вычисления факториала заданного числа: + +{% scalafiddle %} +```tut + def factorial(x: Int): Int = { + def fact(x: Int, accumulator: Int): Int = { + if (x <= 1) accumulator + else fact(x - 1, x * accumulator) + } + fact(x, 1) + } + + println("Factorial of 2: " + factorial(2)) + println("Factorial of 3: " + factorial(3)) +``` +{% endscalafiddle %} + +Результат выполнения программы: + +``` +Factorial of 2: 2 +Factorial of 3: 6 +``` diff --git a/_ru/tour/operators.md b/_ru/tour/operators.md new file mode 100644 index 0000000000..1609ce60f5 --- /dev/null +++ b/_ru/tour/operators.md @@ -0,0 +1,81 @@ +--- +layout: tour +title: Операторы + +discourse: true + +partof: scala-tour + +num: 30 +language: ru +next-page: by-name-parameters +previous-page: type-inference +prerequisite-knowledge: case-classes + +--- +В Скале операторы - это обычные методы. В качестве _инфиксного оператора_ может быть использован любой метод с одним параметром. Например, `+` может вызываться с использованием точки: +``` +10.+(1) +``` + +Однако легче воспринимать код, когда такие методы записаны как инфиксный оператор: +``` +10 + 1 +``` + +## Создание и использование операторов +В качестве оператора можно использовать любой допустимый символ. Включая имена на подобии `add` или символ (символы) типа `+`. +```tut +case class Vec(val x: Double, val y: Double) { + def +(that: Vec) = new Vec(this.x + that.x, this.y + that.y) +} + +val vector1 = Vec(1.0, 1.0) +val vector2 = Vec(2.0, 2.0) + +val vector3 = vector1 + vector2 +vector3.x // 3.0 +vector3.y // 3.0 +``` +У класса Vec есть метод `+`, который мы использовали для добавления `vector1` и `vector2`. Используя круглые скобки, можно строить сложные выражения с читаемым синтаксисом. Пример создания класса `MyBool`, которое включает в себя методы `and` и `or` + +```tut +case class MyBool(x: Boolean) { + def and(that: MyBool): MyBool = if (x) that else this + def or(that: MyBool): MyBool = if (x) this else that + def negate: MyBool = MyBool(!x) +} +``` + +Теперь можно использовать операторы `and` и `or` в качестве инфиксных операторов: + +```tut +def not(x: MyBool) = x.negate +def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) +``` + +Это помогает сделать объявление `xor` более читабельным. + +## Порядок очередности +Когда выражение использует несколько операторов, операторы оцениваются на основе приоритета первого символа. Таблица приоритетов символов: +``` +(символы которых нет снизу) +* / % ++ - +: += ! +< > +& +^ +| +(буквы) +``` +Такой приоритет распространяется на любые функции, которые вы задаете. Например, следующее выражение: +``` +a + b ^? c ?^ d less a ==> b | c +``` +эквивалентно +``` +((a + b) ^? (c ?^ d)) less ((a ==> b) | c) +``` +`?^` имеет высший приоритет, потому что начинается с символа `?`. Второй по старшинству приоритет имеет `+`, за которым следуют `==>`, `^?`, `|`, и `less`. diff --git a/_ru/tour/package-objects.md b/_ru/tour/package-objects.md new file mode 100644 index 0000000000..43cc9c85da --- /dev/null +++ b/_ru/tour/package-objects.md @@ -0,0 +1,72 @@ +--- +layout: tour +title: Объекты Пакета + +discourse: true + +partof: scala-tour + +num: 36 +language: ru +previous-page: packages-and-imports +--- + +# Объекты Пакета + +У каждого пакета может существовать связанный с этим пакетом объект (package object), общий для всех членов пакета. Такой объект может быть только один. Любые выражения, содержащиеся в объекте пакета, считаются членами самого пакета. + +Объекты пакета могут содержать произвольные виды выражений, а не только переменные и методы. Например, они часто используются для хранения псевдонимов типа и наборов неявных преобразований доступных всему пакету. Объекты пакета могут также наследоваться от классов и трейтов Scala. + +По соглашению, исходный код объекта пакета обычно помещается в файл под названием `package.scala`. + +См. пример ниже. Предположим, есть старший класс `Fruit` и три наследуемых от него объекта `Fruit` в пакете. + +`gardening.fruits`: + +``` +// в файле gardening/fruits/Fruit.scala +package gardening.fruits + +case class Fruit(name: String, color: String) +object Apple extends Fruit("Apple", "green") +object Plum extends Fruit("Plum", "blue") +object Banana extends Fruit("Banana", "yellow") +``` + +Теперь предположим, что мы хотим поместить переменную `planted` и метод `showFruit` непосредственно в пакет `gardening`. +Вот как это делается: + +``` +// в файле gardening/fruits/package.scala +package gardening +package object fruits { + val planted = List(Apple, Plum, Banana) + def showFruit(fruit: Fruit): Unit = { + println(s"${fruit.name}s are ${fruit.color}") + } +} +``` + +Для примера, следующий объект `PrintPlanted` импортирует `planted` и `showFruit` точно так же, как с вариантом импорта класса `Fruit`, используя групповой стиль импорта пакета gardening.fruits: + +``` +// в файле PrintPlanted.scala +import gardening.fruits._ +object PrintPlanted { + def main(args: Array[String]): Unit = { + for (fruit <- fruits.planted) { + showFruit(fruit) + } + } +} +``` + +Объекты пакета ведут себя также, как и любые другие объекты. Это означает, что вы можете использовать наследование, при этом сразу нескольких трейтов: + +``` +package object fruits extends FruitAliases with FruitHelpers { + // здесь располагаются вспомогательные классы и переменные +} +``` + +Обратите внимание, что перегрузка метода не работает в объектах пакета. diff --git a/_ru/tour/packages-and-imports.md b/_ru/tour/packages-and-imports.md new file mode 100644 index 0000000000..ef7865e3e7 --- /dev/null +++ b/_ru/tour/packages-and-imports.md @@ -0,0 +1,86 @@ +--- +layout: tour +title: Пакеты и Импорт + +discourse: true + +partof: scala-tour + +num: 35 +language: ru +previous-page: named-arguments +next-page: package-objects +--- + +# Пакеты и Импорт +Scala использует пакеты для указания пространства имен, они позволяют создавать модульную структуру кода. + +## Создание пакета +Пакеты создаются путем объявления одного или нескольких имен пакетов в верхней части файла Scala. + +``` +package users + +class User +``` +По соглашению пакеты называют тем же именем, что и каталог, содержащий файл Scala. Однако Scala не обращает внимания на расположение файлов. Структура каталогов sbt-проекта для `package users` может выглядеть следующим образом: +``` +- ExampleProject + - build.sbt + - project + - src + - main + - scala + - users + User.scala + UserProfile.scala + UserPreferences.scala + - test +``` +Обратите внимание, что каталог `users` находится внутри каталога `scala` и как в пакете содержатся несколько файлов Scala. Каждый файл Scala в пакете может иметь одно и то же объявление пакета. Другой способ объявления пакетов - с помощью фигурных скобок: +``` +package users { + package administrators { + class NormalUser + } + package normalusers { + class NormalUser + } +} +``` +Как видите, такой способ позволяет вкладывать пакеты друг в друга, а также обеспечивает отличный контроль за областью видимости и возможностью изоляции. + +Имя пакета должно быть все в нижнем регистре, и если код разрабатывается в организации имеющей сайт, то следует использовать имя следующего формата: `<домен-верхнего-уровня>.<доменное-имя>.<название-проекта>`. Например, если бы у Google был проект под названием `SelfDrivingCar`, название пакета выглядело бы следующим образом: +``` +package com.google.selfdrivingcar.camera + +class Lens +``` +Что может соответствовать следующей структуре каталога: `SelfDrivingCar/src/main/scala/com/google/selfdrivingcar/camera/Lens.scala`. + +## Импорт +Указание `import` открывает доступ к членам (классам, трейтам, функциям и т.д.) в других пакетах. Указание `import` не требуется для доступа к членам одного и того же пакета. Указание `import` избирательны: +``` +import users._ // групповой импорт всего пакета users +import users.User // импортировать только User +import users.{User, UserPreferences} // импортировать только User, UserPreferences +import users.{UserPreferences => UPrefs} // импортировать и переименовать +``` + +Одним из отличий Scala от Java является то, что импорт можно использовать где угодно: + +```tut +def sqrtplus1(x: Int) = { + import scala.math.sqrt + sqrt(x) + 1.0 +} +``` +В случае возникновения конфликта имен и необходимости импортировать что-либо из корня проекта, имя пакета должно начинаться с префикса `_root_`: +``` +package accounts + +import _root_.users._ +``` + + +Примечание: Пакеты `scala` и `java.lang`, а также `object Predef` импортируются по умолчанию. diff --git a/_ru/tour/pattern-matching.md b/_ru/tour/pattern-matching.md new file mode 100644 index 0000000000..9a46a74005 --- /dev/null +++ b/_ru/tour/pattern-matching.md @@ -0,0 +1,151 @@ +--- +layout: tour +title: Сопоставление с примером + +discourse: true + +partof: scala-tour + +num: 12 +language: ru +next-page: singleton-objects +previous-page: case-classes +prerequisite-knowledge: case-classes, string-interpolation, subtyping + +--- + +Сопоставление с примером (Pattern matching) - это механизм сравнения значений с определенным примером. При успешном совпадении значение может быть разложено на составные части. Мы рассматриваем сопоставление с примером, как более мощную версию `switch` оператора из Java. Eго также можно использовать вместо серии if/else выражений. + +## Синтаксис +Синтаксис сопоставления с примером состоит из значения, ключевого слова `match` (сопоставить) и по крайней мере, одного пункта с примером `case`, с которым мы хотим сопоставить наше значение. +```tut +import scala.util.Random + +val x: Int = Random.nextInt(10) + +x match { + case 0 => "zero" + case 1 => "one" + case 2 => "two" + case _ => "many" +} +``` +Значение константы `x` выше представляет собой случайное целое число от 0 до 10. `x` становится левым операндом оператора `match`, а справа - выражением с четырьмя примерами (называемые еще _вариантами_). Последний вариант `_` - позволяет "поймать все оставшиеся варианты" т. е. для любого числа больше 2. + +Сопоставление с примером возвращает значение. +```tut +def matchTest(x: Int): String = x match { + case 1 => "one" + case 2 => "two" + case _ => "many" +} +matchTest(3) // many +matchTest(1) // one +``` +Это сопоставляющее выражение имеет тип String, так как все варианты сопоставления возвращают String. Поэтому функция `matchTest` возвращает String. + +## Сопоставление с классами образцами + +Классы образцы особенно полезны для сопоставления. + +```tut +abstract class Notification + +case class Email(sender: String, title: String, body: String) extends Notification + +case class SMS(caller: String, message: String) extends Notification + +case class VoiceRecording(contactName: String, link: String) extends Notification + + +``` +`Notification` - абстрактный суперкласс, от которого наследуются три конкретных типа реализаций классов образцов `Email`, `SMS`, и `VoiceRecording`. Теперь мы можем делать сопоставление с примером используя в качестве примера один из этих классов образцов. +При сопоставлении с классом образцом мы можем сразу извлекать параметры из которых состоит класс (благодаря автоматическому использованию [объекта экстрактора](extractor-objects.html)): + +``` +def showNotification(notification: Notification): String = { + notification match { + case Email(email, title, _) => + s"You got an email from $email with title: $title" + case SMS(number, message) => + s"You got an SMS from $number! Message: $message" + case VoiceRecording(name, link) => + s"you received a Voice Recording from $name! Click the link to hear it: $link" + } +} +val someSms = SMS("12345", "Are you there?") +val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") + +println(showNotification(someSms)) // выводит "You got an SMS from 12345! Message: Are you there?" + +println(showNotification(someVoiceRecording)) // выводит "you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123" +``` +Функция `showNotification` принимает в качестве параметра абстрактный тип `Notification` который проверяет по образцам (т.е. выясняет, является ли он классом `Email`, `SMS` или `VoiceRecording`). В `case Email(email, title, _)`поля `email` и `title` используются в возвращаемом значении, а вот поле `body` игнорируется благодаря символу `_`. + +## Ограждения примеров +Ограждения примеров - это просто логические выражения, которые используются для того, чтобы сделать выбор более специфичным (убрать лишние варианты). Просто добавьте `if <логическое выражение>` после примера. +``` + +def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = { + notification match { + case Email(email, _, _) if importantPeopleInfo.contains(email) => + "You got an email from special someone!" + case SMS(number, _) if importantPeopleInfo.contains(number) => + "You got an SMS from special someone!" + case other => + showNotification(other) // в этом варианте считается подходящими параметры любого типа. Значит этот вариант выполняется во всех случаях и передает исходный параметр в функцию showNotification + } +} + +val importantPeopleInfo = Seq("867-5309", "jenny@gmail.com") + +val someSms = SMS("867-5309", "Are you there?") +val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") +val importantEmail = Email("jenny@gmail.com", "Drinks tonight?", "I'm free after 5!") +val importantSms = SMS("867-5309", "I'm here! Where are you?") + +println(showImportantNotification(someSms, importantPeopleInfo)) +println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) +println(showImportantNotification(importantEmail, importantPeopleInfo)) +println(showImportantNotification(importantSms, importantPeopleInfo)) +``` + +В варианте `case Email(email, _, _) if importantPeopleInfo.contains(email)`, пример сравнивается только если `email` находится в списке `importantPeopleInfo`. + +## Сопоставление только с типом +Вы можете сопоставлять только по типу как в примере: +```tut +abstract class Device +case class Phone(model: String) extends Device{ + def screenOff = "Turning screen off" +} +case class Computer(model: String) extends Device { + def screenSaverOn = "Turning screen saver on..." +} + +def goIdle(device: Device) = device match { + case p: Phone => p.screenOff + case c: Computer => c.screenSaverOn +} +``` +метод `goIdle` реализует изменение поведения в зависимости от типа `Device`. По соглашению в качестве названия варианта используется первая буква типа (в данном случае `p` и `c`). + +## Запечатанные классы +Трейты и классы могут быть помечены как `sealed` это означает, что подтипы должны быть объявлены в одном файле, гарантируя тем самым, что все подтипы будут известны. + +```tut +sealed abstract class Furniture +case class Couch() extends Furniture +case class Chair() extends Furniture + +def findPlaceToSit(piece: Furniture): String = piece match { + case a: Couch => "Lie on the couch" + case b: Chair => "Sit on the chair" +} +``` +Это полезно для сопоставления с примером, ведь мы будем заранее знать все доступные варианты и нам не нужен вариант "все остальные". + +## Замечания + +Сопоставление с примером наиболее полезно для сопоставления алгебраических типов, выраженных через [классы образцы](case-classes.html). +Scala также позволяет создавать образцы независимо от классов образцов, через использование метода `unapply` в [объектах экстракторах](extractor-objects.html). diff --git a/_ru/tour/polymorphic-methods.md b/_ru/tour/polymorphic-methods.md new file mode 100644 index 0000000000..a430aaf654 --- /dev/null +++ b/_ru/tour/polymorphic-methods.md @@ -0,0 +1,36 @@ +--- +layout: tour +title: Полиморфные методы + +discourse: true + +partof: scala-tour + +num: 28 +language: ru +next-page: type-inference +previous-page: implicit-conversions +prerequisite-knowledge: unified-types + +--- + +Также как и у обобщенных классов, у методов есть полиморфизм по типу, с таким же синтаксисом (параметр типа указывается в квадратных скобках сразу после названия метода). + +Вот пример: + +```tut +def listOfDuplicates[A](x: A, length: Int): List[A] = { + if (length < 1) + Nil + else + x :: listOfDuplicates(x, length - 1) +} +println(listOfDuplicates[Int](3, 4)) // List(3, 3, 3, 3) +println(listOfDuplicates("La", 8)) // List(La, La, La, La, La, La, La, La) +``` + +Метод `listOfDuplicates` принимает параметр типа `A` и параметры значений `x` и `length`. Значение `x` имеет тип `A`. Если `length < 1` мы возвращаем пустой список. В противном случае мы добавляем `x`к списку, которые возвращаем через рекурсивный вызовов. (Обратите внимание, что `::` означает добавление элемента слева к списку справа). + +В первом вызове метода мы явно указываем параметр типа, записывая `[Int]`. Поэтому первым аргументом должен быть `Int` и тип возвращаемого значения будет `List[Int]`. + +Во втором вызове показано, что вам не всегда нужно явно указывать параметр типа. Часто компилятор сам может вывести тип исходя из контекста или типа передаваемых аргументов. В этом варианте `"La"` - это `String`, поэтому компилятор знает, что `A` должен быть `String`. diff --git a/_ru/tour/regular-expression-patterns.md b/_ru/tour/regular-expression-patterns.md new file mode 100644 index 0000000000..32877a0106 --- /dev/null +++ b/_ru/tour/regular-expression-patterns.md @@ -0,0 +1,61 @@ +--- +layout: tour +title: Регулярные Выражения + +discourse: true + +partof: scala-tour + +num: 15 +language: ru +next-page: extractor-objects +previous-page: singleton-objects + +--- + +Регулярные выражения (Regular expression) - это специальный шаблон для поиска данных задаваемый в виде текстовой строки. Любая строка может быть преобразована в регулярное выражение методом `.r`. + +```tut +import scala.util.matching.Regex + +val numberPattern: Regex = "[0-9]".r + +numberPattern.findFirstMatchIn("awesomepassword") match { + case Some(_) => println("Password OK") + case None => println("Password must contain a number") +} +``` + +В приведенном выше примере `numberPattern` - это `Regex` (регулярное выражение), которое мы используем, чтобы убедиться, что пароль содержит число. + +Используя круглые скобки можно объединять сразу несколько групп регулярных выражений. + +```tut +import scala.util.matching.Regex + +val keyValPattern: Regex = "([0-9a-zA-Z-#() ]+): ([0-9a-zA-Z-#() ]+)".r + +val input: String = + """background-color: #A03300; + |background-image: url(img/header100.png); + |background-position: top center; + |background-repeat: repeat-x; + |background-size: 2160px 108px; + |margin: 0; + |height: 108px; + |width: 100%;""".stripMargin + +for (patternMatch <- keyValPattern.findAllMatchIn(input)) + println(s"key: ${patternMatch.group(1)} value: ${patternMatch.group(2)}") +``` +Здесь мы обработали сразу и ключи и значения строки. В каждой совпадении есть подгруппа совпадений. Вот как выглядит результат: +``` +key: background-color value: #A03300 +key: background-image value: url(img +key: background-position value: top center +key: background-repeat value: repeat-x +key: background-size value: 2160px 108px +key: margin value: 0 +key: height value: 108px +key: width value: 100 +``` diff --git a/_ru/tour/self-types.md b/_ru/tour/self-types.md new file mode 100644 index 0000000000..ccc896b0ec --- /dev/null +++ b/_ru/tour/self-types.md @@ -0,0 +1,40 @@ +--- +layout: tour +title: Самоописываемые типы + +discourse: true + +partof: scala-tour + +num: 25 +language: ru +next-page: implicit-parameters +previous-page: compound-types +topics: self-types +prerequisite-knowledge: nested-classes, mixin-class-composition + +--- +Самоописываемый тип(Self type) - это способ объявить, что трейт должен быть смешан с другим трейтом, даже если он не расширяет его напрямую. Что открывает доступ к членам зависимости без импортирования. + +Самоописываемый тип - это способ сузить тип `this` или другого идентификатора, который ссылается на `this`. Синтаксис похож на синтаксис обычной функции, но означает кое-что иное. + +Чтобы использовать самоописываемый тип в трейте напишите: идентификатор, тип другого трейта, который хотите добавить и `=>` (например, `someIdentifier: SomeOtherTrait =>`). +```tut +trait User { + def username: String +} + +trait Tweeter { + this: User => // переназначил this + def tweet(tweetText: String) = println(s"$username: $tweetText") +} + +class VerifiedTweeter(val username_ : String) extends Tweeter with User { // Мы добавили User потому этого требует Tweeter + def username = s"real $username_" +} + +val realBeyoncé = new VerifiedTweeter("Beyoncé") +realBeyoncé.tweet("Just spilled my glass of lemonade") // выведет "real Beyoncé: Just spilled my glass of lemonade" +``` + +Поскольку мы указали `this: User =>` в трейте `Tweeter`, теперь переменная `username` находится в пределах видимости для метода `tweet`. Это также означает что `VerifiedTweeter` при наследовании от `Tweeter` должен быть смешан с `User` (используя `with User`). diff --git a/_ru/tour/singleton-objects.md b/_ru/tour/singleton-objects.md new file mode 100644 index 0000000000..eab6688c75 --- /dev/null +++ b/_ru/tour/singleton-objects.md @@ -0,0 +1,107 @@ +--- +layout: tour +title: Объекты Одиночки + +discourse: true + +partof: scala-tour + +num: 13 +language: ru +next-page: regular-expression-patterns +previous-page: pattern-matching +prerequisite-knowledge: classes, methods, private-methods, packages, option +--- +Все объекты являются одиночками (Singleton Object) - то есть существуют в единственном экземпляре. Он создается лениво, когда на него ссылаются, также как ленивые значения (lazy val). + +На самом верхнем уровне объект является одиночкой. + +Как член класса или как локальная переменная, он ведет себя точно так же как ленивое значение (lazy val). +# Объявление одиночного объекта +Объект - является значением. Объявление объекта происходит схожим с классом образом, но используется ключевое слово `object`: +```tut +object Box +``` + +Вот пример объекта с методом: +``` +package logging + +object Logger { + def info(message: String): Unit = println(s"INFO: $message") +} +``` +Метод `info` может быть импортирован в любом месте программы. Создание подобных методов является распространенным вариантом использования одиночных объектов. + +Давайте посмотрим, как использовать `info` в другом пакете: + +``` +import logging.Logger.info + +class Project(name: String, daysToComplete: Int) + +class Test { + val project1 = new Project("TPS Reports", 1) + val project2 = new Project("Website redesign", 5) + info("Created projects") // Prints "INFO: Created projects" +} +``` + +Метод `info` виден благодаря указанию импорта `import logging.Logger.info`. + +Замечание: Если `object` не является объектом верхнего уровня, но вложен в другой класс или объект, то объект, как и любой другой член, "зависим от пути". + +## Объекты компаньоны + +Объект с тем же именем, что и класс называется _объект компаньон_ (companion object). И наоборот, класс является классом-компаньоном объекта. Класс или объект компаньон может получить доступ к приватным членам своего спутника. Используйте объект компаньон для методов и значений, которые не специфичны для экземпляров класса компаньона. +``` +import scala.math._ + +case class Circle(radius: Double) { + import Circle._ + def area: Double = calculateArea(radius) +} + +object Circle { + private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0) +} + +val circle1 = new Circle(5.0) + +circle1.area +``` + +Класс `Circle` имеет член `area`, который специфичен для каждого конкретного экземпляра, а метод `calculateArea` одиночного объекта `Circle`, доступен для каждого экземпляра класса `Circle`. + +Объект компаньон может также содержать методы создающие конкретные экземпляры класса спутника: +```tut +class Email(val username: String, val domainName: String) + +object Email { + def fromString(emailString: String): Option[Email] = { + emailString.split('@') match { + case Array(a, b) => Some(new Email(a, b)) + case _ => None + } + } +} + +val scalaCenterEmail = Email.fromString("scala.center@epfl.ch") +scalaCenterEmail match { + case Some(email) => println( + s"""Registered an email + |Username: ${email.username} + |Domain name: ${email.domainName} + """) + case None => println("Error: could not parse email") +} +``` +`object Email` содержит производящий метод `fromString`, который создает экземпляр `Email` из строки. Мы возвращаем результат как `Option[Email]` на случай возникновения ошибок парсинга. + +Примечание: Если у класса или объекта есть компаньон, они должны быть размещены в одном и том же файле. Чтобы задать компаньонов в REPL, либо определите их на той же строке, либо перейдите в режим `:paste`. + +## Примечания для Java-программистов ## + +`static` члены в Java смоделированы как обычные члены объекта компаньона в Scala. + +При использовании объекта компаньона из Java-кода, члены будут определены в сопутствующем классе компаньоне с `static` модификатором. Это называется _пробрасывание статики_. Такое происходит, даже если вы сами не определили класс компаньон. diff --git a/_ru/tour/tour-of-scala.md b/_ru/tour/tour-of-scala.md new file mode 100644 index 0000000000..a2e6cc805f --- /dev/null +++ b/_ru/tour/tour-of-scala.md @@ -0,0 +1,64 @@ +--- +layout: tour +title: Введение + +discourse: true + +partof: scala-tour + +num: 1 +language: ru +next-page: basics +--- + +## Добро пожаловать к обзору +Здесь вы увидите вводное описание наиболее часто используемых возможностей Scala. +Этот обзор предназначен для новичков в изучении языка. + +Это лишь небольшая экскурсия, а не полный курс освоения языка. Для глубокого погружения рекомендуем почитать [книги](/books.html) или воспользоваться курсами +[на других ресурсах](/learn.html). + +## Что такое Scala? +Scala - это современный мультипарадигмальный язык программирования, разработанный для выражения общих концепций программирования в простой, удобной и типобезопасной манере. Элегантно объединяя особенности объектно-ориентированных и функциональных языков. + +## Scala объектно ориентированный ## +Scala - это чистый объектно-ориентированный язык в том смысле, что [каждое значение - это объект](unified-types.html). Типы и поведение объектов описаны в [классах](classes.html) и [трейтах](traits.html)(характеристиках объектов). Классы расширяются за счет механизма наследования и гибкого [смешивания классов](mixin-class-composition.html), который используется для замены множественного наследования. + +## Scala функциональный ## +Scala также является функциональным языком в том смысле, что [каждая функция - это значение](unified-types.html). Scala предоставляет [легкий синтаксис](basics.html) для определения анонимных функций, поддерживает [функции высшего порядка](higher-order-functions.html), поддерживает [вложенные функции](nested-functions.html), а также [каррирование](multiple-parameter-lists.html). Scala имеют встроенную поддержку алгебраических типов данных, которые используются в большинстве функциональных языках программирования (эта поддержка базируется на механизме [сопоставления с примером](pattern-matching.html), где в качестве примера выступают [классы образцы](case-classes.html) ). [Объекты](singleton-objects.html) предоставляют удобный способ группировки функций, не входящих в класс. + +Вдобавок к этому, концепция сопоставления с примером логично переносится на [обработку XML-данных](https://github.com/scala/scala-xml/wiki/XML-Processing) используя в качестве примера [регулярные выражения](regular-expression-patterns.html), при поддержке функционала [объектов экстракторов](extractor-objects.html). Для еще большего удобства обработки данных представлена схема формирования запросов с использованием [for-выражения](for-comprehensions.html). Такие возможности делают Scala идеальным решением для разработки приложений по типу веб-сервисов. + +## Scala статически типизированный ## +Scala оснащен выразительной системой типов, которая обеспечивает безопасное и гармоничное использование абстракций. В частности, система типов поддерживает: + +* [обобщенные классы](generic-classes.html) +* [вариантность типов](variances.html) +* [верхние](upper-type-bounds.html) и [нижние](lower-type-bounds.html) границы типов +* [внутренние классы](inner-classes.html) и [члены абстрактного типа](abstract-type-members.html), как часть объектов +* [составные типы](compound-types.html) +* [самоописываемые типы](self-types.html) +* [неявные параметры](implicit-parameters.html) и [неявные преобразования](implicit-conversions.html) +* [полиморфные методы](polymorphic-methods.html) + +[Выведение типов](type-inference.html) означает, что разработчику не обязательно добавлять в код избыточную информацию о типах. +Такой функционал обеспечивает основу для безопасного переиспользования абстракций и типобезопасного развития программного обеспечения. + +## Scala расширяемый ## + +Зачастую разработка приложений для очень специфичных областей требует специфичных для этих областей языковых возможностей, либо отдельных специализированных языков программирования. Вместо этого Scala предлагает уникальные механизмы, для легкой модификации и расширения самого языка. + +Во многих случаях такое можно сделать без использования средств мета-программирования, таких как макросы. Например: + +* [Неявные классы](http://docs.scala-lang.org/overviews/core/implicit-classes.html) позволяют добавлять новые методы к уже существующим. +* [Интерполяция строк](/overviews/core/string-interpolation.html) позволяет добавлять обработку строк (расширяется разработчиком с помощью интерполяторов). + +## Scala совместимый + +Scala полностью совместим с популярной средой Java Runtime Environment (JRE). Взаимодействие с основным объектно-ориентированным языком программирования Java происходит максимально гладко. Новые функции Java, такие как SAM, [лямбды](higher-order-functions.html), [аннотации](annotations.html) и [дженерики](generic-classes.html), имеют прямые аналоги в Scala. + +Те функции Scala, которые не имеют аналогов в Java, такие как [параметры по умолчанию](default-parameter-values.html) и [именованные параметры](named-arguments.html), компилируются как можно ближе к Java. Scala имеет такую же компиляционную модель (отдельная компиляция, динамическая загрузка классов), как у Java, что позволяет получить доступ к тысячам уже существующих высококачественных библиотек. + +## Наслаждайтесь туром! + +Для продолжения знакомства предлагаю перейти на [следующую страницу](basics.html) нашего тура. \ No newline at end of file diff --git a/_ru/tour/traits.md b/_ru/tour/traits.md new file mode 100644 index 0000000000..4fea28e80f --- /dev/null +++ b/_ru/tour/traits.md @@ -0,0 +1,84 @@ +--- +layout: tour +title: Трейты + +discourse: true + +partof: scala-tour + +num: 5 +language: ru +next-page: tuples +previous-page: classes +topics: traits +prerequisite-knowledge: expressions, classes, generics, objects, companion-objects + +--- + +Трейты (Traits) используются чтоб обмениваться между классами информацией о структуре и полях. Они похожи на интерфейсы из Java 8. Классы и объекты могут расширять трейты, но трейты не могут быть созданы и поэтому не имеют параметров. + +## Объявление трейта +Минимальное объявление трейта - это просто ключевое слово `trait` и его имя: + +```tut +trait HairColor +``` + +Трейты наиболее полезны в качестве обобщенного типа с абстрактными методами. +```tut +trait Iterator[A] { + def hasNext: Boolean + def next(): A +} +``` + +При наследовании от трейта `Iterator[A]` требует указание типа `A` а также реализация методов `hasNext` и `next`. + +## Использование трейтов +Чтоб использовать трейты, необходимо наследовать класс от него используя ключевое слово `extends`. Затем необходимо реализовать все абстрактные члены трейта, используя ключевое слово `override`: +```tut +trait Iterator[A] { + def hasNext: Boolean + def next(): A +} + +class IntIterator(to: Int) extends Iterator[Int] { + private var current = 0 + override def hasNext: Boolean = current < to + override def next(): Int = { + if (hasNext) { + val t = current + current += 1 + t + } else 0 + } +} + + +val iterator = new IntIterator(10) +iterator.next() // вернет 0 +iterator.next() // вернет 1 +``` +Этот класс `IntIterator` использует параметр `to` в качестве верхней границы. Он наследуется от `Iterator[Int]`, что означает, что метод `next` должен возвращать Int. + +## Подтипы +Туда где требуется определенный тип трейта, мы можем передавать любой наследованный от требуемого трейта класс +```tut +import scala.collection.mutable.ArrayBuffer + +trait Pet { + val name: String +} + +class Cat(val name: String) extends Pet +class Dog(val name: String) extends Pet + +val dog = new Dog("Harry") +val cat = new Cat("Sally") + +val animals = ArrayBuffer.empty[Pet] +animals.append(dog) +animals.append(cat) +animals.foreach(pet => println(pet.name)) // выведет "Harry" и "Sally" +``` +У трейта `Pet` есть абстрактное поле `name`, которое реализовано в классах `Cat` and `Dog`. В последней строке мы вызываем `pet.name`, который должен быть реализован в любом подтипе унаследованным от трейта `Pet`. diff --git a/_ru/tour/tuples.md b/_ru/tour/tuples.md new file mode 100644 index 0000000000..094a76fbdf --- /dev/null +++ b/_ru/tour/tuples.md @@ -0,0 +1,95 @@ +--- +layout: tour +title: Кортежи + +discourse: true + +partof: scala-tour + +num: 6 +language: ru +next-page: mixin-class-composition +previous-page: traits +topics: tuples + +--- + +В Scala, кортеж (Тuple) это класс контейнер содержащий упорядоченный набор элементов различного типа. +Кортежи неизменяемы. + +Кортежи могут пригодиться, когда нам нужно вернуть сразу несколько значений из функции. + +Кортеж может быть создан как: + +```tut +val ingredient = ("Sugar" , 25):Tuple2[String, Int] +``` +Такая запись создает кортеж размерности 2, содержащий пару элементов String и Int. + +Кортежи в Скале - представлены серией классов: Tuple2, Tuple3 и т.д., до Tuple22. +Таким образом, создавая кортеж с n элементами (n лежащими между 2 и 22), Скала просто создает один из соответствующих классов, который параметризован типом входящих в состав элементов. + +В нашем примере, составляющие тип Tuple2[String, Int]. + +## Доступ к элементам + +Доступ к элементам кортежа осуществляется при помощи синтаксиса подчеркивания. +'tuple._n' дает n-ый элемент (столько, сколько существует элементов). + +```tut +println(ingredient._1) // Sugar + +println(ingredient._2) // 25 +``` + +## Распаковка данных кортежа + +Scala кортежи также поддерживают [распаковку](extractor-objects.html). + +```tut +val (name, quantity) = ingredient + +println(name) // Sugar + +println(quantity) // 25 +``` + +Распаковка данных кортежа может быть использована в [сопоставлении с примером](pattern-matching.html) + +```tut +val planetDistanceFromSun = List(("Mercury", 57.9), ("Venus", 108.2), ("Earth", 149.6 ), ("Mars", 227.9), ("Jupiter", 778.3)) + +planetDistanceFromSun.foreach{ tuple => { + + tuple match { + + case ("Mercury", distance) => println(s"Mercury is $distance millions km far from Sun") + + case p if(p._1 == "Venus") => println(s"Venus is ${p._2} millions km far from Sun") + + case p if(p._1 == "Earth") => println(s"Blue planet is ${p._2} millions km far from Sun") + + case _ => println("Too far....") + + } + + } + +} +``` + +Или в ['for' выражении](for-comprehensions.html). + +```tut +val numPairs = List((2, 5), (3, -7), (20, 56)) + +for ((a, b) <- numPairs) { + + println(a * b) + +} +``` + +Значение () типа Unit по свой сути совпадает со значением () типа Tuple0. Может быть только одно значение такого типа, так как в нём нет элементов. + +Иногда бывает трудно выбирать между кортежами и классами образцами. Как правило, классы образцы являются предпочтительным выбором, если класс-контейнер содержащий элементы сам по себе имеет значимый смысл. diff --git a/_ru/tour/type-inference.md b/_ru/tour/type-inference.md new file mode 100644 index 0000000000..3f936385fa --- /dev/null +++ b/_ru/tour/type-inference.md @@ -0,0 +1,75 @@ +--- +layout: tour +title: Выведение Типа + +discourse: true + +partof: scala-tour + +num: 29 +language: ru +next-page: operators +previous-page: polymorphic-methods +--- + +Компилятор Scala часто может вывести тип выражения, так что вам не нужно указывать его явным образом. + +## Не указывая тип + +```tut +val businessName = "Montreux Jazz Café" +``` +Компилятор может определить, что тип константы `businessName` является `String`. Аналогичным образом это работает и для методов: + +```tut +def squareOf(x: Int) = x * x +``` +Компилятор может определить, что возвращаемый тип является `Int`, поэтому явного указания типа не требуется. + +Для рекурсивных методов компилятор не в состоянии вывести тип. Вот программа, которая не скомпилируется по этой причине: + +```tut:fail +def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) +``` + +Также необязательно указывать параметры типа при вызове [полиморфных методов](polymorphic-methods.html) или [обобщенных классов](generic-classes.html). Компилятор Scala определит тип параметра из контекста и из типов фактически передаваемых параметров метода/конструктора. + +Вот два примера: + +```tut +case class MyPair[A, B](x: A, y: B); +val p = MyPair(1, "scala") // тип: MyPair[Int, String] + +def id[T](x: T) = x +val q = id(1) // тип: Int +``` + +Компилятор использует типы аргументов `MyPair` для определения типа `A` и `B`. Тоже самое для типа `x`. + +## Параметры + +Для параметров компилятор никогда не выводит тип. Однако, в некоторых случаях, он может вывести типы для параметров анонимной функции при передаче ее в качестве аргумента. + +```tut +Seq(1, 3, 4).map(x => x * 2) // List(2, 6, 8) +``` + +Параметр у map - `f: A => B` (функциональный параметр переводящий тип из A в B). Поскольку мы разместили целые числа в нашей последовательности `Seq`, компилятор знает, что элемент `A` является `Int` (т.е. `x` является целым числом). Поэтому компилятор может определить из выражения `x * 2`, что результат (`B`) является типом `Int`. + +## Когда _не следует_ полагаться на выведение типа + +Обычно считается, наиболее удобочитаемым объявить тип членов, которые открыты для публичного использования через API. Поэтому мы рекомендуем вам явно указывать тип для любых API, которые будут доступны пользователям вашего кода. + +Кроме того, выведение может иногда приводить к слишком специфичному типу. Предположим, мы напишем: + +```tut +var obj = null +``` + +Тогда мы не сможем далее сделать это переназначение: + +```tut:fail +obj = new AnyRef +``` + +Такое не будет компилироваться, потому что для `obj` предполагался тип `Null`. Поскольку единственным значением этого типа является `null`, то невозможно присвоить другое значение. \ No newline at end of file diff --git a/_ru/tour/unified-types.md b/_ru/tour/unified-types.md new file mode 100644 index 0000000000..521ff51754 --- /dev/null +++ b/_ru/tour/unified-types.md @@ -0,0 +1,82 @@ +--- +layout: tour +title: Единобразие типов + +discourse: true + +partof: scala-tour + +num: 3 +language: ru +next-page: classes +previous-page: basics +prerequisite-knowledge: classes, basics + +--- + +В Scala все значения имеют тип, включая числовые значения и функции. Диаграмма ниже иллюстрирует подмножество иерархии типов. + +Scala Type Hierarchy + +## Иерархия типов Scala ## + +[`Any`](http://www.scala-lang.org/api/2.12.1/scala/Any.html) это супертип всех типов, также называемый верхним типом. Он определяет несколько универсальных методов, таких как `equals`, `hashCode` и `toString`. У `Any` есть два прямых подкласса: `AnyVal` и `AnyRef`. + +`AnyVal` представляет числовые типы. Существует девять предварительно определенных числовых типов и они никогда не могут быть равны 'null': `Double`, `Float`, `Long`, `Int`, `Short`, `Byte`, `Char`, `Unit`, и `Boolean`. `Unit` - это числовой тип, который не содержит значимой информации (также обозначает пустое множество). Есть только один представитель типа `Unit`, который можно объявить вот так: `()`. Все функции должны возвращать что-то, поэтому иногда `Unit` полезный для возврата тип. + +`AnyRef` представляет ссылочные типы. Все типы, не относящиеся к "числовым типам", называются ссылочными типами. Каждый объявляемый пользователем тип в Scala является подтипом `AnyRef`. Если в Scala исходить из контекста среды исполнения Java, `AnyRef` соответствует `java.lang.Object`. + +Вот пример, демонстрирующий, что строки, целые числа, символы, логические значения и функции являются объектами, как и любой другой объект: + +```tut +val list: List[Any] = List( + "a string", + 732, // целое число + 'c', // символ + true, // логическое значение + () => "анонимная функция возвращающая строку" +) + +list.foreach(element => println(element)) +``` + +Объявляем переменную `list` типа `List[Any]`. Список инициализируется элементами различных типов, но все они являются экземпляром `scala.Any`, так что вы можете добавить их в список. + +Ниже приведен вывод программы: + +``` +a string +732 +c +true + +``` + +## Приведение типа +Числовые типы могут быть приведены следующим образом: +Scala Type Hierarchy + +Например: + +```tut +val x: Long = 987654321 +val y: Float = x // 9.8765434E8 (заметьте, что некоторая точность теряется в этом случае.) + +val face: Char = '☺' +val number: Int = face // 9786 +``` + +Приведение типа - однонаправленно. Следующий пример не скомпилируется: + +``` +val x: Long = 987654321 +val y: Float = x // 9.8765434E8 +val z: Long = y // обратно не подходит +``` + +Вы также можете приводить к своему подтипу. Об этом мы поговорим позже в ходе нашего обзора. + +## Nothing и Null +`Nothing` является подтипом всех типов, также называемым нижним типом. Нет значения, которое имеет тип `Nothing`. Обычно он используется чтоб дать сигнал о не вычислимости, например брошено исключение, выход из программы, бесконечное зацикливание (т.е. это тип выражения, которое не вычисляется). + +`Null` подтип всех ссылочных типов (т.е. любой подтип AnyRef). Он имеет одно значение, определяемое ключевым словом литерала `null`. `Null` предоставляется в основном для функциональной совместимости с другими языками JVM и почти никогда не должен использоваться в коде Scala. Об альтернативах `null` мы поговорим позднее. diff --git a/_ru/tour/upper-type-bounds.md b/_ru/tour/upper-type-bounds.md new file mode 100644 index 0000000000..7446261c1a --- /dev/null +++ b/_ru/tour/upper-type-bounds.md @@ -0,0 +1,54 @@ +--- +layout: tour +title: Верхнее Ограничение Типа + +discourse: true + +partof: scala-tour +categories: tour +num: 20 +language: ru +next-page: lower-type-bounds +previous-page: variances + +--- + +В Scala [параметры типа](generic-classes.html) и [члены абстрактного типа](abstract-type-members.html) могут быть ограничены определенными диапазонами. Такие диапазоны ограничивают конкретные значение типа и, возможно, предоставляют больше информации о членах таких типов. _Верхнее ограничение типа_ `T <: A` указывает на то что тип `T` относится к подтипу типа `A`. +Приведем пример, демонстрирующий верхнее ограничение для типа класса `PetContainer`: + +```tut +abstract class Animal { + def name: String +} + +abstract class Pet extends Animal {} + +class Cat extends Pet { + override def name: String = "Cat" +} + +class Dog extends Pet { + override def name: String = "Dog" +} + +class Lion extends Animal { + override def name: String = "Lion" +} + +class PetContainer[P <: Pet](p: P) { + def pet: P = p +} + +val dogContainer = new PetContainer[Dog](new Dog) +val catContainer = new PetContainer[Cat](new Cat) +``` + +```tut:fail +// это не скомпилируется +val lionContainer = new PetContainer[Lion](new Lion) +``` +Класс `PetContainer` принимает тип `P`, который должен быть подтипом `Pet`. `Dog` и `Cat` - это подтипы `Pet`, поэтому мы можем создать новые `PetContainer[Dog]` и `PetContainer[Cat]`. Однако, если мы попытаемся создать `PetContainer[Lion]`, то получим следующую ошибку: + +`type arguments [Lion] do not conform to class PetContainer's type parameter bounds [P <: Pet]` + +Это потому, что `Lion` не является подтипом `Pet`. diff --git a/_ru/tour/variances.md b/_ru/tour/variances.md new file mode 100644 index 0000000000..4b3a1d4a3f --- /dev/null +++ b/_ru/tour/variances.md @@ -0,0 +1,154 @@ +--- +layout: tour +title: Вариантность + +discourse: true + +partof: scala-tour + +num: 19 +language: ru +next-page: upper-type-bounds +previous-page: generic-classes + +--- + +Вариантность (Variances) - это указание определенной специфики взаимосвязи между связанными типам. Scala поддерживает вариантную аннотацию типов у [обобщенных классов](generic-classes.html), что позволяет им быть ковариантными, контрвариантными или инвариантными (если нет никакого указание на вариантность). Использование вариантности в системе типов позволяет устанавливать понятные взаимосвязи между сложными типами, в то время как отсутствие вариантности может ограничить повторное использование абстракции класса. + +```tut +class Foo[+A] // ковариантный класс +class Bar[-A] // контрвариантный класс +class Baz[A] // инвариантными класс +``` + +### Ковариантность + +Параметр типа `A` обобщенного класса можно сделать ковариантным с помощью аннотации `+A`. Для некоторого класса `List[+A]`, указание `A` в виде коварианта подразумевает, что для двух типов `A` и `B`, где `A` является подтипом `B`, `List[A]` представляет собой подтип `List[B]`. Что позволяет нам создавать очень полезные и интуитивно понятные взаимоотношения между типами с использованием обобщений (generics). + +Рассмотрим простую структуру классов: + +```tut +abstract class Animal { + def name: String +} +case class Cat(name: String) extends Animal +case class Dog(name: String) extends Animal +``` + +И `Cat` (кошка) и `Dog`(собака) являются подтипами `Animal`(животное). Стандартная библиотека Scala имеет обобщенный неизменяемый тип `List[+A]`, где параметр типа `A` является ковариантным. Это означает, что `List[Cat]` - это `List[Animal]`, а `List[Dog]` - это также `List[Animal]`. Интуитивно понятно, что список кошек и список собак - это список животных и вы должны быть в состоянии заменить любого из них на `List[Animal]`. + +В следующем примере метод `printAnimalNames` принимает в качестве аргумента список животных и выводит их имена в новой строке. Если бы `List[A]` не был ковариантным, последние два вызова метода не компилировались бы, что сильно ограничило бы полезность метода `printAnimalNames`. + +```tut +object CovarianceTest extends App { + def printAnimalNames(animals: List[Animal]): Unit = { + animals.foreach { animal => + println(animal.name) + } + } + + val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom")) + val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex")) + + printAnimalNames(cats) + // Whiskers + // Tom + + printAnimalNames(dogs) + // Fido + // Rex +} +``` + +### Контрвариантность + +Параметр типа `A` обобщенного класса можно сделать контрвариантным с помощью аннотации `-A`. Это создает схожее, но противоположное ковариантным, взаимоотношения между типом параметра и подтипами класса. То есть, для некого класса `Writer[-A]`, указание `A` контрвариантным подразумевает, что для двух типов `A` и `B` где `A` является подтипом `B`, `Writer[B]` является подтипом `Writer[A]`. + +Рассмотрим классы `Cat`, `Dog`, и `Animal`, описанные выше для следующего примера: + +```tut +abstract class Printer[-A] { + def print(value: A): Unit +} +``` + +`Printer[A]` - это простой класс, который знает, как распечатать некоторый тип `A`. Давайте определим подклассы для конкретных типов: + +```tut +class AnimalPrinter extends Printer[Animal] { + def print(animal: Animal): Unit = + println("The animal's name is: " + animal.name) +} + +class CatPrinter extends Printer[Cat] { + def print(cat: Cat): Unit = + println("The cat's name is: " + cat.name) +} +``` + +Если `Printer[Cat]` знает, как распечатать любой класс `Cat` в консоли, а `Printer[Animal]` знает, как распечатать любое `Animal` в консоли, то разумно если `Printer[Animal]` также знает, как распечатать любое `Cat`. Обратного отношения нет, потому что `Printer[Cat]` не знает, как распечатать любой `Animal` в консоли. Чтоб иметь возможность заменить `Printer[Animal]` на `Printer[Cat]`, необходимо `Printer[A]` сделать контрвариантным. + +```tut +object ContravarianceTest extends App { + val myCat: Cat = Cat("Boots") + + def printMyCat(printer: Printer[Cat]): Unit = { + printer.print(myCat) + } + + val catPrinter: Printer[Cat] = new CatPrinter + val animalPrinter: Printer[Animal] = new AnimalPrinter + + printMyCat(catPrinter) + printMyCat(animalPrinter) +} +``` + +Результатом работы этой программы будет: + +``` +The cat's name is: Boots +The animal's name is: Boots +``` + +### Инвариантность + +Обобщенные классы в Scala по умолчанию являются инвариантными. Это означает, что они не являются ни ковариантными, ни контрвариантными друг другу. В контексте следующего примера класс `Container` является инвариантным. Между `Container[Cat]` и `Container[Animal]`, нет ни прямой, ни обратной взаимосвязи. + +```tut +class Container[A](value: A) { + private var _value: A = value + def getValue: A = _value + def setValue(value: A): Unit = { + _value = value + } +} +``` + +Может показаться что `Container[Cat]` должен также являться и `Container[Animal]`, но позволить мутабельному обобщенному классу быть ковариантным было бы небезопасно. В данном примере очень важно, чтобы `Container` был инвариантным. Предположим, что `Container` на самом деле был ковариантным, что-то вроде этого могло случиться: + +``` +val catContainer: Container[Cat] = new Container(Cat("Felix")) +val animalContainer: Container[Animal] = catContainer +animalContainer.setValue(Dog("Spot")) +val cat: Cat = catContainer.getValue // Ой, мы бы закончили присвоением собаки к коту. +``` + +К счастью, компилятор остановит нас прежде, чем мы зайдем так далеко. + +### Другие Примеры + +Другим примером, который может помочь понять вариантность, является трейт `Function1[-T, +R]` из стандартной библиотеки Scala. `Function1` представляет собой функцию с одним параметром, где первый тип `T` представляет собой параметр типа, а второй тип `R` представляет собой тип результата. Функция `Function1` является контрвариантной в рамках типа принимаемого аргумента, а ковариантной - в рамках возвращаемого типа. Для этого примера мы будем использовать явное обозначение типа `A =>B` чтоб продемонстрировать `Function1[A, B]`. + +Рассмотрим схожий пример `Cat`, `Dog`, `Animal` в той же взаимосвязи что и раньше, плюс следующее: + +```tut +abstract class SmallAnimal extends Animal +case class Mouse(name: String) extends SmallAnimal +``` + +Предположим, мы работаем с функциями, которые принимают типы животных и возвращают типы еды, которую они едят. Если мы хотим `Cat => SmallAnimal` (потому что кошки едят маленьких животных), но вместо этого мы получим функцию `Animal => Mouse`, то наша программа все равно будет работать. Интуитивно функция `Animal => Mouse` все равно будет принимать `Cat` в качестве аргумента, тк `Cat` является `Animal`, и возвращать `Mouse` - который также является и `SmallAnimal`. Поскольку мы можем безопасно заменить первое вторым, можно сказать, что `Animal => Mouse` аналогично `Cat => SmallAnimal`. + +### Сравнение с другими языками + +В языках, похожих на Scala, разные способы поддержи вариантности. Например, указания вариантности в Scala очень похожи на то, как это делается в C#, где такие указания добавляются при объявлении абстракции класса (вариантность при объявлении). Однако в Java, указание вариантности задается непосредственно при использовании абстракции класса (вариантность при использовании).