Unwrapping a list or map as function arguments in

2020-06-11 14:42发布

问题:

Is it possible to dynamically unwrap a list/tuple/map items as arguments to a function in Scala? I am looking for a Scala equivalent of Python's args/kwargs.

For instance, in Python if a function is defined as def foo(bar1, bar2, bar3=None, bar4=1) then given a list x=[1,7] and a dictionary y={'bar3':True, 'bar4':9} you can call foo as foo(*x, **y).

回答1:

Just to be clear, the following is valid Python code:

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)

However, there is no analogous Scala syntax. There are some similar things, but the main reason this is never going to be possible is that it would violate the compile-time type checking that Scala requires. Let's look more closely.

The reasons

First, think about the varargs portion. Here you want to be able to pass in an arbitrary-length list of arguments and have it fill in the relevant function parameters. This will never work in Scala because the type checker requires that the parameters passed into a function be valid. In your scenario, foo() can accept a parameter list x of length two, but no less. But since any Seq can have an arbitrary number of parameters, how would the type checker know that the x being pass it is valid at compile time?

Second, think about the keywword arguments. Here you are asking for the function to accept an arbitrary Map of arguments and values. But you get the same problem: How can the compile-time type checker know that you are passing in all of the necessary arguments? Or, further, that they are the right types? After all, they example you give is a Map containing both a Boolean and an Int, which would have the type Map[String, Any], so how would the type checker know that this would match your parameter types?

Some solutions

Scala's varargs

You can do some similar things, but not this exactly. For example, if you defined your function to explicitly use varargs, you can pass in a Seq:

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

This works because Scala knows that it only needs a sequence of zero or more arguments, and a Seq will always contain zero or more items, so it matches. Further, it only works if the types match as well; here it's expecting a sequence of Ints, and gets it.

tupled

The other thing you can do is to pass in a tuple of arguments:

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)

Again, this works because Scala's type checker can verify that the arguments are valid. The function requires four arguments, of types Int, Int, Boolean, and Int, and since a tuple in Scala has a fixed length and known (and possibly different) types for each position, the type-checker can verify that the arguments match the expected parameters.



回答2:

Original answers don't mention handling Map as list of pairs - it can be easily converted to map (even -> operator is just shorthand for pair).

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


回答3:

Sort of an edge case for the OQ but if you want to pass a Map of arguments for a case class this seems to work:

scala> case class myCC(foo: String = "bar", negInt: Int = -1)

scala> val row = myCC()
scala> println(row)
myCC(bar,-1)

scala> val overrides = Map("foo" -> "baz")
scala> row.getClass.getDeclaredFields foreach { f =>
 f.setAccessible(true)
 overrides.foreach{case (k,v) => if (k == f.getName) f.set(row, v)}
}
scala> println(row)
myCC(baz,-1)

(borrowed from Scala: How to access a class property dynamically by name?)



回答4:

You can use varargs syntax:

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

No you can use this function so:

printAll("foo")

so:

printAll("foo", "bar")

or so:

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


标签: python scala