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

时间:2014-05-08 15:27:19

标签: scala implicit convention

我有以下功能:

def getIntValue(x: Int)(implicit y: Int ) : Int = {x + y}

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

implicit val temp = 3

scala> getIntValue(3)
res8: Int = 6

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

3 个答案:

答案 0 :(得分:8)

在您的具体示例中,没有实际的好处。事实上,对此任务使用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))

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

未提及大量细节,如语法糖,def实例等。这是一个很大的主题,幸运的是它在整个网络上都有很好的报道。只需google for" scala type class"。

答案 1 :(得分:6)

这是我的"实用主义"回答:你通常使用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
}

这说的是"我有一个名为transaction的函数,它的第一个参数是一个Connection,它的第二个参数是一个代码块"。

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

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

}

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

transaction(myConn, {

   //code block

})

implicit怎么样?是的,它看起来像一个非常模糊的构造,但是你会在一段时间后习惯它,而隐式函数的优点是它们具有范围规则。因此,对于生产而言,您可以定义一个隐式函数来从PROD数据库获取该数据库连接,但是在集成测试中,您将定义一个将取代PROD版本的隐式函数,并且它将用于获取来自DEV数据库的连接,而不是用于您的测试。

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

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

}

现在,假设我的代码库中有一个隐式函数 somewhere ,它返回一个Connection,如下所示:

def implicit getConnectionFromPool() : Connection = { ...}

我可以这样执行交易方法:

transaction {
   //code to execute in transaction
}

并且Scala会将其转换为:

transaction(getConnectionFromPool) {
  //code to execute in transaction
}

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

答案 2 :(得分:4)

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

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

trait MyContainer[A] {
  def containedValue:A
}

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

但是因为您希望您的课程对所有类型都有用,您不希望将A限制为Seq类型,或Traversable,或类似的任何类型。 基本上,您需要一种方法:“我只能在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
}

因此,如果在MyContainer[Seq[Int]]上调用此方法,编译器将查找隐式Seq[Int]=>Seq[B]。 这对于编译器来说非常简单。 因为有一个称为identity的全局隐式函数,它始终在范围内。 它的类型签名类似于:A=>A

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

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

这不是唯一使用含义,当然不是最突出的,但我会说这是一个值得一提的。 : - )