Развертывание списка или карты в качестве аргументов функции в Scala

Можно ли динамически разворачивать элементы list/tuple/map в качестве аргументов функции в Scala? Я ищу эквивалент Scala Python args/kwargs.

Например, в Python, если функция определена как def foo(bar1, bar2, bar3=None, bar4=1), то, учитывая список x=[1,7] и словарь y={'bar3':True, 'bar4':9}, вы можете вызвать foo как foo(*x, **y).

Ответ 1

Чтобы быть ясным, допустимым является код Python:

def foo(bar1, bar2, bar3=None, bar4=1): print("bar1="+str(bar1)+" bar2="+str(bar2)+" bar3="+str(bar3)+" bar4="+str(bar4))
x=[1,7]
y={'bar3':True, 'bar4':9}
foo(*x,**y)

Однако не существует аналогичного синтаксиса Scala. Есть некоторые подобные вещи, но основная причина, по которой это никогда не будет возможным, заключается в том, что она нарушит проверку типа компиляции, требуемую Scala. Давайте посмотрим более внимательно.

Причины

Сначала подумайте о части varargs. Здесь вы хотите иметь возможность передавать произвольный список аргументов и заполнять соответствующие параметры функции. Это никогда не будет работать в Scala, потому что для проверки типа требуется, чтобы параметры, переданные в функцию, были действительны. В вашем сценарии foo() может принимать список параметров x длины два, но не менее. Но так как любой Seq может иметь произвольное количество параметров, как мог бы знать, что прохождение x проходит, это действительно во время компиляции?

Во-вторых, подумайте о аргументах ключевого слова. Здесь вы просите функцию принять произвольный Map аргументов и значений. Но у вас возникает та же проблема: как средство проверки типа компиляции знает, что вы передаете все необходимые аргументы? Или, кроме того, что они правильные типы? В конце концов, пример, который вы приводите, представляет собой карту, содержащую как логические, так и Int, которые будут иметь тип Map[String, Any], и как бы проверивший тип знал, что это будет соответствовать вашим типам параметров?

Некоторые решения

Scala varargs

Вы можете сделать некоторые подобные вещи, но не это точно. Например, если вы определили свою функцию для явного использования varargs, вы можете перейти в Seq:

def foo(bar1: Int*) = println(f"bar1=$bar1")
val x = Seq(1, 2)
foo(x:_*)

Это работает, потому что Scala знает, что ему нужна только последовательность из нуля или более аргументов, а Seq всегда будет содержать ноль или несколько элементов, поэтому он будет соответствовать. Кроме того, он работает только в том случае, если типы совпадают; здесь он ожидает последовательность интов и получает ее.

tupled

Другое, что вы можете сделать, это передать кортеж аргументов:

def foo(bar1: Int, bar2: Int, bar3: Boolean = false, bar4: Int = 1) = println(f"bar1=$bar1 bar2=$bar2 bar3=$bar3 bar4=$bar4")
val x = (1, 2, true, 9)
(foo _).tupled(x)

Опять же, это работает, потому что Scala checker типа может проверить правильность аргументов. Функция требует четырех аргументов типов Int, Int, Boolean и Int, а поскольку кортеж в Scala имеет фиксированную длину и известные (и, возможно, разные) типы для каждой позиции, средство проверки типов может проверить, что аргументы соответствуют ожидаемым параметрам.

Ответ 2

В исходных ответах не упоминается обработка Map как списка пар - его можно легко преобразовать в map (четный → оператор - только сокращение для пары).

def parse(options: (String, String)*) = println (options.toMap)

Ответ 3

Вы можете использовать синтаксис varargs:

def printAll(strings: String*) {
  strings.map(println)
}

Нет, вы можете использовать эту функцию так:

printAll("foo")

так:

printAll("foo", "bar")

или так:

printAll("foo", "bar", "baz")