Reputation: 1449
Having Java classes:
class abstract Exp
class ExpAdd extends Exp
class ExpSub extends Exp
and Scala methods:
def interpret(e: ExpAdd) = interpret(e.left_arg) + interpret(e.right_arg)
def interpret(e: ExpSub) = interpret(e.left_arg) - interpret(e.right_arg)
def interpret(exp: Exp) = exp match {
case e : ExpAdd = interpret(e)
case e : ExpSub = interpret(e)
}
How to refactor the code to make adding new Exps easier? I thought interpret(Exp) wouldn't be used at all as two other are more specific and
If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.
however that seems not to be the case in Scala. I also tried removing interpret(Exp) but compiler didn't like it. That's why I used case clause... is there any way to make overloading work as expected? or maybe some workaround?
Upvotes: 1
Views: 211
Reputation: 17431
As @Alexy says, the method resolution is based on the compile-time or "static" type of the arguments. You could make the types of the operands part of the type of the expression, and then use implicits to recursively construct the interpreter for a given type:
trait Exp
case class ExpAdd[A <: Exp, B <: Exp](left_arg: A, right_arg: B) extends Exp
case class ExpSub[A <: Exp, B <: Exp](left_arg: A, right_arg: B) extends Exp
case class Literal(i: Int) extends Exp
trait ExpInterpreter[E <: Exp] {
def interpret(e: E): Int
}
object ExpInterpreter {
implicit object LiteralInterpreter extends Interpreter[Literal] {
def interpret(l: Literal) = l.i
}
implicit def addInterpreter[A <: Exp, B <: Exp](
implicit leftInterpreter: Interpreter[A], rightInterpreter: Interpreter[B]) =
new Interpreter[ExpAdd[A, B]] {
def interpret(e: ExpAdd[A, B]) = leftInterpreter.interpret(e.left_arg) +
rightInterpreter.interpret(e.right_arg)
}
implicit def subInterpreter ...
}
Then when you add new Exp
cases you just need to define a new implicit, and you can do that anywhere rather than having to add a case to a particular match expression. You could even have Exp
and ExpInterpreter
in a library and the cases and specific interpreters in a different jar.
As written this means the structure of an expression is part of its type (and you have to carry the type around until you interpret it - there's no way to interpret a generic Exp
, only an E <: Exp: ExpInterpreter
), but maybe it's possible to use a similar technique to the Free monad to overcome this?
Upvotes: 3
Reputation: 170723
If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.
This is the same in Java and Scala: which overload is used is chosen at compile-time (and then this specific overload is dispatched at run-time, as the quote says). Since e.left_arg
is not known to be either ExpAdd
or ExpSub
, the compiler can only choose interpret(exp: Exp)
and complains when you remove it. One option is to remove the other overloads instead:
def interpret(exp: Exp) = exp match {
case e : ExpAdd => interpret(e.left_arg) + interpret(e.right_arg)
case e : ExpSub => interpret(e.left_arg) - interpret(e.right_arg)
}
// when extending
override def interpret(exp: Exp) = exp match {
case e : ExpMult => interpret(e.left_arg) * interpret(e.right_arg)
case _ => super.interpret(exp)
}
Upvotes: 1
Reputation: 21391
Your definition of Exp
and interpret
are not fully visible and not sure I understand what you are trying to represent but here's 2 ideas:
+
and a -
methods for more DSL-esque operationsUpvotes: 1