Suppose:
def greet(name: String = "Sam", age: Int = 10): String =
s"Hello $name, you're $age years old"
I want to construct function calls like greet(age = 12)
or greet(name = "Jen")
or greet()
but just got confused on how to determine programmatically name
, age
and any other function parameter names. I mean, how a function call like greet(ParameterNameCalulatedWithinProgram = parameterValue)
can be created. I’m asking for help if:
- Any solution is available through Scala 3 macros at compile time
- Any solution is available at runtime.
Please note that a general solution shall be able to instantiate any valid value for any parameter name in any order from any hypothetic function definition.
1 Like
I am not sure I understand the question?
greet
already allows you to call any of the variations you mentioned.
Or do you mean that, depending on some runtime dat,a you will call any of the versions? But if so, then how is that different from any other kind of code? Like just use if
or pattern matching
or whatever.
It would help if you can provide more details, like what is the meta-problem you are trying to solve? What do you already tried? Why it doesn’t work or would you rather see improved?
Yes, the pattern matching may help; But suppose there is a function definition with more than 15 parameters, there are too many combinations to be handled. Presence and order of some or all parameters also make this task difficult to implement through pattern matching.
Back to my question, suppose an old main function which only accepts varargs, an Array
of String
s. How can I call an arbitrary function based on the strings provided as parameter names and parameter values. Probably some parameters are omitted to be instantiated by values from default value part of the function definition. So the function call construction needs to create a function call with only some parameter names, whose existence and order are unknown in prior.
If you control the method, then the best IMHO would make it accept something like:
- The
varargs
directly.
- A
Map[String, String]
.
- A
Config
like case class that exposes a builder.
Furthermore, if this is like the “main” of something, then you may look into libraries that allow parsing those varargs.
But, if rather, the problem is that you don’t control the function that is being called. Then, IMHO, the problem can be divided into two cases:
- You have this problem for a single method.
- You have this problem for an arbitrary number of methods.
In the first case, I personally would just code the mapping logic once. The idea would be to prepare all parameters, even if they have default values, and then just perform a full call of the method.
For the second case, then yeah I guess the solution would be a macro that given an arbitrary method foo
can create another method fooWrapper
that recieves the varargs and generates the boring logic I described above to call foo
.
But, still, you would need to know foo
at compile time.
If you don’t know foo
at compile time, but rather at runtime, you would need to go full reflection into this.
1 Like
Yeah, thanks. This is somehow like what I planned in my mind to implement. But just wanted to ask if there are any solutions or point of view which makes a difference, probably through the MacroAnnotation
. Anyway, thanks for your kind help.
With enough effort you should probably be able to write a macro that generates an AST like Apply(Ident("greet"), List(NamedArg("age", Literal(IntConstant(12))), NamedArg("name", Literal(StringConstant("Jen")))
based on some String
or other input.
And with multi-stage programming in Scala 3 it should be possible to generate code at runtime as well.
2 Likes
At runtime, parameter names are available through regular Java reflection. (You don’t specify a platform.)
The “default args” are implemented as methods named something like greet$default$1
etc.
scala-cli repl --server=false -S 3.7.2
Welcome to Scala 3.7.2 (23.0.2, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.
scala> def greet(name: String = "Sam", age: Int = 10): String =
| s"Hello $name, you're $age years old"
|
def greet(name: String, age: Int): String
scala> object G { def greet(name: String = "Sam", age: Int = 10): String =
| s"Hello $name, you're $age years old" }
// defined object G
scala> classOf[G.type].getDeclaredMethods
val res1: Array[java.lang.reflect.Method] = Array(private java.lang.Object rs$line$2$G$.writeReplace(), public java.lang.String rs$line$2$G$.greet$default$1(), public int rs$line$2$G$.greet$default$2(), public java.lang.String rs$line$2$G$.greet(java.lang.String,int))
scala> res1.last.getParameters
val res2: Array[java.lang.reflect.Parameter] = Array(final java.lang.String name, final int age)
scala>
I thought I was too lazy to remember how to do that.
2 Likes