理解隐含在Scala中

我正在通过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()) } ) } 

所以我决定调查,并遇到这个职位 。

我还是不明白

这有什么区别:

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

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

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

什么时候应该使用implicit ,为什么?

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

含义参数

方法的最后一个参数列表可以标记为implicit ,这意味着这些值将从调用它们的上下文中获取。 如果范围中没有正确types的隐式值,则不会编译。 由于隐式值必须parsing为单个值并避免冲突,因此将types设置为特定目的是个好主意,例如,不要求您的方法find隐式的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" 

隐式转换

当编译器find上下文的错误typesexpression式时,它将查找一个隐式的Functiontypes的值,这将允许它进行types检查。 所以如果需要一个A并且它find一个B ,它将在范围内寻找一个types为B => A的隐式值(它也检查其他地方,如BA伴侣对象,如果存在的话)。 由于def s可以被“eta扩展”到Function对象中, implicit def xyz(arg: B): A也可以。

所以你的方法之间的区别在于,当find一个Double而需要一个Int时,编译器会为你插入标记为implicit那个。

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

将工作相同

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

在第二个我们手动插入了转换; 在第一个编译器自动执行相同的操作。 由于左侧的types注释,转换是必需的。


关于Play的第一个片段:

操作从Play文档的这个页面上解释(另请参阅API文档 )。 你正在使用

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

Action对象(这是同名的特性的伴侣)。

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

 request => ... 

在函数文本中, =>之前的部分是一个值声明,如果需要,可以将其标记为implicit ,就像在任何其他的val声明中一样。 在这里, request 不必被标记为implicit的,因为这样做对于types检查是必要的,但是通过这样做,对于在该函数内可能需要的任何方法来说,它可以作为隐式值来使用(当然,它可以明确地用作好)。 在这种特殊情况下,这是因为Form类的bindFromRequest方法需要一个隐含的Request参数。

警告:明智地包含讽刺! 因人而异…

路易吉的回答是完整而正确的。 这个只是扩展一点,例如你可以光荣地过度使用implicits ,因为它经常发生在Scala项目中。 事实上,你甚至可以在“最佳实践”指南中find它。

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

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

你将在你的动作中使用的一些方法有一个隐含的参数列表 ,比如Form.scala定义了一个方法:

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

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

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

你提示编译器可以使用Play框架发送的请求对象(我们给出名字“request”,但是可以只用“r”或者“req”),在“狡猾的” 。

 myForm.bindFromRequest() 

看见? 它不在那里,但它那里!

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

 myForm.bindFromRequest()(request) 

没有标明它是隐含的,你将不得不这样做。 标记为隐含的,你不必。

何时应该将请求标记为implicit ? 如果您正在使用声明隐含参数列表的方法来期待实例的请求 ,那么您只需要真正需要。 但是为了简单起见,你可以习惯于总是标记implicit的请求。 这样你就可以写出美丽的简洁的代码。

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