implicit 的用途是什么以及如何在 Scala 中使用它们?

What are the uses of implicits and How to use them in scala?

implicit关键字对于来自Java和CC++等其他语言的程序员来说是一个非常晦涩的东西,所以了解implicit 关键字在 Scala 中非常重要。 implicit 在 Scala 中如何使用?

Many may find it a duplicate of other post but it is not that. It is different.

编辑::

大多数时候,问题“Scala 中隐含函数的用法是什么?”的回答类似于“如何write/use 隐式转换?", "如何使用隐式类型类?" 等等。

对于新的 Scala 程序员(至少是我认识的那些),这样的答案大多数时候给人的印象是 implicits 实际上只是一个 "beautifying" 工具来减少诸如,

val s = (new RichInt(5)).toString + ":: Well"

只是

val s = 5 + ":: Well"

大多数人只是将隐式视为这种缩短工具,如果需要,他们总是可以避免这种工具。

一些具有更多"bare-metal"哲学的程序员甚至不喜欢implicit隐藏这些细节的存在。

不知何故,重要的问题“Scala 中 implicits 的 "use and importance" 是什么?”不知何故被忽略了,也没有得到回答。

特别是了解 Scala 的程序员在某种程度上理解 "how to use implicits" 并且有时会因 "hidden magic" 对这个问题感到疑惑而感到困惑 - “隐式有什么特别之处Scala 的创造者甚至选择 implicits?"

我不太确定 OP 在探索 implicits 作为 Scala 程序员时是否也考虑过这些问题。

令人惊讶的是,我没有在任何容易获得的地方找到这些问题的答案。一个原因是,即使回答 implicits 的实际 "need/benefits" 的一个用例也需要大量解释。

我认为这些问题值得社区关注,以帮助新的 Scala 程序员。我试图简单地解释 implicits 的一个用例。我希望看到更多的人(更有知识的)有兴趣回答这个问题。

I am answering this question more from the perspective of "what are the uses of implicits (why are implicit required in some situations)" instead of "how to use implicit in some of these situations" (as answer to this can be found by searching on google).

While explaining, I somehow ended up using type and class in an interchangeable way at some places. It should still convey the intended information in a simple way but it may indicate to the mis-conception that type and class are similar. I don't see a way to fix that without doing heavy changes in the answer. Just keep in mind that type and class are not same things.

implicit 的实际作用。

其实很简单,implicit顾名思义。如果需要查找所述类型的“转到”实例,它将事物标记为相应范围内的“转到”instance/value。

所以我们可以说 A 类型的 implicit instance/value 是 A 类型的“转到”实例,只要需要“转到“A.

类型的实例

要在相应范围内将任何 instance/value 标记为 implicitly(“转到”),我们需要使用 implicit 关键字。

scala> implicit val i: Int = 5
// i: Int = 5

我们如何在需要的时候召唤implicitinstances/values?

召唤implicit最直接的方法是使用implictly方法。

val gotoInt = implicitly[Int]
// gotoInt: Int = 5

或者我们可以使用 implicit 参数定义我们的 methods 以期望 implicit 实例在其使用范围内的可用性,

def addWithImplictInt(i: Int)(implicit j: Int): Int = i + j

请记住,我们也可以在不指定 implicit 参数的情况下定义相同的方法,

def addWithImplicitInt(i: Int): Int = {
  val implictInt = implicitly[Int]
  i + implictInt
}

请注意,带有 implicit 参数的第一个选择向用户表明 method 需要隐式参数。由于这个原因,implicit参数在大多数情况下应该是选择(例外总是存在)。

为什么我们实际使用implicit

这与我们使用 implicit 值的可能方式有点不同。我们正在谈论为什么我们“实际上”需要使用它们。

答案是帮助编译器确定 type 并帮助我们为问题编写类型安全的代码,否则将导致 运行 次类型比较,我们最终失去所有编译器可以为我们提供的帮助。

考虑以下示例,

假设我们正在使用定义了以下类型的库,

trait LibTrait

case class LibClass1(s: String) extends LibTrait
case class LibClass2(s: String) extends LibTrait
case class LibClass3(s: String) extends LibTrait
case class LibClass4(s: String) extends LibTrait

考虑到这是一个开放的 trait,您和其他任何人都可以定义自己的 classes 来扩展这个 LibTrait

case class YourClass1(s: String) extends LibTrait
case class YourClass2(s: String) extends LibTrait

case class OthersClass1(s: String) extends LibTrait
case class OthersClass2(s: String) extends LibTrait

