implicit 的用途是什么以及如何在 Scala 中使用它们?
What are the uses of implicits and How to use them in scala?
implicit
关键字对于来自Java和C
和C++
等其他语言的程序员来说是一个非常晦涩的东西,所以了解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
我们如何在需要的时候召唤implicit
instances/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
转换为 MyRestriction
的 implementation
(这将确保您的限制得到满足)。
但是查看 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
实例。问题是,您仍然可以通过提供一个 Map
以 Class
作为键和 MyRestriction
实例作为所有 types
的值来以迂回的方式做到这一点。但这是一个丑陋的 hack,它在编译时不会有效。
但是如果你使用基于implict
的impl_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.
implicit
关键字对于来自Java和C
和C++
等其他语言的程序员来说是一个非常晦涩的东西,所以了解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 areimplicit
required in some situations)" instead of "how to useimplicit
in some of these situations" (as answer to this can be found by searching on google).While explaining, I somehow ended up using
type
andclass
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 thattype
andclass
are similar. I don't see a way to fix that without doing heavy changes in the answer. Just keep in mind thattype
andclass
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
我们如何在需要的时候召唤implicit
instances/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
转换为 MyRestriction
的 implementation
(这将确保您的限制得到满足)。
但是查看 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
实例。问题是,您仍然可以通过提供一个 Map
以 Class
作为键和 MyRestriction
实例作为所有 types
的值来以迂回的方式做到这一点。但这是一个丑陋的 hack,它在编译时不会有效。
但是如果你使用基于implict
的impl_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.