2013-03-02 20 views
5

Czy istnieje sposób wywołania funkcji Scala, która przyjmuje indywidualne parametry, z podaniem tablicy (podobnej do JavaScript Spreads w ECMAScript 6)?Parametry "Spread" w Scali?

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

Najczystsze składnia byłoby:

f(1, ys) 

ale to nie wydaje się być możliwe. Nawet f(1, ys:_*) nie działa (ani nie ma wartości f(ys:_*), ponieważ kompilator zgłasza za mało parametrów - wypełniany jest tylko pierwszy).

przykład:

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 

przypadku użycia: wstrzyknięcie danych testowych (z kolekcji) do istniejących metod, które przyjmują poszczególne parametry. Ponieważ są to przypadki testowe, jest całkiem w porządku, jeśli liczba znaków w ys nie jest zgodna i daje albo błąd wykonania, albo niepoprawny wynik.

Pytanie brzmi, czy Scala umożliwia czystą składnię wywołania funkcji, która przyjmuje indywidualne parametry, biorąc pod uwagę zbiór parametrów - nie, czy jest to dobry projekt (chociaż opinie są z pewnością mile widziane).

+4

Co powinno się stać, gdy 'ys' ma mniej niż 3 elementy lub więcej? – huynhjl

+0

Przeciążanie funkcji 'f' jest tym, co przychodzi na myśl. Umożliwiłoby to wywołanie funkcji w sposób podany przez użytkownika, ale w innych sytuacjach może się nie powieść –

Odpowiedz

3

Podanie listy jako krotki nie jest łatwe, ponieważ typy nie pasują do siebie bardzo dobrze (więcej o tym później). Mając wystarczająco dużo shoehorning i smarowe cokolwiek pasuje jednak:

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

można rozszerzyć to podejście poprzez dodanie dodatkowych argumentów do listy, lub przez Schönfinkling argumenty w dwóch różnych grup. Nie pójdę tą drogą.

Z moich uwag mogłeś zauważyć, że nie podoba mi się projekt, który powoduje pojawienie się tego pytania. Kod, który pokazałem, to w zasadzie kod, który i tak zawija funkcję w elewacji. Dlaczego nie zrobić tego właściwie?

Patrząc na Spray możesz zauważyć, że ich pełna metoda akceptuje nieskończenie wiele różnych parametrów. Podstępna sztuczka, którą wykorzystali do tego, nazwali Magnet Pattern. Możesz zrobić to samo i wprowadzić niejawne konwersje na swój magnes dla różnych krotek, które zaakceptujesz.

+0

+1 za kreatywność i dość czyste rozwiązanie. Ale jakikolwiek pomysł jak wywołać funkcję z większą liczbą argumentów przed krotką, jak w przykładzie? oraz b) pozwolić jej działać dla dowolnej liczby parametrów (nie tylko dwóch lub trzech, czy też jak wielu jest zakodowanych jako przypadki)? –

+1

Jeśli potrzebujesz dowolnej liczby parametrów, potrzebujesz generowania kodu lub odbicia. Z dodatkowymi parametrami możesz manipulować listą lub obsłużyć całą rzecz w niejawnej konwersji. Zaktualizuję odpowiedź, by podać kilka wskazówek, ale przede wszystkim dodam lepszą opcję. – iwein

+1

Można to zrobić za pomocą nowych funkcji Macro w wersji 2.10. Nie mam czasu, żeby to zakodować. Chociaż możesz zrobić coś podobnego do aplikacji (f, args ...) i uzyskać wynik, którego szukasz (z bezpieczeństwem typu). – jroesch

3

Myślę, że musiałbyś odrzucić bezpieczeństwo i użyć refleksji.

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 

Jednakże, jeśli nie tworzysz jakiegoś DSL i naprawdę potrzebujesz tego rodzaju funkcji, spróbuję znaleźć inny sposób. Albo przeładuj metody, jeśli jest pod twoją kontrolą, albo włóż to w jakąś klasę elewacji.

EDIT

Aby answere pytania Rubistro w komentarzach.

a) Jak ta technika zadziałałaby w celu wywołania foo.doFoo? (To znaczy, że nie masz obiektu - po prostu instancja klasy, która definiuje doFoo.)

val fooFunc jest instancją Function2 to, że instancja funkcji, która jest wywoływana podczas wywoływania applyMeth.invoke(fooFunc, seq :_*) zastosowania.

b) czy ta metoda pozwala przekazywać parametry do doFoo przed i po wartościach w seq?

Nie bezpośrednio. Aby tego użyć, musisz zbudować sekwencję przed wywołaniem metody. Jednakże, ponieważ jest to Sekwencja, możesz łatwo dodać/dołączyć wartości do sekwencji, której używasz przed wywołaniem metody. Zawijanie go w konstruktorze może być przydatne, np.

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) Jak ta technika zadziała w celu wywołania foo.doFoo? (To znaczy, że nie masz obiektu - tylko instancja klasy, która definiuje doFoo.) B) czy ta metoda pozwala przekazywać parametry do doFoo * przed * i * po * wartościach w 'seq'? –

+0

@Rubistro Zaktualizowałem odpowiedź z dodatkowymi wyjaśnieniami dotyczącymi twoich pytań. –