我在学习Scala游戏框架教程时,遇到了这样一段让我困惑的代码:

def newTask = Action { implicit request =>
taskForm.bindFromRequest.fold(
        errors => BadRequest(views.html.index(Task.all(), errors)),
        label => {
          Task.create(label)
          Redirect(routes.Application.tasks())
        } 
  )
}

所以我决定调查一下,看到了这个帖子。

我还是不明白。

这两者的区别是什么:

implicit def double2Int(d : Double) : Int = d.toInt

and

def double2IntNonImplicit(d : Double) : Int = d.toInt

除了它们有不同的方法名之外。

什么时候应该使用隐式,为什么?


当前回答

我将在下面解释隐式的主要用例,但要了解更多细节,请参阅Scala编程的相关章节。

隐式参数

方法的最后一个参数列表可以标记为隐式的,这意味着这些值将从调用它们的上下文中获取。如果作用域中没有正确类型的隐式值,则不会编译。因为隐式值必须解析为单个值,为了避免冲突,让类型特定于它的目的是一个好主意,例如,不需要你的方法找到一个隐式Int!

例子:

  // probably in a library
class Prefixer(val prefix: String)
def addPrefix(s: String)(implicit p: Prefixer) = p.prefix + s

  // then probably in your application
implicit val myImplicitPrefixer = new Prefixer("***")
addPrefix("abc")  // returns "***abc"

隐式转换

当编译器为上下文找到错误类型的表达式时,它将寻找允许它进行类型检查的类型的隐式Function值。因此,如果A是必需的,并且它找到了B,它将在作用域中寻找类型为B => A的隐式值(它还检查其他一些地方,如B和A伴生对象,如果它们存在的话)。由于defs可以被“扩展”为Function对象,隐式def xyz(arg: B): A也可以。

因此,你的方法之间的区别是,标记为隐式的方法将被编译器插入,当你找到一个Double,但Int是必需的。

implicit def doubleToInt(d: Double) = d.toInt
val x: Int = 42.0

会和

def doubleToInt(d: Double) = d.toInt
val x: Int = doubleToInt(42.0)

在第二种方法中,我们手动插入转换;在第一种情况下,编译器自动执行相同的操作。转换是必需的,因为左边有类型注释。


关于Play的第一个片段:

在这个页面上,Play文档解释了操作(参见API文档)。你正在使用

apply(block: (Request[AnyContent]) ⇒ Result): Action[AnyContent]

Action对象(它是同名trait的同伴)。

所以我们需要提供一个Function作为参数,它可以写成这样的文字形式

request => ...

In a function literal, the part before the => is a value declaration, and can be marked implicit if you want, just like in any other val declaration. Here, request doesn't have to be marked implicit for this to type check, but by doing so it will be available as an implicit value for any methods that might need it within the function (and of course, it can be used explicitly as well). In this particular case, this has been done because the bindFromRequest method on the Form class requires an implicit Request argument.

其他回答

我将在下面解释隐式的主要用例,但要了解更多细节,请参阅Scala编程的相关章节。

隐式参数

方法的最后一个参数列表可以标记为隐式的,这意味着这些值将从调用它们的上下文中获取。如果作用域中没有正确类型的隐式值,则不会编译。因为隐式值必须解析为单个值,为了避免冲突,让类型特定于它的目的是一个好主意,例如,不需要你的方法找到一个隐式Int!

例子:

  // probably in a library
class Prefixer(val prefix: String)
def addPrefix(s: String)(implicit p: Prefixer) = p.prefix + s

  // then probably in your application
implicit val myImplicitPrefixer = new Prefixer("***")
addPrefix("abc")  // returns "***abc"

隐式转换

当编译器为上下文找到错误类型的表达式时,它将寻找允许它进行类型检查的类型的隐式Function值。因此,如果A是必需的,并且它找到了B,它将在作用域中寻找类型为B => A的隐式值(它还检查其他一些地方,如B和A伴生对象,如果它们存在的话)。由于defs可以被“扩展”为Function对象,隐式def xyz(arg: B): A也可以。

因此,你的方法之间的区别是,标记为隐式的方法将被编译器插入,当你找到一个Double,但Int是必需的。

implicit def doubleToInt(d: Double) = d.toInt
val x: Int = 42.0

会和

def doubleToInt(d: Double) = d.toInt
val x: Int = doubleToInt(42.0)

在第二种方法中,我们手动插入转换;在第一种情况下,编译器自动执行相同的操作。转换是必需的,因为左边有类型注释。


关于Play的第一个片段:

