How to get function name in Scala?

Consider a simple task. Just show the whole expression, for example:

a operator b = c 

In Common Lisp, it might look like this:

  (defun applyOp (ab op) (format t "~S ~S ~S = ~S" a op b (apply op (list ab)))) $ (applyOp 1 2 #'logand) 1 #<FUNCTION LOGAND> 2 = 0 

In Scala, this does not look so trivial:

  def applyOperator(x: Int, y: Int, op: (Int, Int) => Int) = { println(x + s" $op " + y + " = " + op(x,y)) } scala> applyOperator(1, 2, _ & _) 1 <function2> 2 = 0 // how to get the function name? 

What is the name <function2> ?

+5
source share
3 answers

Another full-featured approach will be a simple macro .

Suppose you write the simplest expression to extract an expression string and typename:

 import scala.reflect.macros.blackbox.Context import scala.language.experimental.macros class VerboseImpl(val c: Context) { import c.universe._ def describe[T: c.WeakTypeTag](expr: c.Expr[T]): c.Expr[(String, String, T)] = { val repr = expr.tree.toString val typeName = weakTypeOf[T].toString c.Expr[(String, String, T)](q"($repr, $typeName, $expr)") } } object Verbose{ def apply[T](expr: T): (String, String, T) = macro VerboseImpl.describe[T] } 

Further in another source (preferably in another subproject) you can write

 val a = 2 val b = 3 println(Verbose(a + b + 3)) 

and see the magic line

 (a.+(b).+(3),Int,8) 

From now on, you can improve your macro to display any information about methods, parameters, types, etc.

Note that the macro is evaluated at compile time. So the Verbose.apply call Verbose.apply like creating a tuple with two string constants, so although it is the only uncompromising, extensible approach, it is definitely the most efficient

+5
source

a op b - call a method named op

a op b syntactically equivalent to a.op(b) (except that the name op ends with a colon, in which case it is equivalent to b.op(a) ).

This, of course, is completely different from something like a.callOperator(op,b) , where you need the name op . In scala, a different method exists for each operator, and access to the name of the operator inside the method that implements this operator is meaningless.

This is necessary for type checking.

The good thing with callOperator (op, b) is that you can implement the entire operator in the same place, maybe very general and concise.

A good thing with one method for each, if the compiler verifies that you can only call the one that is actually implemented, with the corresponding arguments. In addition, the type of result will be known at compile time.

Scala, a typed language, prefers a very second.

There is an exit hatch, dynamic

However, in some remote corner of the language, there is a way to turn a method call that is not available at compile time into some kind of backup call that receives the name of the method (or statement) and arguments.

The purpose of the call (i.e., the left operand) should extend the Dynamic trait. Since this is a pretty special feature, you should enable it with import scala.language.dynamics .

Then, each call to a non-existing method will be rewritten as a call to the applyDynamic method with two lists of arguments, the first of which will receive the name of the method, the second - the actual arguments. Depending on how you defined applyDynamic , this rewriting may or may not be allowed by the compiler.

Here is an example

 case class A(string name) { def applyDynamic(methodOrOperatorName: String)(arg: Any) : A { A(s"($name $methodOrOperatorName ${arg.mkString(", ")}) } } 

Here I choose to allow only one argument in the call, which is good if I want only binary operators (but there is no way to distinguish op b from a.op (b), scala consider them equivalent), Otherwise I would write args: Any* . I allowed any type (Any), but I could limit it, for example force arg: A I am not forced to use A for the result type, but if my result type is not known as dynamic, I will not bind a op b op' c .

There may be several problems. applyDynamic will only be called if the compiler does not compile a op b other ways. If, for example, there are some implicit conversions that make op available, this will take precedence. For example, Predef makes + available for each object to concatenate strings. Therefore, if your operator is + , this is what will be called. To avoid this, you can define + in A:

 def +(arg: Any): A = applyDynamic("+")(arg) // Arg and result type as in applyDynamic 

This would make + safe, but any available agent available on the call site via implicit will also be a priority.

Not a magical way

If you have a limited list of allowed operators, you can generally avoid magic.

 class A { def +(arg: A): A = callOp("+", b) // or another signature def -(arg: A): A = callOp("-", b) def callOp(name: String, arg: A): A = {...} } 
+4
source

You can get something like this if your Function2 objects are objects. This is a little gentle, but I found it convenient in some DSLs where there is a fixed number of function objects. For instance,

 def applyOperator(x: Int, y: Int, op: Function2[Int, Int, Int]) = s"$x ${op.getClass.getName.split("\\$").last } $y = ${op(x, y)}" object A extends Function2[Int, Int, Int] { def apply(x: Int, y: Int) = x + y } scala> applyOperator(5, 6, A) res1: String = 5 A 6 = 11 
+3
source

Source: https://habr.com/ru/post/1236263/


All Articles