diff --git a/_ru/tour/annotations.md b/_ru/tour/annotations.md index 46718f2d77..0af144139d 100644 --- a/_ru/tour/annotations.md +++ b/_ru/tour/annotations.md @@ -9,21 +9,44 @@ previous-page: by-name-parameters --- Аннотации используются для передачи метаданных при объявлении. Например, аннотация `@deprecated` перед объявлением метода, заставит компилятор вывести предупреждение, если этот метод будет использован. -``` + +{% tabs annotations_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_1 %} + +```scala mdoc:fail object DeprecationDemo extends App { @deprecated("deprecation message", "release # which deprecates method") def hello = "hola" - hello + hello } ``` + +{% endtab %} +{% tab 'Scala 3' for=annotations_1 %} + +```scala +object DeprecationDemo extends App: + @deprecated("deprecation message", "release # which deprecates method") + def hello = "hola" + + hello +``` + +{% endtab %} +{% endtabs %} + Такой код скомпилируется, но компилятор выдаст предупреждение: "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). Хвостовая рекурсия помогает держать потребление памяти на постоянном уровне. Вот как она используется в методе, который вычисляет факториал: +{% tabs annotations_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_2 %} + ```scala mdoc import scala.annotation.tailrec @@ -36,8 +59,30 @@ def factorial(x: Int): Int = { factorialHelper(x, 1) } ``` -Метод `factorialHelper` имеет аннотацию `@tailrec`, которая гарантирует, что метод действительно является хвостовой рекурсией. Если бы мы изменили реализацию `factorialHelper` так как указано далее, то компиляция бы провалилась: + +{% endtab %} +{% tab 'Scala 3' for=annotations_2 %} + +```scala +import scala.annotation.tailrec + +def factorial(x: Int): Int = + + @tailrec + def factorialHelper(x: Int, accumulator: Int): Int = + if x == 1 then accumulator else factorialHelper(x - 1, accumulator * x) + factorialHelper(x, 1) ``` + +{% endtab %} +{% endtabs %} + +Метод `factorialHelper` имеет аннотацию `@tailrec`, которая гарантирует, что метод действительно является хвостовой рекурсией. Если бы мы изменили реализацию `factorialHelper` так как указано далее, то компиляция бы провалилась: + +{% tabs annotations_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_3 %} + +```scala mdoc:fail import scala.annotation.tailrec def factorial(x: Int): Int = { @@ -48,77 +93,155 @@ def factorial(x: Int): Int = { factorialHelper(x) } ``` -Мы бы получили сообщение "Recursive call not in tail position"(Рекурсивный вызов не в хвостовой позиции). +{% endtab %} +{% tab 'Scala 3' for=annotations_3 %} + +```scala +import scala.annotation.tailrec + +def factorial(x: Int): Int = + @tailrec + def factorialHelper(x: Int): Int = + if x == 1 then 1 else x * factorialHelper(x - 1) + factorialHelper(x) +``` + +{% endtab %} +{% endtabs %} + +Мы бы получили сообщение "Recursive call not in tail position"(Рекурсивный вызов не в хвостовой позиции). ## Аннотации, влияющие на генерацию кода + +{% tabs annotations_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=annotations_4 %} + Некоторые аннотации типа `@inline` влияют на сгенерированный код (т.е. в результате сам код вашего jar-файл может отличаться). Такая аннотация означает вставку всего кода в тело метода вместо вызова. Полученный байт-код длиннее, но, надеюсь, работает быстрее. Использование аннотации `@inline` не гарантирует, что метод будет встроен, но заставит компилятор сделать это, если и только если будут соблюдены некоторые разумные требования к размеру сгенерированного кода. -### Java аннотации ### +{% endtab %} +{% tab 'Scala 3' for=annotations_4 %} + +Некоторые аннотации типа `@main` влияют на сгенерированный код (т.е. в результате сам код вашего jar-файл может отличаться). +Аннотация `@main` к методу создает исполняемую программу, которая вызывает метод как точку входа. + +{% endtab %} +{% endtabs %} + +### Java аннотации + Есть некоторые отличия синтаксиса аннотаций, если пишется Scala код, который взаимодействует с Java. **Примечание:**Убедитесь, что вы используете опцию `-target:jvm-1.8` с аннотациями Java. Java имеет определяемые пользователем метаданные в виде [аннотаций](https://docs.oracle.com/javase/tutorial/java/annotations/). Ключевой особенностью аннотаций является то, что они задаются в виде пар ключ-значение для инициализации своих элементов. Например, если нам нужна аннотация для отслеживания источника какого-то класса, мы можем определить её как -``` +{% tabs annotations_5 %} +{% tab 'Java' for=annotations_5 %} + +```java @interface Source { - public String URL(); + public String url(); public String mail(); } ``` +{% endtab %} +{% endtabs %} + А затем использовать следующим образом -``` -@Source(URL = "https://coders.com/", +{% tabs annotations_6 %} +{% tab 'Java' for=annotations_6 %} + +```java +@Source(url = "https://coders.com/", mail = "support@coders.com") -public class MyClass extends HisClass ... +public class MyJavaClass extends TheirClass ... ``` +{% endtab %} +{% endtabs %} + Использование аннотации в Scala похоже на вызов конструктора. Для создания экземпляра из Java аннотации необходимо использовать именованные аргументы: -``` -@Source(URL = "https://coders.com/", +{% tabs annotations_7 %} +{% tab 'Scala 2 и 3' for=annotations_7 %} + +```scala +@Source(url = "https://coders.com/", mail = "support@coders.com") class MyScalaClass ... ``` +{% endtab %} +{% endtabs %} + Этот синтаксис достаточно перегруженный, если аннотация содержит только один элемент (без значения по умолчанию), поэтому, если имя указано как `value`, оно может быть применено в Java с помощью конструктора-подобного синтаксиса: -``` +{% tabs annotations_8 %} +{% tab 'Java' for=annotations_8 %} + +```java @interface SourceURL { public String value(); public String mail() default ""; } ``` +{% endtab %} +{% endtabs %} + А затем можно использовать следующим образом -``` +{% tabs annotations_9 %} +{% tab 'Java' for=annotations_9 %} + +```java @SourceURL("https://coders.com/") -public class MyClass extends HisClass ... +public class MyJavaClass extends TheirClass ... ``` +{% endtab %} +{% endtabs %} + В этом случае Scala предоставляет такую же возможность -``` +{% tabs annotations_10 %} +{% tab 'Scala 2 и 3' for=annotations_10 %} + +```scala @SourceURL("https://coders.com/") class MyScalaClass ... ``` +{% endtab %} +{% endtabs %} + Элемент `mail` был указан со значением по умолчанию, поэтому нам не нужно явно указывать его значение. Мы не можем смешивать эти два стиля в Java: -``` +{% tabs annotations_11 %} +{% tab 'Java' for=annotations_11 %} + +```java @SourceURL(value = "https://coders.com/", mail = "support@coders.com") -public class MyClass extends HisClass ... +public class MyJavaClass extends TheirClass ... ``` +{% endtab %} +{% endtabs %} + Scala обеспечивает большую гибкость в этом отношении -``` +{% tabs annotations_12 %} +{% tab 'Scala 2 и 3' for=annotations_12 %} + +```scala @SourceURL("https://coders.com/", mail = "support@coders.com") - class MyScalaClass ... +class MyScalaClass ... ``` + +{% endtab %} +{% endtabs %} diff --git a/_ru/tour/by-name-parameters.md b/_ru/tour/by-name-parameters.md index 3be9478fe8..06c9a32063 100644 --- a/_ru/tour/by-name-parameters.md +++ b/_ru/tour/by-name-parameters.md @@ -9,13 +9,24 @@ previous-page: operators --- _Вызов параметров по имени_ - это когда значение параметра вычисляется только в момент вызова параметра. Этот способ противоположен _вызову по значению_. Чтоб вызов параметра был по имени, необходимо просто указать `=>` перед его типом. + +{% tabs by-name-parameters_1 %} +{% tab 'Scala 2 и 3' for=by-name-parameters_1 %} + ```scala mdoc def calculate(input: => Int) = input * 37 ``` + +{% endtab %} +{% endtabs %} + Преимущество вызова параметров по имени заключается в том, что они не вычисляются если не используются в теле функции. С другой стороны плюсы вызова параметров по значению в том, что они вычисляются только один раз. Вот пример того, как мы можем реализовать условный цикл: +{% tabs by-name-parameters_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=by-name-parameters_2 %} + ```scala mdoc def whileLoop(condition: => Boolean)(body: => Unit): Unit = if (condition) { @@ -30,8 +41,29 @@ whileLoop (i > 0) { i -= 1 } // выведет 2 1 ``` -Метод `whileLoop` использует несколько списков параметров - условие и тело цикла. Если `condition` является верным, выполняется `body`, а затем выполняется рекурсивный вызов whileLoop. Если `condition` является ложным, то тело никогда не вычисляется, тк у нас стоит `=>` перед типом `body`. + +{% endtab %} +{% tab 'Scala 3' for=by-name-parameters_2 %} + +```scala +def whileLoop(condition: => Boolean)(body: => Unit): Unit = + if condition then + body + whileLoop(condition)(body) + +var i = 2 + +whileLoop (i > 0) { + println(i) + i -= 1 +} // выведет 2 1 +``` + +{% endtab %} +{% endtabs %} + +Метод `whileLoop` использует несколько списков параметров - условие и тело цикла. Если `condition` является верным, выполняется `body`, а затем выполняется рекурсивный вызов whileLoop. Если `condition` является ложным, то тело никогда не вычисляется, тк у нас стоит `=>` перед типом `body`. Теперь, когда мы передаем `i > 0` как наше условие `condition` и `println(i); i-= 1` как тело `body`, код ведет себя также как обычный цикл в большинстве языков программирования. -Такая возможность откладывать вычисления параметра до его использования может помочь повысить производительность, отсекая не нужные вычисления при определенных условиях. +Такая возможность откладывать вычисления параметра до его использования может помочь повысить производительность, отсекая не нужные вычисления при определенных условиях. diff --git a/_ru/tour/operators.md b/_ru/tour/operators.md index dd80f7905d..c8ec47bb3e 100644 --- a/_ru/tour/operators.md +++ b/_ru/tour/operators.md @@ -8,18 +8,38 @@ next-page: by-name-parameters previous-page: type-inference prerequisite-knowledge: case-classes --- + В Скале операторы - это обычные методы. В качестве _инфиксного оператора_ может быть использован любой метод с одним параметром. Например, `+` может вызываться с использованием точки: + +{% tabs operators_1 %} +{% tab 'Scala 2 и 3' for=operators_1 %} + ``` 10.+(1) ``` +{% endtab %} +{% endtabs %} + Однако легче воспринимать код, когда такие методы записаны как инфиксный оператор: + +{% tabs operators_2 %} +{% tab 'Scala 2 и 3' for=operators_2 %} + ``` 10 + 1 ``` +{% endtab %} +{% endtabs %} + ## Создание и использование операторов + В качестве оператора можно использовать любой допустимый символ. Включая имена на подобии `add` или символ (символы) типа `+`. + +{% tabs operators_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=operators_3 %} + ```scala mdoc case class Vec(x: Double, y: Double) { def +(that: Vec) = Vec(this.x + that.x, this.y + that.y) @@ -32,8 +52,30 @@ val vector3 = vector1 + vector2 vector3.x // 3.0 vector3.y // 3.0 ``` + +{% endtab %} +{% tab 'Scala 3' for=operators_3 %} + +```scala +case class Vec(x: Double, y: Double): + def +(that: Vec) = 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 +``` + +{% endtab %} +{% endtabs %} + У класса Vec есть метод `+`, который мы использовали для добавления `vector1` и `vector2`. Используя круглые скобки, можно строить сложные выражения с читаемым синтаксисом. Пример создания класса `MyBool`, которое включает в себя методы `and` и `or` +{% tabs operators_4 class=tabs-scala-version %} +{% tab 'Scala 2' for=operators_4 %} + ```scala mdoc case class MyBool(x: Boolean) { def and(that: MyBool): MyBool = if (x) that else this @@ -42,17 +84,38 @@ case class MyBool(x: Boolean) { } ``` +{% endtab %} +{% tab 'Scala 3' for=operators_4 %} + +```scala +case class MyBool(x: Boolean): + def and(that: MyBool): MyBool = if x then that else this + def or(that: MyBool): MyBool = if x then this else that + def negate: MyBool = MyBool(!x) +``` + +{% endtab %} +{% endtabs %} + Теперь можно использовать операторы `and` и `or` в качестве инфиксных операторов: +{% tabs operators_5 %} +{% tab 'Scala 2 и 3' for=operators_5 %} + ```scala mdoc def not(x: MyBool) = x.negate def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) ``` +{% endtab %} +{% endtabs %} + Это помогает сделать объявление `xor` более читабельным. ## Порядок очередности + Когда выражение использует несколько операторов, операторы оцениваются на основе приоритета первого символа. Таблица приоритетов символов: + ``` (символы которых нет снизу) * / % @@ -65,12 +128,29 @@ def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) | (буквы, $, _) ``` + Такой приоритет распространяется на любые функции, которые вы задаете. Например, следующее выражение: + +{% tabs operators_7 %} +{% tab 'Scala 2 и 3' for=operators_7 %} + ``` a + b ^? c ?^ d less a ==> b | c ``` + +{% endtab %} +{% endtabs %} + эквивалентно + +{% tabs operators_8 %} +{% tab 'Scala 2 и 3' for=operators_8 %} + ``` ((a + b) ^? (c ?^ d)) less ((a ==> b) | c) ``` + +{% endtab %} +{% endtabs %} + `?^` имеет высший приоритет, потому что начинается с символа `?`. Второй по старшинству приоритет имеет `+`, за которым следуют `==>`, `^?`, `|`, и `less`. diff --git a/_ru/tour/polymorphic-methods.md b/_ru/tour/polymorphic-methods.md index 9d54223100..115cec0d94 100644 --- a/_ru/tour/polymorphic-methods.md +++ b/_ru/tour/polymorphic-methods.md @@ -9,10 +9,13 @@ previous-page: implicit-conversions prerequisite-knowledge: unified-types --- -Также как и у обобщенных классов, у методов есть полиморфизм по типу, с таким же синтаксисом (параметр типа указывается в квадратных скобках сразу после названия метода). +Также как и у обобщенных классов, у методов есть полиморфизм по типу, с таким же синтаксисом (параметр типа указывается в квадратных скобках сразу после названия метода). Вот пример: +{% tabs polymorphic-methods_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=polymorphic-methods_1 %} + ```scala mdoc def listOfDuplicates[A](x: A, length: Int): List[A] = { if (length < 1) @@ -24,8 +27,25 @@ println(listOfDuplicates[Int](3, 4)) // List(3, 3, 3, 3) println(listOfDuplicates("La", 8)) // List(La, La, La, La, La, La, La, La) ``` +{% endtab %} +{% tab 'Scala 3' for=polymorphic-methods_1 %} + +```scala +def listOfDuplicates[A](x: A, length: Int): List[A] = + if length < 1 then + 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) +``` + +{% endtab %} +{% endtabs %} + Метод `listOfDuplicates` принимает параметр типа `A` и параметры значений `x` и `length`. Значение `x` имеет тип `A`. Если `length < 1` мы возвращаем пустой список. В противном случае мы добавляем `x`к списку, которые возвращаем через рекурсивный вызовов. (Обратите внимание, что `::` означает добавление элемента слева к списку справа). В первом вызове метода мы явно указываем параметр типа, записывая `[Int]`. Поэтому первым аргументом должен быть `Int` и тип возвращаемого значения будет `List[Int]`. -Во втором вызове показано, что вам не всегда нужно явно указывать параметр типа. Часто компилятор сам может вывести тип исходя из контекста или типа передаваемых аргументов. В этом варианте `"La"` - это `String`, поэтому компилятор знает, что `A` должен быть `String`. +Во втором вызове показано, что вам не всегда нужно явно указывать параметр типа. Часто компилятор сам может вывести тип исходя из контекста или типа передаваемых аргументов. В этом варианте `"La"` - это `String`, поэтому компилятор знает, что `A` должен быть `String`. diff --git a/_ru/tour/self-types.md b/_ru/tour/self-types.md index 5ca4c91b22..06597bfe70 100644 --- a/_ru/tour/self-types.md +++ b/_ru/tour/self-types.md @@ -9,11 +9,16 @@ previous-page: compound-types topics: self-types prerequisite-knowledge: nested-classes, mixin-class-composition --- -Самоописываемый тип(Self type) - это способ объявить, что трейт должен быть смешан с другим трейтом, даже если он не расширяет его напрямую. Что открывает доступ к членам зависимости без импортирования. + +Самоописываемый тип (Self type) - это способ объявить, что трейт должен быть смешан с другим трейтом, даже если он не расширяет его напрямую. Что открывает доступ к членам зависимости без импортирования. Самоописываемый тип - это способ сузить тип `this` или другого идентификатора, который ссылается на `this`. Синтаксис похож на синтаксис обычной функции, но означает кое-что иное. Чтобы использовать самоописываемый тип в трейте напишите: идентификатор, тип другого трейта, который хотите добавить и `=>` (например, `someIdentifier: SomeOtherTrait =>`). + +{% tabs self-types_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=self-types_1 %} + ```scala mdoc trait User { def username: String @@ -25,7 +30,7 @@ trait Tweeter { } class VerifiedTweeter(val username_ : String) extends Tweeter with User { // Мы добавили User потому этого требует Tweeter - def username = s"real $username_" + def username = s"real $username_" } val realBeyoncé = new VerifiedTweeter("Beyoncé") @@ -33,3 +38,26 @@ realBeyoncé.tweet("Just spilled my glass of lemonade") // выведет "real ``` Поскольку мы указали `this: User =>` в трейте `Tweeter`, теперь переменная `username` находится в пределах видимости для метода `tweet`. Это также означает что `VerifiedTweeter` при наследовании от `Tweeter` должен быть смешан с `User` (используя `with User`). + +{% endtab %} +{% tab 'Scala 3' for=self-types_1 %} + +```scala +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, User: // Мы добавили User потому этого требует Tweeter + def username = s"real $username_" + +val realBeyoncé = 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` (используя `, User`). + +{% endtab %} +{% endtabs %} diff --git a/_ru/tour/type-inference.md b/_ru/tour/type-inference.md index 6709acf411..52f0836467 100644 --- a/_ru/tour/type-inference.md +++ b/_ru/tour/type-inference.md @@ -12,26 +12,56 @@ previous-page: polymorphic-methods ## Не указывая тип +{% tabs type-inference_1 %} +{% tab 'Scala 2 и 3' for=type-inference_1 %} + ```scala mdoc val businessName = "Montreux Jazz Café" ``` + +{% endtab %} +{% endtabs %} + Компилятор может определить, что тип константы `businessName` является `String`. Аналогичным образом это работает и для методов: +{% tabs type-inference_2 %} +{% tab 'Scala 2 и 3' for=type-inference_2 %} + ```scala mdoc def squareOf(x: Int) = x * x ``` + +{% endtab %} +{% endtabs %} + Компилятор может определить, что возвращаемый тип является `Int`, поэтому явного указания типа не требуется. Для рекурсивных методов компилятор не в состоянии вывести тип. Вот программа, которая не скомпилируется по этой причине: +{% tabs type-inference_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=type-inference_3 %} + ```scala mdoc:fail def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) ``` +{% endtab %} +{% tab 'Scala 3' for=type-inference_3 %} + +```scala +def fac(n: Int) = if n == 0 then 1 else n * fac(n - 1) +``` + +{% endtab %} +{% endtabs %} + Также необязательно указывать параметры типа при вызове [полиморфных методов](polymorphic-methods.html) или [обобщенных классов](generic-classes.html). Компилятор Scala определит тип параметра из контекста и из типов фактически передаваемых параметров метода/конструктора. Вот два примера: +{% tabs type-inference_4 %} +{% tab 'Scala 2 и 3' for=type-inference_4 %} + ```scala mdoc case class MyPair[A, B](x: A, y: B) val p = MyPair(1, "scala") // тип: MyPair[Int, String] @@ -40,32 +70,53 @@ def id[T](x: T) = x val q = id(1) // тип: Int ``` +{% endtab %} +{% endtabs %} + Компилятор использует типы аргументов `MyPair` для определения типа `A` и `B`. Тоже самое для типа `x`. ## Параметры Для параметров компилятор никогда не выводит тип. Однако, в некоторых случаях, он может вывести типы для параметров анонимной функции при передаче ее в качестве аргумента. +{% tabs type-inference_5 %} +{% tab 'Scala 2 и 3' for=type-inference_5 %} + ```scala mdoc Seq(1, 3, 4).map(x => x * 2) // List(2, 6, 8) ``` +{% endtab %} +{% endtabs %} + Параметр у map - `f: A => B` (функциональный параметр переводящий тип из A в B). Поскольку мы разместили целые числа в нашей последовательности `Seq`, компилятор знает, что элемент `A` является `Int` (т.е. `x` является целым числом). Поэтому компилятор может определить из выражения `x * 2`, что результат (`B`) является типом `Int`. ## Когда _не следует_ полагаться на выведение типа Обычно считается, наиболее удобочитаемым объявить тип членов, которые открыты для публичного использования через API. Поэтому мы рекомендуем вам явно указывать тип для любых API, которые будут доступны пользователям вашего кода. -Кроме того, выведение может иногда приводить к слишком специфичному типу. Предположим, мы напишем: +Кроме того, выведение может иногда приводить к слишком специфичному типу. Предположим, мы напишем: + +{% tabs type-inference_6 %} +{% tab 'Scala 2 и 3' for=type-inference_6 %} ```scala var obj = null ``` +{% endtab %} +{% endtabs %} + Тогда мы не сможем далее сделать это переназначение: +{% tabs type-inference_7 %} +{% tab 'Scala 2 и 3' for=type-inference_7 %} + ```scala mdoc:fail obj = new AnyRef ``` +{% endtab %} +{% endtabs %} + Такое не будет компилироваться, потому что для `obj` предполагался тип `Null`. Поскольку единственным значением этого типа является `null`, то невозможно присвоить другое значение.