is it possible to set arguments of a function by name with a string.
For example:
given:
def foo(a: String, b: String)
can i invoke this function dynamically with a Map like
Map(("a", "bla"), ("b", "blub"))
?
If so, how?
Thank you!
is it possible to set arguments of a function by name with a string.
For example:
given:
def foo(a: String, b: String)
can i invoke this function dynamically with a Map like
Map(("a", "bla"), ("b", "blub"))
?
If so, how?
Thank you!
Applying map of args reflectively to method:
apm@mara:~/tmp$ scalam
Welcome to Scala version 2.11.0-M4 (OpenJDK 64-Bit Server VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.
scala> val m = Map(("a", "bla"), ("b", "blub"))
m: scala.collection.immutable.Map[String,String] = Map(a -> bla, b -> blub)
scala> import reflect.runtime._
import reflect.runtime._
scala> import universe._
import universe._
scala> class Foo { def foo(a: String, b: String) = a + b }
defined class Foo
scala> val f = new Foo
f: Foo = Foo@2278e0e7
scala> typeOf[Foo].member(TermName("foo"))
res0: reflect.runtime.universe.Symbol = method foo
scala> .asMethod
res1: reflect.runtime.universe.MethodSymbol = method foo
scala> currentMirror reflect f
res2: reflect.runtime.universe.InstanceMirror = instance mirror for Foo@2278e0e7
scala> res2 reflectMethod res1
res3: reflect.runtime.universe.MethodMirror = method mirror for Foo.foo(a: String, b: String): java.lang.String (bound to Foo@2278e0e7)
scala> res1.paramss.flatten
res5: List[reflect.runtime.universe.Symbol] = List(value a, value b)
scala> .map(s => m(s.name.decoded))
res7: List[String] = List(bla, blub)
scala> res3.apply(res7: _*)
res8: Any = blablub
Notice that you'll want to flatten the parameter lists, paramss.flatten
. That is an ordered list of params which you can map to your args trivially.
The clever naming convention paramss
is meant to convey the multiple nesting; personally, I pronounce it as though it were spelled paramses
in English.
But now that I see it spelled out, I may pronounce it to rhyme with the Egyptian pharoah.
OK, here it goes with reflection. It's not exactly bound per argument name via a Map
, but you have to keep a sequence with argument values in order, so the first value would be for a
, second for b
etc. I'd say that's close enough
Everything was taken from here, actually it's an excellent resource on Mirrors.
scala> import scala.reflect.runtime.{ universe => ru }
import scala.reflect.runtime.{universe=>ru}
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> class Bar { def foo(a : String, b : String) { println(a + ", " + b + "!")
}}
defined class Bar
scala> val instanceMirror = ru.runtimeMirror(getClass.getClassLoader).reflect(new Bar)
instanceMirror: reflect.runtime.universe.InstanceMirror = instance mirror for Bar@47f3b292
scala> val fooMethod = typeOf[Bar].declaration(newTermName("foo")).asMethod
fooMethod: reflect.runtime.universe.MethodSymbol = method foo
scala> val method = instanceMirror.reflectMethod(fooMethod)
method: reflect.runtime.universe.MethodMirror = method mirror for Bar.foo(a: String, b: String): scala.Unit (bound to Bar@47f3b292)
scala> val args = Seq("Hey", "you")
args: Seq[String] = List(Hey, you)
scala> method(args: _*)
Hey, you!
res0: Any = ()
If the method returns anything, no problem, but the return type will be Any
(docs):
scala> class Bar { def foo(a : String, b : String) = a + b }
defined class Bar
scala> val instanceMirror = ru.runtimeMirror(getClass.getClassLoader).reflect(ne
w Bar)
instanceMirror: reflect.runtime.universe.InstanceMirror = instance mirror for Bar@642e0260
scala> val fooMethod = typeOf[Bar].declaration(newTermName("foo")).asMethod
fooMethod: reflect.runtime.universe.MethodSymbol = method foo
scala> val method = instanceMirror.reflectMethod(fooMethod)
method: reflect.runtime.universe.MethodMirror = method mirror for Bar.foo(a: String, b: String): java.lang.String (bound to Bar@642e0260)
scala> val args = Seq("Testing", "concatenation")
args: Seq[String] = List(Testing, concatenation)
scala> method(args: _*)
res5: Any = Testingconcatenation
Without reflection, your setting:
val valmap = Map (("a", "bla"), ("b", "blub"))
def foo (a: String, b: String) = a + "-baz-" + b
A function which takes care of name mapping:
def fmap (m: Map[String, String]) : String = (m.get("a"), m.get ("b")) match {
case (Some(s), Some (t)) => foo (s, t)
case _ => "didn't match"
}
Invokation:
fmap (valmap)
// res0: String = bla-baz-blub
A better name than fmap, which might be confused with flatmap, might be useful. Note, that Maps don't guarantee to contain certain keys, so we don't get Strings but Options of Strings returned.
The names aren't guaranteed at compile time to match and the function gets called with s and t in the end. But occasionally you might need such a functionality, and have internal proof, that "a" and "b" are in the map. Of course you could invoke every Method which expects 2 Strings that way with arbitrary names, the simplest one with b,a instead of a,b:
// ...
case (Some(s), Some (t)) => foo (t, s)
// ...
A similar approach with an Array:
val arr: Array[String] = (Seq.fill ('c')("not initialized")).toArray
arr('a')="bla"
arr('b')="blub"
def fmap (ar: Array[String]) : String = foo (ar('a'), ar('b'))
fmap (arr)
Seq.fill ('c')... fills the Array from '\0' to 'b', so you get a pretty sparse Array, just to handle 2 values. If you would fill it until 'z', you could invoke the function dynamically:
def fmap (ar: Array[String], c1: Char, c2: Char) : String = foo (ar(c1), ar(c2))
fmap (arr, 'a', 'b')
fmap (arr, 'b', 'a')
fmap (arr, 'a', 'a')
fmap (arr, 'i', 't')
and so on.