I have a hierarchy of classes denoting expressions, some of which wrap objects of other types. I'd like to set up some implicit conversions from wrapped types to expression types, so that it is easier to construct expressions. I tried 3 places where to define implicit conversions (as suggested by various sources online): an object that gets imported (this works), companion objects for source types (this works conditionally, for non-overloaded methods), and a companion object for target type (this does not work). The code is below:
import scala.language.implicitConversions
// 1.
// If conversions are defined this way, the program compiles.
// But many sources insist that compiler also looks in the source/target companions for conversions.
object Conversions {
implicit def varToExpr(v: Var): VarExpr = new VarExpr(v)
implicit def constToExpr(c: Const): ConstExpr = new ConstExpr(c)
}
import Conversions._
class Var(val name: String)
class Const(val value: String)
// 2.
// These conversions do not work for e1-e4 (see below), but e5-e10 compile.
object Var {
implicit def varToExpr(v: Var): VarExpr = new VarExpr(v)
}
object Const {
implicit def constToExpr(c: Const): ConstExpr = new ConstExpr(c)
}
trait Expr {
def +(right: Expr): PlusExpr = PlusExpr(this, right)
def :+(right: Expr): PlusExpr = PlusExpr(this, right)
// 3.
// These do not work at all.
implicit def varToExpr(v: Var): VarExpr = new VarExpr(v)
implicit def constToExpr(c: Const): ConstExpr = new ConstExpr(c)
}
case class ConstExpr(c: Const) extends Expr
case class VarExpr(v: Var) extends Expr
case class PlusExpr(left: Expr, right: Expr) extends Expr
class Test {
def main(args: Array[String]) {
val a = new Var("a")
val b = new Var("b")
val c = new Const("c")
// Want to be able to write things like that.
// The first 4 work only with the 1-st way of defining the implicit conversions.
// For the 2nd way they give
// Error:(__, __) type mismatch;
// found : Const (or Var, or VarExpr)
// required: String
val e1 = a + b
val e2 = c + c
val e3 = (a + b) + c
val e4 = a + VarExpr(b)
// This compiles with both 1st and 2nd ways.
val e5 = ConstExpr(c) + b
val e6 = VarExpr(a) + c
val e7 = a :+ b
val e8 = c :+ c
val e9 = (a :+ b) :+ c
val e10 = a :+ VarExpr(b)
}
}
I tried to define two operators: +
and :+
. For +
, conversions seem to somehow conflict with conversions to String
, thus only 1st way (imported object) really works. For :+
companion objects of the source type are also fine. Defining conversions in the companion object of the target type does not seen to work, despite what some sources say (e.g., http://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html).
The question is, what is the appropriate way to define such conversions. 1st way seems to be often used in the standard library (e.g., JavaConversions
/JavaConverters
). But what is the right way?
UPD 1
As pointed out by Alexey Romanov, I tried to define the conversions in trait Expr
instead of its companion object. Having these:
object Expr {
implicit def varToExpr(v: Var): VarExpr = new VarExpr(v)
implicit def constToExpr(c: Const): ConstExpr = new ConstExpr(c)
}
allows to write things like
val e12: Expr = a
but not
val e11: Expr = a :+ VarExpr(b)
UPD 2
As hinted by @cchantep, the problem with defining the conversions in companion objects might be with precedence. In Predef.scala
I managed to find:
implicit final class any2stringadd[A](private val self: A) extends AnyVal {
def +(other: String): String = String.valueOf(self) + other
}
What I understand from this is that the compiler would try to use this definition, if +
is used on the object of the type which does not itself define +
. Also, this definition of +
would apparently take precedence of any implicit conversion that is defined in a companion object. This would explain the compiler error that I was getting.