隐式的用途是什么以及如何在 scala 中使用它们

  • 本文关键字:scala 是什么 scala implicit
  • 更新时间 :
  • 英文 :


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

许多人可能会发现它是其他帖子的副本,但事实并非如此。是的 不同。

编辑::

大多数时候,">Scala中隐式的用法是什么?"这个问题的回答是">如何编写/使用隐式转换?",">如何使用隐式类型类?"等等。

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

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

到只是

val s = 5 + ":: Well"

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

一些具有更多"裸机"哲学的程序员甚至倾向于不喜欢隐藏这些细节的implicit的存在。

不知何故,">implicits在Scala中的'用途和重要性'是什么?"这个重要的问题不知何故被忽视了,没有答案。

特别是那些对Scala有足够的了解,在某种程度上理解"如何使用implicits"的程序员,有时会被"隐藏的魔法"所困扰,他们对这个问题感到困惑——">隐式有什么特别之处,以至于Scala的创造者甚至选择拥有implicits

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

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

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

我更多地从">implicits的用途是什么(为什么在某些情况下需要implicit)"而不是"如何在某些情况下使用implicit">的角度来回答这个问题(因为可以通过在谷歌上搜索找到答案)。

在解释时,我不知何故最终在某些地方以可互换的方式使用了typeclass。它仍然应该以简单的方式传达预期的信息,但它可能会向误解表明typeclass是相似的。我看不出在不对答案进行大量更改的情况下解决此问题的方法。请记住,typeclass不是一回事。

implicit实际做了什么。

它实际上非常简单,implicit完全符合顾名思义。如果需要查找所述类型的"转到"实例,它将内容标记为相应范围内的"转到"实例/值。

因此,我们可以说,每当需要类型A的"转到"实例时,类型Aimplicit实例/值是类型A的"转到"实例。

要将任何实例/值标记为相应范围内可用的implicitly("转到"),我们需要使用implicit关键字。

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

我们如何在需要时调用implicit实例/值?

召唤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,你和其他任何人都可以定义自己的类来扩展这个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转换为某些implementationMyRestriction(这将确保您的限制得到满足)。

但是看看performMySpecialBehaviour的签名,你不会发现与你真正想要的有任何相似之处。

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

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

用户可以为其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一样传递类型类实例。那么为什么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的值。但这是一个丑陋的黑客,它在编译时不会有效。

但是如果你使用基于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调用的作用域中可用,则代码将无法编译。

这只是一个例子,但它应该足以说明"为什么"和"什么"是 Scala 中implicits的实际用途

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

此示例中的用例对参数类型施加限制,A在范围内具有type classTypeClass[A]的实例。它被称为Context Bound,这些方法通常写成:

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

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

注意::如果您发现答案有任何问题,请发表评论。欢迎任何反馈。

相关内容

  • 没有找到相关文章

最新更新