2015-08-26 3 views
0

У меня есть иерархия классов, обозначающих выражения, некоторые из которых обертывают объекты других типов. Я хотел бы настроить некоторые неявные преобразования из завернутых типов в типы выражений, так что легче создавать выражения. Я попробовал 3 места, где можно определить неявные преобразования (как предложено различными источниками в Интернете): объект, который импортируется (это работает), объекты-компаньоны для типов источников (это условно работает для неперегруженных методов) и сопутствующий объект для тип цели (это не работает). Код ниже:Где/как Scala ищет неявные методы преобразования?

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) 
    } 
} 

Я попытался определить два оператора: + и :+. Для + конверсии, похоже, каким-то образом конфликтуют с конверсиями до String, таким образом, только 1-й путь (импортированный объект) действительно работает. Для :+ сопутствующие объекты типа источника также прекрасны. Определение конверсий в сопутствующем объекте целевого типа не работает, несмотря на то, что говорят некоторые источники (например, http://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html).

Вопрос в том, каким образом можно определить такие преобразования. Первый способ, по-видимому, часто используется в стандартной библиотеке (например, JavaConversions/JavaConverters). Но каков правильный путь?

UPD 1 Как отметил Алексей Романов, я попытался определить преобразования в trait Expr вместо своего объекта-компаньона. Имея эти:

object Expr { 
    implicit def varToExpr(v: Var): VarExpr = new VarExpr(v) 
    implicit def constToExpr(c: Const): ConstExpr = new ConstExpr(c) 
} 

позволяет писать такие вещи, как

val e12: Expr = a 

но не

val e11: Expr = a :+ VarExpr(b) 

UPD 2 Как намекнул @cchantep, проблема с определением преобразования в компаньона объекты могут иметь приоритет. В Predef.scala мне удалось найти:

implicit final class any2stringadd[A](private val self: A) extends AnyVal { 
    def +(other: String): String = String.valueOf(self) + other 
} 

То, что я понял из этого является то, что компилятор попытается использовать это определение, если + используется на объекте типа, который сам по себе не определяет +. Кроме того, это определение +, по-видимому, будет иметь приоритет любого неявного преобразования, которое определено в сопутствующем объекте. Это объясняет ошибку компилятора, которую я получал.

+2

Возможный дубликат [Scala: Неявный приоритет разрешения параметров] (http://stackoverflow.com/questions/8623055/scala-implicit-parameter-resolution-precedence) – cchantep

+0

@cchantep Не могли бы вы рассказать подробнее? Вы предполагаете, что преобразование в 'String' для оператора' + 'имеет более высокий приоритет, чем преобразования, которые я определяю в сопутствующих объектах? Ну может быть, но я пока не могу найти, где именно это определено. –

+0

@cchantep Это также не объясняет, почему сопутствующий объект целевого типа 'Expr' не выполняется, что предлагается в FAQ, который я задал в моем вопросе. –

ответ

1

Вы не определяете ничего в сопутствующем объекте Expr, вы определяете неявные преобразования внутри trait Expr, и они видны только внутри его области. Вам нужно написать

trait Expr { 
    def +(right: Expr): PlusExpr = PlusExpr(this, right) 
    def :+(right: Expr): PlusExpr = PlusExpr(this, right) 
} 
object Expr { 
    implicit def varToExpr(v: Var): VarExpr = new VarExpr(v) 
    implicit def constToExpr(c: Const): ConstExpr = new ConstExpr(c) 
} 

Но я не думаю, что это будет работать либо для e1 - e10: компилятор будет выглядеть в сопутствующем объект целевого типа, но для целевого типа неExpr, это «что-то с + или :+ методами». Он не будет пытаться использовать все такие типы (Int, Double, Map и т. Д.), Если один из них имеет неявное преобразование в своем сопутствующем объекте.

Он будет работать в таких ситуациях, как val e11: Expr = a или foo(a), где foo занимает Expr.

any2stringadd неявный класс имеет более высокий приоритет по сравнению с неявными преобразованиями в сопутствующем объекте, поскольку он импортируется автоматически и так заметен в любой области видимости, а импликации в области видимости проверяются перед объектами-компаньонами. Вы можете избежать этого, указав неявный метод с тем же именем.

+0

Мой плохой, это не было предназначено. Тем не менее, я как-то ожидал (из формулировки часто задаваемых вопросов), что если конверсии определены в компаньоне 'object Expr', я бы смог написать' val e: Expr = a + b'. По-видимому, это не так. –

Смежные вопросы