为什么比隐式对象更喜欢隐式 val

Why prefer implicit val over implicit object

当询问有关隐式的问题时,与答案一起给出的常见建议/建议/意见 (或者有时这就是答案本身) 是使用 implicit vals 使用 显式类型签名 而不是使用 implicit objects.

但是,这背后的原因是什么?

"TL;DR;" 原因是带有显式类型签名的 implici val 具有您想要的确切类型,而 implicit object 具有另一种类型。

说明为什么这可能是一个问题的最好方法是用一个简单的例子:

// Having the following definitions.

trait SomeTrait[T] {
  def f: T
}

trait ExtendedTrait[T] extends SomeTrait[T] {
  def g: T
}

implicit val SomeStringVal: SomeTrait[String] = new SomeTrait[String] {
  override def f: String = "from val f"
}

implicit val ExtendedStringVal: ExtendedTrait[String] = new ExtendedTrait[String] {
  override def f: String = "from extended val f"
  override def g: String = "from extended val g"
}

implicit object ExtendedStringObject extends ExtendedTrait[String] {
  override def f: String = "from extended obj f"
  override def g: String = "from extended obj g"
}

// What will be the result of:
implicitly[SomeTrait[String]].f

记住:

If there are several eligible arguments which match the implicit parameter's type, the most specific one will be chosen using the rules of static overloading resolution.

那么,答案是:"from extended obj f"
上面的(有点意外)结果,是因为对象有自己的类型(单例类型ExtendedStringObject.typeExtendedTrait[String]的子类型) ,因此,它比其他的更“具体”

现在,这可能成为问题的原因是大多数人都没有意识到 object 有自己的类型,而且它比他们认为的更具体。尽管如此,这也可能使 object 在您不希望它被选中时被选中;例如:

// If instead we only had this:

implicit val ExtendedStringVal: ExtendedTrait[String] = new ExtendedTrait[String] {
  override def f: String = "from extended val f"
  override def g: String = "from extended val g"
}

implicit object ExtendedStringObject extends SomeTrait[String] {
  override def f: String = "from extended obj f"
}

// Then what will be the result of:
implicitly[SomeTrait[String]].f

在这种情况下,我们将有一个“模糊的隐式值”异常;因为这两个选项同样 具体 .


规则是通用的吗?

没有

与软件工程中的大多数事情一样,没有什么是通用的。
在某些情况下,使用具有显式类型签名的 implicit valnot possible (for example, because the type is not writable in source code even if the compiler knows it exists) or it would not produce the correct result whereas an implicit object would do.
一个简单的例子是:

trait A {
  type X
}

object A {
  type Aux[XX] = A { type X = XX }
}

class B extends A {
  type X = T

  class T
}

implicit object b extends B
implicit object b1 extends B

这样你就可以要求 implicitly[A.Aux[b.T]],而使用 implicit val b: B = new B {} 是行不通的。
(代码运行 here full context ).

但是,可以说那些是罕见的 (高级) 案例。因此,这可以被认为是一个很好的指导方针。