从作为方法参数vs类构造函数参数传递的对象导入implicits

时间:2016-12-05 19:03:04

标签: scala implicit

我有以下代码:

case class Custom(value: Int)
case class Custom2(value: Float)

case class MappedEncoding[I, O](f: I => O)

trait Decoders {
  type BaseDecoder[T] = () => T
  type Decoder[T] <: BaseDecoder[T]
}

trait ConcreteDecoders extends Decoders {
  type Decoder[T] = ConcreteDecoder[T]

  case class ConcreteDecoder[T](decoder: () => T) extends BaseDecoder[T] {
    def apply(): T = decoder()
  }

  implicit def optionDecoder[T](implicit d: Decoder[T]): Decoder[Option[T]] =
    ConcreteDecoder[Option[T]](() => Some(d()))

  implicit def mappedDecoder[I, O](implicit mapped: MappedEncoding[I, O], decoder: Decoder[I]): Decoder[O] =
    ConcreteDecoder[O](() => mapped.f(decoder()))

  implicit def intDecoder: Decoder[Int] = ConcreteDecoder[Int](() => 1)
  implicit def floatDecoder: Decoder[Float] = ConcreteDecoder(() => 1)

}

class ConcreteContext extends ConcreteDecoders {
}

case class TestObject() {

  implicit val customDecoder = MappedEncoding[Int, Custom](Custom)
  implicit val custom2Encoder = MappedEncoding[Custom2, Float](_.value) // 1
  implicit val custom2Decoder = MappedEncoding[Float, Custom2](Custom2)

  def a(c: ConcreteContext): Unit = {
    import c._
    implicitly[Decoder[Option[Custom]]] // 2
//  implicitly[Decoder[Float]]          // 3
    implicitly[Decoder[Option[Float]]]
    ()
  }
}


object Main extends App {
  implicit val c = new ConcreteContext()

  TestObject().a(c)
  // TestObject(a).()
}

它不能在Scala 2.11.8和2.12.0中编译,但错误:

diverging implicit expansion for type c.Decoder[Option[Float]]
[error] starting with method intDecoder in trait ConcreteDecoders
[error]     implicitly[Decoder[Option[Float]]]

使用-Xlog-implicits选项可以提供长输出,但最有趣的部分是:

floatDecoder is not a valid implicit value for c.Decoder[Float] because:
[info] diverging implicit expansion for type c.Decoder[T]
[info] starting with method intDecoder in trait ConcreteDecoders
[info]     implicitly[Decoder[Option[Float]]]

c: CustomContext从method参数移动到case类构造函数参数使其编译。我认为可能是它的变化意味着搜索范围。

以下操作之一也可以编译:

  • 评论标有评论1(==第1行)
  • 的行
  • 评论第2行
  • 取消注释第3行

看起来解析implicitly[Decoder[Option[Custom]]]会使Scala编译器处于影响解析implicitly[Decoder[Option[Float]]]的状态。

为什么会发生这种情况?如何在不从方法参数移动c: ConcreteContext的情况下进行编译?

P.S。这是重现问题的简化代码。实际代码要复杂得多,我需要支持将ConcreteContext作为方法参数传递的情况。

1 个答案:

答案 0 :(得分:1)

这不是一个完整的答案。

我没有任何令人满意的解释为什么提到另一个隐含的可能会导致解决方案退出循环,由mappedEncoder custom2Encoder + custom2Decoder对创建。我只能猜测implicitly[Decoder[Float]]的存在优于floatDecoder优先于custom2...循环。

但是有一个很好的解决方案,可能不是最好但可行的选择 shapeless.Lazy。它有时可以用来代替LowPriority分解来处理这种更可怕的情况。

您只需将mappedDecoder重写为

即可
import shapeless.Lazy

implicit def mappedDecoder[I, O]
  (implicit mapped: MappedEncoding[I, O], 
            decoder: Lazy[Decoder[I]]): Decoder[O] =
    ConcreteDecoder[O](() => mapped.f(decoder.value()))

使原始代码正常工作