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)