在这个页面上,Play文档解释了操作(参见API文档)。你正在使用

apply(block: (Request[AnyContent]) ⇒ Result): Action[AnyContent]

Action对象(它是同名trait的同伴)。

所以我们需要提供一个Function作为参数,它可以写成这样的文字形式

request => ...

In a function literal, the part before the => is a value declaration, and can be marked implicit if you want, just like in any other val declaration. Here, request doesn't have to be marked implicit for this to type check, but by doing so it will be available as an implicit value for any methods that might need it within the function (and of course, it can be used explicitly as well). In this particular case, this has been done because the bindFromRequest method on the Form class requires an implicit Request argument.

为什么以及何时应该将请求参数标记为隐式:

您将在操作主体中使用的一些方法具有隐式参数列表,例如Form。Scala定义了一个方法:

def bindFromRequest()(implicit request: play.api.mvc.Request[_]): Form[T] = { ... }

你不一定会注意到这一点,因为你只需要调用myForm.bindFromRequest(),你不必显式地提供隐式参数。不,您让编译器在每次遇到需要请求实例的方法调用时寻找要传入的任何有效候选对象。由于您确实有一个可用的请求,您所需要做的就是将其标记为隐式。

您显式地将其标记为可用于隐式使用。

你暗示编译器,它是“OK”使用的请求对象发送的Play框架(我们给了名字“请求”,但可以只使用“r”或“req”)在任何需要的地方,“偷偷地”。

myForm.bindFromRequest()

看到了吗?它不在那里,但它就在那里!

它发生的时候,你不需要在每个需要它的地方手动插入它(但你可以显式地传递它,如果你愿意,不管它是否标记为隐式):

myForm.bindFromRequest()(request)

如果不将其标记为隐式,则必须执行上述操作。你不需要把它标记为隐式。

什么时候应该将请求标记为隐式?只有在使用声明隐式参数列表期望Request实例的方法时才真正需要这样做。但是为了保持简单,您可以养成总是将请求标记为隐式的习惯。这样你就可以写出漂亮简洁的代码。

我和你有同样的问题,我想我应该通过几个非常简单的例子来分享我是如何开始理解它的(注意,它只涵盖了常见的用例)。

在Scala中有两种使用隐式的常见用例。

在变量上使用它 在函数上使用它

例子如下

在变量上使用它。如您所见,如果在最后一个参数列表中使用隐式关键字,那么将使用最接近的变量。

// Here I define a class and initiated an instance of this class
case class Person(val name: String)
val charles: Person = Person("Charles")

// Here I define a function
def greeting(words: String)(implicit person: Person) = person match {
  case Person(name: String) if name != "" => s"$name, $words"
    case _ => "$words"
}

greeting("Good morning") // Charles, Good moring

val charles: Person = Person("")
greeting("Good morning") // Good moring

在函数上使用它。如您所见,如果在函数上使用隐式,则将使用最接近的类型转换方法。

val num = 10 // num: Int (of course)

// Here I define a implicit function
implicit def intToString(num: Int) = s"$num -- I am a String now!"

val num = 10 // num: Int (of course). Nothing happens yet.. Compiler believes you want 10 to be an Int

// Util...
val num: String = 10 // Compiler trust you first, and it thinks you have `implicitly` told it that you had a way to covert the type from Int to String, which the function `intToString` can do!
// So num is now actually "10 -- I am a String now!"
// console will print this -> val num: String = 10 -- I am a String now!

希望这能有所帮助。

警告:含有讽刺的明智!YMMV……

路易吉的回答是完整而正确的。这篇文章只是通过一个示例对它进行了一点扩展,该示例展示了如何光荣地过度使用隐式,因为这种情况在Scala项目中经常发生。实际上,你甚至可以在“最佳实践”指南中找到它。

object HelloWorld {
  case class Text(content: String)
  case class Prefix(text: String)

  implicit def String2Text(content: String)(implicit prefix: Prefix) = {
    Text(prefix.text + " " + content)
  }

  def printText(text: Text): Unit = {
    println(text.content)
  }

  def main(args: Array[String]): Unit = {
    printText("World!")
  }

  // Best to hide this line somewhere below a pile of completely unrelated code.
  // Better yet, import its package from another distant place.
  implicit val prefixLOL = Prefix("Hello")
}

同样,在上述情况下,应该只有一个隐式函数,其类型为double => Int。否则,编译器会感到困惑,无法正确编译。

//this won't compile

implicit def doubleToInt(d: Double) = d.toInt
implicit def doubleToIntSecond(d: Double) = d.toInt
val x: Int = 42.0