有没有办法调用带有单个参数的Scala函数,给定数组(类似于JavaScript ECMAScript 6中的Spreads?
ys = [10.0, 2.72, -3.14]
f(x, ...ys);
最干净的语法是:
f(1, ys)
但这似乎不可能。即使f(1, ys:_*)
不起作用(f(ys:_*)
也不起作用,因为编译器报告的参数太少 - 只填充第一个参数 - 。
示例:
def f (a:Int, b:Float, c:Float, d:Float): String
val x = 1
val ys = List(10.0, 2.72, -3.14) // note: non-Objects
val result = f(x, ys) // intuitive, but doesn't work
用例:将测试数据(来自集合)注入到接受单个参数的现有方法中。由于这些是测试用例,如果ys中的#params不匹配并且产生运行时错误或结果不正确,那就没关系。
问题在于Scala是否允许一个干净的语法来调用一个带有单个参数的函数,给定一组参数 - 而不是它是否是一个好的设计(虽然意见肯定是受欢迎的)。
答案 0 :(得分:3)
将列表作为元组传递并不容易,因为类型不匹配(稍后会详细介绍)。有足够的鞋拔和润滑剂,但任何东西都适合:
"My hack" should {
"allow a function to be called with Lists" in {
def function(bar:String, baz:String)= bar+baz
//turn the function into something that accepts a tuple
val functionT = function _
val functionTT = functionT.tupled
val arguments = List("bar", "baz")
//Give the compiler a way to try and turn a list into a tuple of the size you need
implicit def listToTuple(args:List[String]) = args match {
case List(a, b) => (a, b)
case _ => throw IllegalArgumentException("Trying to pass off %s as a pair".format(args))
}
//Shove it in and hope for the best at runtime
val applied = functionTT(arguments)
applied === "barbaz"
}
}
您可以通过向列表中添加其他参数来扩展此方法,或者通过Schönfinkling两个不同组中的参数来扩展此方法。我不会这样。
从我的评论中你可能已经注意到我不喜欢导致这个问题弹出的设计。我展示的代码本质上是将函数包装在外观中的代码。为什么不这样做呢?
看看Spray你可能会发现他们的完整方法隐含地接受了大量不同的参数。他们为此使用了一个漂亮的技巧,他们命名为Magnet Pattern。您可以执行相同的操作,并为您选择接受的不同元组引入隐式转换。
答案 1 :(得分:3)
我认为你必须抛弃类型安全并使用反射。
scala> object Foo {
| def doFoo(i:Int, s:String) = println(s * i)
| }
defined module Foo
scala> val fooFunc = Foo.doFoo _
fooFunc: (Int, String) => Unit = <function2>
scala> val applyMeth = fooFunc.getClass.getMethods()(0)
applyMeth: java.lang.reflect.Method = public final void $anonfun$1.apply(int,java.lang.String)
scala> val i:Integer = 5
i: Integer = 5
scala> val seq = Seq[Object](i,"do the foo! ")
seq: Seq[Object] = List(5, "do the foo! ")
scala> applyMeth.invoke(fooFunc, seq :_*)
do the foo! do the foo! do the foo! do the foo! do the foo!
res59: Object = null
但是,除非您正在创建一些DSL并且真的需要这种功能,否则我会尝试寻找其他方法。如果方法在你的控制之下,要么重载这些方法,要么将它包装在某种外观类中。
修改强>
在评论中回答Rubistro的问题。
a)这种技术如何调用foo.doFoo? (也就是说,你没有对象 - 只是一个定义doFoo的类的实例。)
val fooFunc
是Function2
的一个实例,它是在调用applyMeth.invoke(fooFunc, seq :_*)
时调用的实例应用函数。
b)这个方法是否允许参数在seq中的值之前和之后传递给doFoo?
不直接。要使用它,您必须在调用方法之前构建序列。但是,由于它是一个序列,因此您可以在调用方法之前轻松地为您正在使用的序列添加/附加值。将其包装在构建器中可能很有用,例如
class InvokationBuilder(meth:java.lang.reflect.Method, args: List[Object] = Nil) {
def invoke(instance:Object) = meth.invoke(instance, args.toSeq :_*)
def append(arg:Object) = new InvokationBuilder(meth, args :+ arg)
def prepend(arg:Object)= new InvokationBuilder(meth, arg :: args)
}