Tour of Scala

Множественные списки параметров (Каррирование)

Language

Методы могут объявляться с несколькими списками параметров.

Пример

Вот пример, определенный для трейта Iterable из API Scala коллекций:

trait Iterable[A] {
   ...
   def foldLeft[B](z: B)(op: (B, A) => B): B
   ...
}
trait Iterable[A]:
...
def foldLeft[B](z: B)(op: (B, A) => B): B
...

foldLeft применяет бинарный оператор op к начальному значению z и ко всем остальным элементам коллекции слева направо. Ниже приведен пример его использования.

Начиная с начального значения 0, foldLeft применяет функцию (m, n) => m + n к каждому элементу списка и предыдущему накопленному значению.

val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val res = numbers.foldLeft(0)((m, n) => m + n)
println(res) // 55

Варианты для использования

Предлагаемые варианты для использования множественных списков параметров включают:

Вывод типа

Исторически сложилось, что в Scala вывод типов происходит по одному списку параметров за раз. Скажем, у вас есть следующий метод:

def foldLeft1[A, B](as: List[A], b0: B, op: (B, A) => B) = ???

Затем при желании вызвать его следующим образом, можно обнаружить, что метод не компилируется:

def notPossible = foldLeft1(numbers, 0, _ + _)

вам нужно будет вызвать его одним из следующих способов:

def firstWay = foldLeft1[Int, Int](numbers, 0, _ + _)
def secondWay = foldLeft1(numbers, 0, (a: Int, b: Int) => a + b)

Это связано с тем, что Scala не может вывести тип функции _ + _, так как она все еще выводит A и B. Путем перемещения параметра op в собственный список параметров, A и B выводятся в первом списке. Затем эти предполагаемые типы будут доступны для второго списка параметров и _ + _ станет соответствовать предполагаемому типу (Int, Int) => Int.

def foldLeft2[A, B](as: List[A], b0: B)(op: (B, A) => B) = ???
def possible = foldLeft2(numbers, 0)(_ + _)

Последнее определение метода не нуждается в подсказках типа и может вывести все типы своих параметров.

Неявные параметры

Чтоб указать что параметр используется неявно (implicit) необходимо задавать несколько списков параметров. Примером может служить следующее:

def execute(arg: Int)(implicit ec: scala.concurrent.ExecutionContext) = ???
def execute(arg: Int)(using ec: scala.concurrent.ExecutionContext) = ???

Частичное применение

Методы могут объявляться с несколькими списками параметров. При этом когда такой метод вызывается с меньшим количеством списков параметров, это приводит к созданию новой функции, которая ожидает на вход недостающий список параметров. Формально это называется частичное применение.

Например,

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)
println(squares) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)

val cubes = numberFunc((xs, x) => xs :+ x*x*x)
println(cubes)  // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000)

Сравнение с «каррированием»

Иногда можно встретить, что метод с несколькими списками параметров называется «каррированный».

Как говорится в статье на Википедии о каррировании,

Каррирование — преобразование функции от многих аргументов в набор вложенных функций, каждая из которых является функцией от одного аргумента.

Мы не рекомендуем использовать слово «каррирование» в отношении множественных списков параметров Scala по двум причинам:

  1. В Scala множественные параметры и множественные списки параметров задаются и реализуются непосредственно как часть языка, а не преобразуются из функций с одним параметром.

  2. Существует опасность путаницы с методами из стандартной Scala библиотеки curried и uncurried, которые вообще не включают множественные списки параметров.

Тем не менее, несомненно, есть сходство между множественными списками параметров и каррированием. Хотя они различаются в месте определения, в месте вызова могут тем не менее выглядеть одинаково, как в этом примере:

// версия с множественными списками параметров
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

Contributors to this page: