理解隐含在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式时,它将查找一个隐式的Function
types的值,这将允许它进行types检查。 所以如果需要一个A
并且它find一个B
,它将在范围内寻找一个types为B => A
的隐式值(它也检查其他地方,如B
和A
伴侣对象,如果存在的话)。 由于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