10

给定一个数组(类似于 ECMAScript 6 中的 JavaScript Spreads ) ,有没有办法调用一个接受单个参数的 Scala 函数?

ys = [10.0, 2.72, -3.14]
f(x, ...ys);

最简洁的语法是:

f(1, ys)

但这似乎是不可能的。Evenf(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 是否允许在给定参数集合的情况下调用带有单个参数的函数的简洁语法——而不是它是否是一个好的设计(尽管当然欢迎提出意见)。

4

2 回答 2

5

将列表作为元组传递并不容易,因为类型不太匹配(稍后会详细介绍)。有了足够的鞋拔和润滑,任何东西都适合:

"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önfinkel 将参数分成两个不同的组。我不会那样做的。

从我的评论中你可能已经注意到我不喜欢导致这个问题弹出的设计。我展示的代码本质上是将函数包装在外观中的代码。为什么不正确地做呢?

查看 Spray,您可能会看到他们的完整方法隐式接受大量不同的参数。他们为此使用的巧妙技巧将其命名为磁铁图案。你可以做同样的事情,并为你选择接受的不同元组引入你的磁铁的隐式转换。

于 2013-03-02T08:36:16.387 回答
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是 a 的一个实例,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)
}
于 2013-03-02T08:37:07.837 回答