首页 文章

理解Scala中的隐式

提问于
浏览
266

我正在通过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 ?为什么?

4 回答

  • 29

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

    Implicit parameters

    方法的最终参数列表可以标记为 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"
    

    Implicit conversions

    当编译器为上下文找到错误类型的表达式时,它将查找允许其进行类型检查的类型的隐式 Function 值 . 因此,如果需要 A 并找到 B ,它将在范围内查找类型为 B => A 的隐式值(它还会检查其他位置,如 BA 伴随对象(如果存在)) . 由于 def 可以"eta-expanded"到 Function 对象, implicit def xyz(arg: B): A 也可以 .

    因此,您的方法之间的区别在于,当找到 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)
    

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


    关于Play的第一个片段:

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

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

    Action 对象上(它是同名特征的伴侣) .

    所以我们需要提供一个Function作为参数,可以在表单中写成文字

    request => ...
    

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

  • 345

    WARNING: 明智地包含讽刺!因人而异...

    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

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

    您将在动作的主体中使用的一些方法具有 implicit parameter list 之类的,例如,Form.scala定义了一个方法:

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

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

    您明确将其标记为可用于隐式使用 .

    你暗示编译器使用Play框架发送的请求对象(我们给出的名称为“request”,但可能只使用了“r”或“req”),“在狡猾的地方”是“OK” .

    myForm.bindFromRequest()
    

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

    它只是在没有你需要在每个需要的地方手动插入它的情况下发生(但你可以明确地传递它,如果你愿意,无论它是否标记为 implicit ):

    myForm.bindFromRequest()(request)
    

    如果不将其标记为隐式,您将 have to 执行上述操作 . 将其标记为隐含的您不必 .

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

  • 5

    此外,在上述情况下,应该有 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
    

相关问题