scala - 隐含的用途是什么以及如何在scala中使用它们?
问题描述
对于implicit
来自 Java 和其他语言的程序员来说,关键字是一个非常晦涩的东西C
,C++
因此了解implicit
Scala 中的关键字非常重要。implicit
在 Scala 中如何使用?
许多人可能会发现它与其他帖子重复,但事实并非如此。那不一样。
编辑 ::
大多数时候,“ Scala 中隐式函数的用法是什么? ”这个问题的答案是“如何编写/使用隐式转换? ”、“如何使用隐式类型类? ”等等。
对于新的 Scala 程序员(至少我认识的那些),大多数时候这样的答案给人的印象implicits
实际上只是一个“美化”工具来减少诸如,
val s = (new RichInt(5)).toString + ":: Well"
只是
val s = 5 + ":: Well"
大多数人只是将隐式视为这种缩短工具,如果需要,他们总是可以避免。
一些具有更多“裸机”哲学的程序员甚至倾向于不喜欢implicit
隐藏这些细节的存在。
不知何故,“ Scala中的“用途和重要性”是implicits
什么?”这个重要问题不知何故被忽视和回答了。
implicits
特别是那些对 Scala 有足够了解的程序员,他们在某种程度上理解了“如何使用”,并且有时会被“隐藏的魔法”所困扰——“ Scala 的创建者甚至选择拥有的隐式有什么特别之处implicits
? ”
我不太确定 OP 在探索implicits
Scala 程序员时是否也考虑过这些问题。
令人惊讶的是,我没有在任何容易获得的地方找到这些问题的答案。一个原因是,即使回答其中一个用例的实际“需求/好处”,implicits
也需要大量解释。
我认为这些问题值得社区关注,以帮助新的 Scala 程序员。我试图简单地解释implicits
. 我希望看到更多人(知识渊博)有兴趣回答这个问题。
解决方案
我更多地从“有什么用途
implicits
(为什么implicit
在某些情况下需要) ”而不是“如何在implicit
某些情况下使用”的角度来回答这个问题(可以通过在谷歌上搜索找到答案) .在解释时,我不知何故最终在某些地方以可互换的方式使用
type
和class
。它仍然应该以一种简单的方式传达预期的信息,但它可能会向误解表明它们是相似的type
。class
如果不对答案进行重大更改,我看不到解决此问题的方法。请记住,这type
不是class
同一件事。
implicit
实际上是做什么的。
它实际上非常简单,implicit
正如其名称所暗示的那样。如果需要查找所述类型的“转到”实例,它会将事物标记为相应范围内的“转到”实例/值。
所以我们可以说,只要需要type的“go to”实例,type的implicit
实例/值就是 type的“go to”实例。A
A
A
要将任何实例/值标记为implicitly
(“转到”)在相应范围内可用,我们需要使用implicit
关键字。
scala> implicit val i: Int = 5
// i: Int = 5
我们如何implicit
在需要时调用实例/值?
召唤一个最直接的方法implicit
是使用implictly
方法。
val gotoInt = implicitly[Int]
// gotoInt: Int = 5
或者我们可以定义我们的methods
withimplicit
参数以期望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
,您和其他任何人都可以定义自己的类来扩展它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
。
一种选择是为所有“支持的”类定义方法。但是由于您不控制 的扩展LibTrait
,您甚至不能这样做(这也不是一个非常优雅的选择)。
另一种选择是为您的方法建模这些“限制”,
trait MyRestriction[A <: LibTrait] {
def myRestrictedBehaviour(a: A): Unit
}
现在,只有LibTrait
支持这种特定行为的子类型才能提出MyRestriction
.
现在,以最简单的方式,您使用它定义您的方法,
// impl_2
def performMySpecialBehaviour(mr: MyRestriction): Unit
因此,现在用户首先必须将其转换instances
为一些implementation
(MyRestriction
这将确保满足您的限制)。
但是看performMySpecialBehaviour
你的签名不会发现你真正想要的有任何相似之处。
此外,您的限制似乎是绑定到class
实例本身而不是实例本身,因此我们可以继续type class
使用。
// impl_3
def performMySpecialBehaviour[A <: LibTrait](a: A, mr: MyRestriction[A]): Unit
instance
用户可以为他们定义类型类class
并将其与您的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 中一样传递类型类实例。那为什么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
based impl_4
,您的相同方法将如下所示,
def otherMethod[A <: YoutTrait](a: A)(implicit ev: MyRestriction[A]): Unit = {
// do something before
performMySpecialBehaviour(a)
// do something after
}
只要MyRestriction
所有子类型的实例YoutTrait
都在范围内,它就可以工作。否则代码将无法编译。
因此,如果有人添加了一个新的子类型YourTraitClassXX
并YoutTrait
忘记确保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
注意::如果您发现答案有任何问题,请发表评论。欢迎任何反馈。