Scala:为什么在函数参数上使用隐式?

dat*_*ser 8 convention scala implicit

我有以下功能:

def getIntValue(x: Int)(implicit y: Int ) : Int = {x + y}
Run Code Online (Sandbox Code Playgroud)

我到处都看到上述声明.我理解上面的功能正在做什么.这是一个currying函数,它有两个参数.如果省略第二个参数,它将调用返回int的隐式定义.所以我认为这与定义参数的默认值非常相似.

implicit val temp = 3

scala> getIntValue(3)
res8: Int = 6
Run Code Online (Sandbox Code Playgroud)

我想知道上述声明有什么好处?

Nik*_*kov 8

在您的具体示例中,没有实际的好处.事实上,对此任务使用implicits只会模糊您的代码.

implicits的标准用例是Type Class Pattern.我会说这是唯一实用的用例.在所有其他情况下,最好明确说明事情.

以下是类型类的示例:

// A typeclass
trait Show[a] {
  def show(a: a): String
}

// Some data type
case class Artist(name: String)

// An instance of the `Show` typeclass for that data type
implicit val artistShowInstance =
  new Show[Artist] {
    def show(a: Artist) = a.name
  }

// A function that works for any type `a`, which has an instance of a class `Show`
def showAListOfShowables[a](list: List[a])(implicit showInstance: Show[a]): String =
  list.view.map(showInstance.show).mkString(", ")

// The following code outputs `Beatles, Michael Jackson, Rolling Stones`
val list = List(Artist("Beatles"), Artist("Michael Jackson"), Artist("Rolling Stones"))
println(showAListOfShowables(list))
Run Code Online (Sandbox Code Playgroud)

这种模式源于一种名为Haskell的函数式编程语言,并且比编写模块化和解耦软件的标准OO实践更加实用.它的主要好处是它允许您使用新功能扩展已存在的类型而不更改它们.

有很多未提及的细节,如语法糖,def实例等.这是一个很大的主题,幸运的是它在整个网络上有很大的覆盖面.只需谷歌"scala类型".

  • http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html (3认同)

Tha*_*Don 7

这是我的"务实"答案:你通常使用currying作为更多的"约定"而不是其他任何有意义的东西.当您的最后一个参数恰好是"按名称调用"参数时(例如:),它非常方便: => Boolean:

def transaction(conn: Connection)(codeToExecuteInTransaction : => Boolean) = {

   conn.startTransaction  // start transaction

   val booleanResult = codeToExecuteInTransaction //invoke the code block they passed in

  //deal with errors and rollback if necessary, or commit
  //return connection to connection pool
}
Run Code Online (Sandbox Code Playgroud)

这就是说"我有一个被调用的函数transaction,它的第一个参数是一个连接,它的第二个参数是一个代码块".

这允许我们像这样使用这种方法(使用"我可以使用花括号而不是括号规则"):

transaction(myConn) {

   //code to execute in a transaction
  //the code block's last executable statement must be a Boolean as per the second
  //parameter of the transaction method

}
Run Code Online (Sandbox Code Playgroud)

如果你没有讨论那种交易方法,那么这样做看起来很不自然:

transaction(myConn, {

   //code block

})
Run Code Online (Sandbox Code Playgroud)

怎么样implicit?是的,它看起来像一个非常模糊的构造,但是你会在一段时间后习惯它,而隐式函数的好处是它们有范围规则.所以这意味着生产,你可以定义一个隐式函数来从PROD数据库获取数据库连接,但在集成测试中你将定义一个隐式函数,它将取代PROD版本,它将用于获取连接从DEV数据库中替代用于测试.

作为一个例子,我们如何向事务方法添加隐式参数?

def transaction(implicit conn: Connection)(codeToExecuteInTransaction : => Boolean) = {

}
Run Code Online (Sandbox Code Playgroud)

现在,假设我有一个隐函数的地方在返回一个连接,像这样我的代码库:

def implicit getConnectionFromPool() : Connection = { ...}
Run Code Online (Sandbox Code Playgroud)

我可以像这样执行事务方法:

transaction {
   //code to execute in transaction
}
Run Code Online (Sandbox Code Playgroud)

而Scala会将其翻译为:

transaction(getConnectionFromPool) {
  //code to execute in transaction
}
Run Code Online (Sandbox Code Playgroud)

总之,Implicits是一种非常好的方法,当参数在99%的时间在您使用函数的任何地方都相同时,不必让开发人员为必需参数提供值.在1%的情况下,您需要一个不同的Connection,您可以通过传入一个值来提供自己的连接,而不是让Scala找出哪个隐式函数提供了值.


Mad*_*doc 5

除了你的例子之外,还有很多好处。我只给一个;同时,这也是在某些场合可以使用的技巧。

想象一下,您创建了一个特征,它是其他值的通用容器,例如列表、集合、树或类似的东西。

trait MyContainer[A] {
  def containedValue:A
}
Run Code Online (Sandbox Code Playgroud)

现在,在某些时候,您会发现迭代所包含值的所有元素很有用。当然,只有当包含的值是可迭代类型时,这才有意义。

但是因为您希望您的类对所有类型都有用,所以您不想限制A为某种Seq类型、 orTraversable或类似的类型。A基本上,您需要一个方法来表示:“只有在属于某种类型时才能调用我Seq。” 如果有人调用它,比如说,,MyContainer[Int]那应该会导致编译错误。

这是可能的。您需要的是一些序列类型的证据A您可以使用 Scala 和隐式参数来做到这一点:

trait MyContainer[A] {
  def containedValue:A
  def aggregate[B](f:B=>B)(implicit ev:A=>Seq[B]):B =
    ev(containedValue) reduce f
}
Run Code Online (Sandbox Code Playgroud)

因此,如果您在 a 上调​​用此方法MyContainer[Seq[Int]],编译器将查找隐式Seq[Int]=>Seq[B]. 对于编译器来说,解决这个问题非常简单。因为有一个名为 的全局隐式函数identity,并且它始终在作用域内。它的类型签名类似于:A=>A

它只是返回传递给它的任何参数。

我不知道这个模式怎么称呼。(任何人都可以帮忙吗?)但我认为这是一个巧妙的技巧,有时会派上用场。如果您查看 的方法签名,您可以在 Scala 库中看到一个很好的示例Seq.sum。在 的情况下sum,使用另一种隐式参数类型;在这种情况下,隐式参数证明所包含的类型是数字,因此可以根据所有包含的值构建总和。

这不是隐式的唯一用途,当然也不是最突出的,但我想说这是一个值得尊敬的提及。:-)