10

配列を指定して個々のパラメータを取る Scala 関数を呼び出す方法はありますか( ECMAScript 6 のJavaScript Spreadsに似ています )?

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"
  }
}

リストに追加の引数を追加するか、2 つの異なるグループで引数をシェーンフィンケリングすることにより、このアプローチを拡張できます。私はそのように行きません。

私の発言から、この質問がポップアップする原因となるデザインが気に入らないことに気付いたかもしれません。私が示したコードは、本質的に関数をファサードにラップするコードです。なぜそれを適切に行わないのですか?

スプレーを見ると、完全なメソッドがさまざまなパラメーターを暗黙的に受け入れていることがわかるかもしれません。彼らがこれに使用した気の利いたトリックは、マグネットパターンと名付けました。同じことを行って、受け入れることを選択したさまざまなタプルのマグネットに暗黙的な変換を導入することができます。

于 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 を作成していて、この種の機能が本当に必要でない限り、別の方法を見つけようとします。メソッドが制御下にある場合はメソッドをオーバーロードするか、何らかのファサードの種類のクラスにラップします。

編集

コメントでルビストロの質問に答える。

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)
}
于 2013-03-02T08:37:07.837 に答える