implicit
关键字对于来自Java和C
和C++
等其他语言的程序员来说是一件非常晦涩的事情,因此了解Scala中的implicit
关键字是很重要。 implicit
在Scala中如何使用?
许多人可能会发现它与其他帖子重复,但事实并非如此。它是 不同。
编辑::
在大多数情况下,问题“ Scala中的隐式用法是什么?”在诸如“ 如何编写/使用隐式转换?”的意义上得到回答。 >”,“ 如何使用隐式类型类?”等等。
对于新的Scala程序员(至少我知道的人),大多数时候这样的回答都给人一种印象,implicits
实际上只是一种“美化”工具,可以减少诸如此类的事情,
val s = (new RichInt(5)).toString + ":: Well"
公正
val s = 5 + ":: Well"
大多数情况下,隐含方法只不过是一种缩短工具,如果需要,他们总是可以避免的。
一些具有“裸机”哲学的程序员甚至倾向于不喜欢隐藏这些细节的implicit
的存在。
不知何故,重要的问题“ Scala中implicits
的{用途和重要性”是什么?”却被忽略并没有得到答案。
特别是程序员,他们足够了解Scala从而在某种程度上理解“如何使用implicits
”,并且有时会因“隐藏的魔术”而对以下问题感到困惑:“ 隐式函数有什么特别之处,以至于Scala的创建者甚至选择拥有implicits
?”
我不确定OP在作为Scala程序员探索implicits
时是否也考虑过这些问题。
令人惊讶的是,我在任何容易找到的地方都找不到这些问题的答案。原因之一是,即使针对implicits
的实际“需求/好处”回答一个用例,也需要大量解释。
我认为这些问题值得社区帮助新的Scala程序员关注。我试图简单地解释implicits
的一种用例。我希望看到更多的人(知识渊博的人)对此有兴趣。
答案 0 :(得分:2)
我从“
implicits
的用途是什么(在某些情况下为什么需要implicit
”的角度回答这个问题,而不是 “在某些情况下如何使用implicit
” (因为可以通过在Google上搜索找到答案)。在解释时,我最终还是在某些地方以可互换的方式使用了
type
和class
。它仍然应该以简单的方式传达预期的信息,但是可能会误解type
和class
相似。如果不对答案进行重大更改,我看不出解决此问题的方法。请记住,type
和class
是不同的东西。
implicit
的实际作用。
实际上非常简单,implicit
确实按照其名称所暗示的那样工作。如果需要寻找所述类型的“执行”实例,它将标记为相应范围内的“执行”实例/值。
因此我们可以说,只要需要“转到”实例,implicit
类型的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
参数定义implicit
,以期望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
的值的implict
来实现此目的。但这是一个丑陋的技巧,在编译时不会有效。
但是如果您使用基于impl_4
的{{1}},则相同的方法将如下所示,
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
的范围内可用进行调用,代码将无法编译。
这只是一个示例,但足以显示“为什么”和“什么”是Scala中implicits
的实际用法
关于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
注意::如果您发现答案有任何问题,请发表评论。欢迎任何反馈。