2013-03-02 26 views
5

(ECMAScript 6のJavaScript Spreadsに似ています)の配列を持つスカラ関数を呼び出す方法はありますか?Scalaの 'Spread'パラメータ?

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 

使用事例:個々のパラメータを受け入れる既存の方法に(コレクションから)テストデータを注入します。これらはテストケースなので、yparの#paramsが一致せず、ランタイムエラーまたは不正確な結果を返す場合は、まったく問題ありません。

質問は、パラメータのコレクションが与えられたときに、個々のパラメータを取る関数を呼び出すためのきれいな構文を使用できるかどうかです。デザインです(ただし、意見は歓迎ですが)。

+4

'ys'の要素数が3個未満の場合はどうなりますか? – huynhjl

+0

関数 'f'のオーバーロードは頭に浮かぶものです。それはあなたが提供した方法で関数を呼び出すことができますが、他の状況では失敗するでしょう –

答えて

3

タプルとしてリストを渡すのは簡単ではありません。タイプがあまりうまく一致しないからです(詳細は後述)。十分なshoehorningで、何を潤滑しかしフィット:

"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つの異なるグループで引数をSchönfinklingすることにより、このアプローチを拡張することができます。私はそのように行かないだろう。

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

スプレーを見ると、それらの完全なメソッドが暗黙のうちに1トンの異なるパラメータを受け取ることがあります。彼らのために使った素敵なトリックはMagnet Patternです。同じことをして、受け入れようとするタプルごとに暗黙的にあなたのマグネットに変換を導入することができます。

+0

+1は創造性ときれいな解決策です。しかし、どのようにすればよいのでしょうか?例のように、タプルの前に引数を多く入れて関数を呼び出す方法はありますか? b)任意の数のパラメータ(2つまたは3つだけでなく、どのように多くがケースとしてハードコードされているか)にも対応できるようにしますか? –

+1

任意の数のパラメータが必要な場合は、コード生成またはリフレクションが必要です。追加パラメータを使用すると、リストを操作したり、暗黙の変換ですべてを処理することができます。私はいくつかのヒントを与えるために答えを更新しますが、主により良いオプションを追加します。 – iwein

+1

2.10の新しいマクロ機能でこれを行う方法があります。私はそれをコード化する時間がありません。 apply(f、args ...)のようなことをして、探している結果を得ることができます(型の安全性を持つ)。 – jroesch

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の質問にanswereする

EDIT

a)foo.doFooを呼び出す方法は? (つまり、オブジェクトはありません。doFooを定義するクラスのインスタンスです。)

val fooFuncは、それがインスタンスapplyMeth.invoke(fooFunc, seq :_*)の起動時に呼び出される関数を適用することであるFunction2のインスタンスです。

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) 
} 
+1

a)foo.doFooをどのように呼び出すのですか? (つまり、オブジェクトがない - doFooを定義するクラスのインスタンスのみ)b)このメソッドは、 'seq'の値の前にdoFoo *の前に*および*を渡すことを許可しますか? –

+0

@Rubistro私はあなたの質問に関するいくつかのさらなる説明でanswereを更新しました。 –

関連する問題