现在,我们要定义一个 method,它只适用于 LibTrait 的某些实现(只有那些具有某些特定属性的实现才能执行您需要的特殊行为)。

// impl_1
def performMySpecialBehaviour[A <: LibTrait](a): Unit

但是,以上将允许扩展 LibTrait.

的所有内容

一种选择是为所有“支持的”classes 定义方法。但是由于您无法控制 LibTrait 的扩展名,因此您甚至不能这样做(这也不是一个非常优雅的选择)。

另一种选择是为您的方法建模这些“限制”,

trait MyRestriction[A <: LibTrait] {
  def myRestrictedBehaviour(a: A): Unit
}

现在,只有支持此特定行为的 LibTrait 子类型才能实现 MyRestriction

现在,以最简单的方式,您使用此方法定义您的方法,

// impl_2 
def performMySpecialBehaviour(mr: MyRestriction): Unit

因此,现在用户首先必须将他们的 instances 转换为 MyRestrictionimplementation(这将确保您的限制得到满足)。

但是查看 performMySpecialBehaviour 的签名,您不会发现与您实际想要的有任何相似之处。

此外,您的限制似乎绑定到 class 而不是实例本身,因此我们可以继续使用 type class

// impl_3
def performMySpecialBehaviour[A <: LibTrait](a: A, mr: MyRestriction[A]): Unit

用户可以为他们的 class 定义类型-class instance 并将其与您的 method

一起使用
object myRestrictionForYourClass1 extends MyRestriction[YourClass1] {
  def myRestrictedBehaviour(a: A): Unit = ???
}

但是查看 performMySpecialBehaviour 的签名,您不会发现与您实际想要的有任何相似之处。

但是,如果您要考虑使用 implicits,我们可以更清楚地说明用法

// impl_4
def performMySpecialBehaviour[A :< LibTrait](a: A)(implicit ev: MyRestriction[A]): Unit

但我仍然可以像 impl_3 那样传递类型 class 实例。那么为什么 implicit?

是的,那是因为例子题太简单了。让我们添加更多。

请记住,LibTrait 仍可扩展。让我们考虑一下您或您团队中的某个人最终得到了关注,

trait YoutTrait extends LibTrait

case class YourTraitClass1(s: String) extends YoutTrait
case class YourTraitClass2(s: String) extends YoutTrait
case class YourTraitClass3(s: String) extends YoutTrait
case class YourTraitClass4(s: String) extends YoutTrait

注意 YoutTrait 也是一个开放特征。

因此,对于 MyRestriction

,每一个都有自己对应的实例
object myRestrictionForYourTraitClass1 extends MyRestriction[YourTraitClass1] {...}
object myRestrictionForYourTraitClass2 extends MyRestriction[YourTraitClass1] {...}
...
...

你还有另一个方法,它调用 performMySpecialBehaviour

def otherMethod[A <: YoutTrait](a: A): Unit = {
  // do something before
  val mr: MyRestriction[A] = ??????????
  performMySpecialBehaviour(a, mr)
  // do something after
}

现在,您如何选择要提供的 MyRestriction 实例。问题是,您仍然可以通过提供一个 MapClass 作为键和 MyRestriction 实例作为所有 types 的值来以迂回的方式做到这一点。但这是一个丑陋的 hack,它在编译时不会有效。

但是如果你使用基于implictimpl_4,你的相同方法将如下所示,

def otherMethod[A <: YoutTrait](a: A)(implicit ev: MyRestriction[A]): Unit = {
  // do something before
  performMySpecialBehaviour(a)
  // do something after
}

只要 YoutTrait 的所有子类型的 MyRestriction 实例在范围内,它就会工作。否则代码将无法编译。

因此,如果有人添加了 YoutTrait 的新子类型 YourTraitClassXX 并且忘记确保 MyRestriction[YourTraitClassXX] 实例已定义并在任何 otherMethod 正在进行调用,代码将无法编译。

这只是一个示例,但足以说明 implicits 在 Scala 中的实际用途“为什么”和“什么”

关于 implicits 还有很多话要说,但这会使答案太长;它已经是。

此示例中的用例对参数类型 A 进行了限制,以在范围内具有 type class TypeClass[A] 的实例。它被称为 Context Bound,此类方法通常写为,

def restricted[A, B](a: A)(implicit ev: TypeClass[A]): B 

或者,

def restricted[A: TypeClass, B](a: A): B
 

Note:: If you notice any problem with the answer, please comment. Any feedback is welcome.