理解Scala中的隐式

时间:2012-04-29 20:19:09

标签: scala syntax playframework keyword

我正在通过Scala playframework教程,我遇到了这段令我困惑的代码片段:

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

所以我决定调查并遇到this post

我仍然没有得到它。

这有什么区别:

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

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

除了明显的事实,他们有不同的方法名称。

我应该何时使用implicit以及为什么?

6 个答案:

答案 0 :(得分:357)

我将在下面解释implicits的主要用例,但有关详细信息,请参阅relevant chapter of Programming in Scala

隐含参数

方法的最终参数列表可以标记为implicit,这意味着值将从调用它们的上下文中获取。如果范围中没有正确类型的隐式值,则不会编译。由于隐含值必须解析为单个值并避免冲突,因此最好使类型特定于其目的,例如不要求您的方法找到隐式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的隐式值(它还会检查{{1}中的其他一些位置}和B个伴随对象(如果存在)。由于A可以“eta扩展”为def个对象,因此Function也可以。

因此,当找到implicit def xyz(arg: B): A但需要implicit时,编译器将为您插入标记为Double的方法之间的区别。

Int

将与

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

在第二个中我们手动插入了转换;在第一个编译器自动完成相同的操作。由于左侧是类型注释,因此需要进行转换。


关于Play中的第一个片段:

Play文档中的this page解释了操作(另请参阅API docs)。您正在使用

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

apply(block: (Request[AnyContent]) ⇒ Result): Action[AnyContent] 对象(它是同名特征的伴侣)上。

所以我们需要提供一个Function作为参数,可以用

形式写成一个文字
Action

在函数文字中,request => ... 之前的部分是值声明,如果需要,可以标记为=>,就像在任何其他implicit声明中一样。在这里,val 必须标记为request才能进行类型检查,但通过这样做,它将作为隐含值对于函数中可能需要它的任何方法(当然,它也可以显式使用)。在这种特殊情况下,这已经完成,因为Form类上的implicit方法需要隐式bindFromRequest参数。

答案 1 :(得分:33)

警告:明智地包含讽刺! YMMV ...

Luigi's answer完整无误。这个只是为了扩展它的一个例子,你可以如何过度使用 implicits ,因为它在Scala项目中经常发生。实际上,你甚至可以在" Best Practice" 指南中找到它。

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")
}

答案 2 :(得分:6)

为什么以及何时将request参数标记为implicit

您将在动作正文中使用的某些方法具有隐式参数列表,例如,Form.scala定义了一个方法:

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

您不一定会注意到这一点,因为您只需调用myForm.bindFromRequest()您不必明确提供隐式参数。不,您每次遇到需要请求实例的方法调用时都会留下编译器来查找要传递的任何有效候选对象。由于有可用请求,您只需将其标记为implicit

明确将其标记为隐式使用。

你提示编译器使用Play框架发送的请求对象是“OK”(我们给出了名称“request”但在任何需要的地方都可以使用“r”或“req”)“on狡猾的“。

myForm.bindFromRequest()
看到了吗?它不存在,但它 那里!

只是在你需要的每个地方都不需要手动插入它(但你可以明确地传递它,如果你愿意,无论它是否被标记为implicit或者不):

myForm.bindFromRequest()(request)

如果不将其标记为隐式,则必须执行上述操作。将其标记为隐含的您不必。

时应将请求标记为implicit?如果您正在使用声明隐式参数列表期望Request 实例的方法,那么您才真正需要。但为了简单起见,您可以养成标记请求的习惯implicit 始终。这样你就可以编写漂亮的简洁代码。

答案 3 :(得分:3)

此外,在上述情况下,应该有only one隐式函数,其类型为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

答案 4 :(得分:1)

我有一个与您完全相同的问题,我想我应该通过一些非常简单的示例来分享我如何开始理解它(请注意,它仅涵盖常见的用例)。

在Scala中,使用implicit有两个常见用例。

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

示例如下

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

// 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

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

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!

希望这会有所帮助。

答案 5 :(得分:0)

scala中隐式的非常基本的例子。

隐含参数

val value = 10
implicit val multiplier = 3
def multiply(implicit by: Int) = value * by
val result = multiply // implicit parameter wiil be passed here
println(result) // It will print 30 as a result

注意:此处multiplier将隐式传递到函数multiply中。在当前作用域中按类型查找函数调用中缺少的参数,这意味着如果作用域中没有Int类型的隐式变量,则代码不会编译。

隐式转换

implicit def convert(a: Double): Int = a.toInt
val res = multiply(2.0) // Type conversions with implicit functions
println(res)  // It will print 20 as a result

注意:当我们调用传递双精度值的multiply函数时,编译器将尝试在当前作用域中查找转换隐式函数,该函数将Int转换为{ {1}}(作为功能Double接受multiply参数)。如果没有隐式Int函数,则编译器将不会编译代码。