From 8647ee5f22c8e54fefba6222e7ad477ed62c8b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9A=D0=BE=D1=80=D1=81?= =?UTF-8?q?=D0=B0=D0=BA=D0=BE=D0=B2?= Date: Sat, 15 Jul 2023 10:50:04 +0300 Subject: [PATCH 1/5] Update compound-types.md in russian --- _ru/tour/compound-types.md | 71 +++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/_ru/tour/compound-types.md b/_ru/tour/compound-types.md index 5d0a49aa0e..876bbaf42a 100644 --- a/_ru/tour/compound-types.md +++ b/_ru/tour/compound-types.md @@ -8,13 +8,19 @@ next-page: self-types previous-page: abstract-type-members --- -Иногда необходимо выразить, то что тип объекта является подтипом нескольких других типов. В Scala это можно выразить с помощью *составных типов*, которые являются объединением нескольких типов объектов. +Иногда необходимо выразить, то что тип объекта является подтипом нескольких других типов. + +В Scala это можно выразить с помощью _типов пересечений_ (или _составных типов_ в Scala 2), +которые являются объединением нескольких типов объектов. Предположим, у нас есть два трейта: `Cloneable` и `Resetable`: +{% tabs compound-types_1 class=tabs-scala-version %} +{% tab 'Scala 2' for=compound-types_1 %} + ```scala mdoc trait Cloneable extends java.lang.Cloneable { - override def clone(): Cloneable = { + override def clone(): Cloneable = { // создает публичный метод 'clone' super.clone().asInstanceOf[Cloneable] } } @@ -23,9 +29,26 @@ trait Resetable { } ``` -Теперь предположим, что мы хотим написать функцию `cloneAndReset`, которая берет объект, клонирует его и сбрасывает (Reset) состояние исходного объекта: +{% endtab %} +{% tab 'Scala 3' for=compound-types_1 %} +```scala +trait Cloneable extends java.lang.Cloneable: + override def clone(): Cloneable = // создает публичный метод 'clone' + super.clone().asInstanceOf[Cloneable] +trait Resetable: + def reset: Unit ``` + +{% endtab %} +{% endtabs %} + +Теперь предположим, что мы хотим написать функцию `cloneAndReset`, которая берет объект, клонирует его и сбрасывает (Reset) состояние исходного объекта: + +{% tabs compound-types_2 class=tabs-scala-version %} +{% tab 'Scala 2' for=compound-types_2 %} + +```scala mdoc:fail def cloneAndReset(obj: ?): Cloneable = { val cloned = obj.clone() obj.reset @@ -33,17 +56,49 @@ def cloneAndReset(obj: ?): Cloneable = { } ``` -Возникает вопрос, какой тип параметр `obj` должна принимать наша объединённая функция. Если это `Cloneable`, то объект может использовать метод `clone`, но не `reset`; если это `Resetable` мы можем использовать метод `reset`, но нет операции `clone`. Чтобы избежать приведения типа в такой ситуации, мы можем указать, что тип `obj` является и `Cloneable`, и `Resetable`. Этот совместный тип в Scala записывается как: `Cloneable with Resetable`. +{% endtab %} +{% tab 'Scala 3' for=compound-types_2 %} + +```scala +def cloneAndReset(obj: ?): Cloneable = + val cloned = obj.clone() + obj.reset + cloned +``` + +{% endtab %} +{% endtabs %} + +Возникает вопрос, какой тип параметра `obj` должна принимать наша объединённая функция. Если это `Cloneable`, то объект может использовать метод `clone`, но не `reset`; если это `Resetable` мы можем использовать метод `reset`, но нет операции `clone`. Чтобы избежать приведения типа в такой ситуации, мы можем указать, что тип `obj` является и `Cloneable`, и `Resetable`. + +{% tabs compound-types_3 class=tabs-scala-version %} +{% tab 'Scala 2' for=compound-types_3 %} +Этот совместный тип в Scala записывается как: `Cloneable with Resetable`. Вот обновленная функция: -``` +```scala mdoc:fail def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { //... } ``` -Составные типы могут состоять из нескольких типов объектов, и они могут содержать единый доработанный объект, в котором будут доработаны характеристики существующих членов объекта. -Общая форма записи: `A with B with C ... { доработанный объект }` +Обратите внимание, что у вас может быть более двух типов: `A with B with C with ...`. +Это означает то же самое, что и: `(...(A with B) with C) with ... )` + +{% endtab %} +{% tab 'Scala 3' for=compound-types_3 %} +Этот совместный тип в Scala записывается как: `Cloneable & Resetable`. + +Вот обновленная функция: + +```scala +def cloneAndReset(obj: Cloneable & Resetable): Cloneable = { + //... +} +``` -Пример использования таких доработок приведен на странице об [объединении классов с примесями](mixin-class-composition.html). +Обратите внимание, что у вас может быть более двух типов: `A & B & C & ...`. +`&` является ассоциативным, поэтому скобки могут быть добавлены вокруг любой части без изменения значения. +{% endtab %} +{% endtabs %} From 58930b4d3aaa1a7042e31ed99f9459befd1a5d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9A=D0=BE=D1=80=D1=81?= =?UTF-8?q?=D0=B0=D0=BA=D0=BE=D0=B2?= Date: Tue, 1 Aug 2023 09:01:41 +0300 Subject: [PATCH 2/5] Add tabs in ru tour --- _ru/tour/polymorphic-methods.md | 24 ++++++++++++++++++++++-- _ru/tour/self-types.md | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) 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 %} From 1aac227c86a3d0b8a81402c71973f4f6837170a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9A=D0=BE=D1=80=D1=81?= =?UTF-8?q?=D0=B0=D0=BA=D0=BE=D0=B2?= Date: Tue, 1 Aug 2023 09:06:06 +0300 Subject: [PATCH 3/5] Add tabs in ru tour --- _ru/tour/type-inference.md | 53 +++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) 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`, то невозможно присвоить другое значение. From 750e37b3977629c3755409dfa43dd20f469c1f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9A=D0=BE=D1=80=D1=81?= =?UTF-8?q?=D0=B0=D0=BA=D0=BE=D0=B2?= Date: Tue, 1 Aug 2023 09:19:06 +0300 Subject: [PATCH 4/5] Add tabs in ru tour --- _ru/tour/by-name-parameters.md | 36 ++++++++++++++- _ru/tour/operators.md | 80 ++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/_ru/tour/by-name-parameters.md b/_ru/tour/by-name-parameters.md index 3be9478fe8..c39a2f3883 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 and 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`. From bd3bf5e9708cb27962229ed3dc41cb407903d479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9A=D0=BE=D1=80=D1=81?= =?UTF-8?q?=D0=B0=D0=BA=D0=BE=D0=B2?= Date: Tue, 1 Aug 2023 09:28:45 +0300 Subject: [PATCH 5/5] Add tabs in ru tour --- _ru/tour/annotations.md | 163 +++++++++++++++++++++++++++++---- _ru/tour/by-name-parameters.md | 2 +- 2 files changed, 144 insertions(+), 21 deletions(-) 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 c39a2f3883..06c9a32063 100644 --- a/_ru/tour/by-name-parameters.md +++ b/_ru/tour/by-name-parameters.md @@ -11,7 +11,7 @@ previous-page: operators _Вызов параметров по имени_ - это когда значение параметра вычисляется только в момент вызова параметра. Этот способ противоположен _вызову по значению_. Чтоб вызов параметра был по имени, необходимо просто указать `=>` перед его типом. {% tabs by-name-parameters_1 %} -{% tab 'Scala 2 and 3' for=by-name-parameters_1 %} +{% tab 'Scala 2 и 3' for=by-name-parameters_1 %} ```scala mdoc def calculate(input: => Int) = input * 37