1

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!

regexp
  • 766
  • 4
  • 14
  • How would such a thing work together with overridden methods. What should happen when I overwrite `foo` with `foo(c, d)`? If you want to call using an array, you could use [MethodMirror](http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Mirrors$MethodMirror).apply. – marc Sep 02 '13 at 19:53
  • Duplicates lots of questions like http://stackoverflow.com/a/11074833/1296806 Read the overview doc http://docs.scala-lang.org/overviews/reflection/overview.html and search for lots of answer with reflectMethod like http://stackoverflow.com/a/17559727/1296806 – som-snytt Sep 02 '13 at 20:16

3 Answers3

5

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.

som-snytt
  • 39,429
  • 2
  • 47
  • 129
0

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
Patryk Ćwiek
  • 14,078
  • 3
  • 55
  • 76
0

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.

user unknown
  • 35,537
  • 11
  • 75
  • 121