diff --git a/_ru/tour/multiple-parameter-lists.md b/_ru/tour/multiple-parameter-lists.md index c93a6e31d1..99f84700d5 100644 --- a/_ru/tour/multiple-parameter-lists.md +++ b/_ru/tour/multiple-parameter-lists.md @@ -8,77 +8,224 @@ next-page: case-classes previous-page: nested-functions --- -Методы могут объявляться с несколькими списками параметров. При этом когда такой метод вызывается с меньшим количеством списков параметров, это приводит к созданию новой функции, которая ожидает на вход не достающий список параметров. Формально это называется [частичное применение](https://en.wikipedia.org/wiki/Partial_application). +Методы могут объявляться с несколькими списками параметров. -Например, - -```scala mdoc -val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) -val numberFunc = numbers.foldLeft(List[Int]()) _ +### Пример -val squares = numberFunc((xs, x) => xs :+ x*x) -print(squares) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) +Вот пример, определенный для трейта `Iterable` из API Scala коллекций: -val cubes = numberFunc((xs, x) => xs :+ x*x*x) -print(cubes) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000) +{% tabs foldLeft_definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=foldLeft_definition %} + +```scala +trait Iterable[A] { + ... + def foldLeft[B](z: B)(op: (B, A) => B): B + ... +} ``` -Рассмотрим такие примеры из класса [Traversable](/overviews/collections/trait-traversable.html) коллекции Scala: +{% endtab %} -```scala mdoc:fail +{% tab 'Scala 3' for=foldLeft_definition %} + +```scala +trait Iterable[A]: +... def foldLeft[B](z: B)(op: (B, A) => B): B +... ``` -`foldLeft` применяет бинарный оператор `op` к начальному значению `z` и ко всем остальным элементам коллекции слева направо. Ниже приведен пример его использования. +{% endtab %} -Начиная с начального значения 0, `foldLeft` применяет функцию `(m, n) => m + n` к каждому элементу списка и предыдущему накопленному значению. +{% endtabs %} -```scala mdoc:nest +`foldLeft` применяет бинарный оператор `op` к начальному значению `z` и ко всем остальным элементам коллекции слева направо. +Ниже приведен пример его использования. + +Начиная с начального значения `0`, `foldLeft` применяет функцию `(m, n) => m + n` к каждому элементу списка +и предыдущему накопленному значению. + +{% tabs foldLeft_use %} + +{% tab 'Scala 2 и 3' for=foldLeft_use %} + +```scala mdoc 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 +println(res) // 55 ``` -Множественные списки параметров имеют избыточный синтаксис, поэтому их следует использовать экономно. Можем предложить следующие варианты для использования множественных списков (каррирования): +{% endtab %} -#### Отдельный функциональный параметр - Функцию `op` можно выделить в отдельный функциональный параметр у `foldLeft`, благодаря такому выделению становится возможен более элегантный стиль передачи анонимной функции в метод. Без такого выделения код выглядел бы следующим образом: -```scala -numbers.foldLeft(0, {(m: Int, n: Int) => m + n}) +{% endtabs %} + +### Варианты для использования + +Предлагаемые варианты для использования множественных списков параметров включают: + +#### Вывод типа + +Исторически сложилось, что в Scala вывод типов происходит по одному списку параметров за раз. +Скажем, у вас есть следующий метод: + +{% tabs foldLeft1_definition %} + +{% tab 'Scala 2 и 3' for=foldLeft1_definition %} + +```scala mdoc +def foldLeft1[A, B](as: List[A], b0: B, op: (B, A) => B) = ??? +``` + +{% endtab %} + +{% endtabs %} + +Затем при желании вызвать его следующим образом, можно обнаружить, что метод не компилируется: + +{% tabs foldLeft1_wrong_use %} + +{% tab 'Scala 2 и 3' for=foldLeft1_wrong_use %} + +```scala mdoc:fail +def notPossible = foldLeft1(numbers, 0, _ + _) ``` - - Обратите внимание, что использование отдельного функционального параметра позволяет нам использовать автоматическое выведение типа для него, что делает код еще более кратким, это было бы невозможно без каррирования. - + +{% endtab %} + +{% endtabs %} + +вам нужно будет вызвать его одним из следующих способов: + +{% tabs foldLeft1_good_use %} + +{% tab 'Scala 2 и 3' for=foldLeft1_good_use %} + ```scala mdoc -numbers.foldLeft(0)(_ + _) +def firstWay = foldLeft1[Int, Int](numbers, 0, _ + _) +def secondWay = foldLeft1(numbers, 0, (a: Int, b: Int) => a + b) ``` - Если в утверждении `numbers.foldLeft(0)(_ + _)` зафиксировать отдельный параметр `z`, мы получим частично определенную функцию, которую можно переиспользовать, как показано ниже: -```scala mdoc:nest -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) +{% endtab %} + +{% endtabs %} + +Это связано с тем, что Scala не может вывести тип функции `_ + _`, так как она все еще выводит `A` и `B`. +Путем перемещения параметра `op` в собственный список параметров, `A` и `B` выводятся в первом списке. +Затем эти предполагаемые типы будут доступны для второго списка параметров +и `_ + _` станет соответствовать предполагаемому типу `(Int, Int) => Int`. + +{% tabs foldLeft2_definition_and_use %} -val cubes = numberFunc((xs, x) => xs:+ x*x*x) -print(cubes.toString()) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000) +{% tab 'Scala 2 и 3' for=foldLeft2_definition_and_use %} + +```scala mdoc +def foldLeft2[A, B](as: List[A], b0: B)(op: (B, A) => B) = ??? +def possible = foldLeft2(numbers, 0)(_ + _) ``` - `foldLeft` и `foldRight` может быть использован в любой из следующих вариаций, +{% endtab %} + +{% endtabs %} + +Последнее определение метода не нуждается в подсказках типа и может вывести все типы своих параметров. + +#### Неявные параметры + +Чтоб указать что параметр используется [_неявно_ (_implicit_)](/ru/tour/implicit-parameters.html) +необходимо задавать несколько списков параметров. +Примером может служить следующее: + +{% tabs execute_definition class=tabs-scala-version %} + +{% tab 'Scala 2' for=execute_definition %} + +```scala mdoc +def execute(arg: Int)(implicit ec: scala.concurrent.ExecutionContext) = ??? +``` + +{% endtab %} + +{% tab 'Scala 3' for=execute_definition %} + +```scala +def execute(arg: Int)(using ec: scala.concurrent.ExecutionContext) = ??? +``` + +{% endtab %} + +{% endtabs %} + +#### Частичное применение + +Методы могут объявляться с несколькими списками параметров. +При этом когда такой метод вызывается с меньшим количеством списков параметров, +это приводит к созданию новой функции, +которая ожидает на вход недостающий список параметров. +Формально это называется [частичное применение](https://ru.wikipedia.org/wiki/%D0%A7%D0%B0%D1%81%D1%82%D0%B8%D1%87%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5). + +Например, + +{% tabs foldLeft_partial %} + +{% tab 'Scala 2 и 3' for=foldLeft_partial %} + ```scala mdoc:nest val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) +val numberFunc = numbers.foldLeft(List[Int]()) _ -numbers.foldLeft(0)((sum, item) => sum + item) // Общая Форма -numbers.foldRight(0)((sum, item) => sum + item) // Общая Форма +val squares = numberFunc((xs, x) => xs :+ x*x) +println(squares) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) -numbers.foldLeft(0)(_+_) // Форма с каррированием -numbers.foldRight(0)(_+_) // Форма с каррированием +val cubes = numberFunc((xs, x) => xs :+ x*x*x) +println(cubes) // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000) ``` - -#### Неявные параметры - Чтоб указать что параметр используется неявно (`implicit`) необходимо задавать несколько списков параметров. Примером может служить следующее: +{% endtab %} -```scala -def execute(arg: Int)(implicit ec: ExecutionContext) = ??? +{% endtabs %} + +### Сравнение с «каррированием» + +Иногда можно встретить, что метод с несколькими списками параметров называется «каррированный». + +Как говорится [в статье на Википедии о каррировании](https://ru.wikipedia.org/wiki/%D0%9A%D0%B0%D1%80%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5), + +> Каррирование — преобразование функции от многих аргументов в набор вложенных функций, +> каждая из которых является функцией от одного аргумента. + +Мы не рекомендуем использовать слово «каррирование» в отношении множественных списков параметров Scala по двум причинам: + +1. В Scala множественные параметры и множественные списки параметров задаются + и реализуются непосредственно как часть языка, а не преобразуются из функций с одним параметром. + +2. Существует опасность путаницы с методами из стандартной Scala библиотеки + [`curried`]() + и [`uncurried`](), + которые вообще не включают множественные списки параметров. + +Тем не менее, несомненно, есть сходство между множественными списками параметров и каррированием. +Хотя они различаются в месте определения, +в месте вызова могут тем не менее выглядеть одинаково, как в этом примере: + +{% tabs about_currying %} + +{% tab 'Scala 2 и 3' for=about_currying %} + +```scala mdoc +// версия с множественными списками параметров +def addMultiple(n1: Int)(n2: Int) = n1 + n2 +// два различных способа получить каррированную версию +def add(n1: Int, n2: Int) = n1 + n2 +val addCurried1 = (add _).curried +val addCurried2 = (n1: Int) => (n2: Int) => n1 + n2 +// независимо от определения, вызов всех трех идентичен +addMultiple(3)(4) // 7 +addCurried1(3)(4) // 7 +addCurried2(3)(4) // 7 ``` + +{% endtab %} + +{% endtabs %}