【问题标题】:Understanding implicit in Scala理解 Scala 中的隐式
【发布时间】:2012-04-29 20:19:09
【问题描述】:

我在学习 Scala 游戏框架教程时遇到了这段让我感到困惑的 sn-p 代码:

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,为什么?

【问题讨论】:

标签: scala syntax playframework keyword


【解决方案1】:

我将在下面解释隐式的主要用例,但更多详细信息请参阅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 类型的隐式值(它还会检查其他一些地方,例如BA 伴随对象,如果它们存在)。由于defs 可以“扩展”为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 中的第一个 sn-p:

操作在 Play 文档中的 this page 上进行了说明(另请参阅 API docs)。你正在使用

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

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

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

request => ...

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

【讨论】:

  • 感谢您的回复。第21章的链接真的很棒。欣赏它。
  • 只是补充一下,下面的视频很好地解释了implicits以及scala的一些其他特性youtube.com/watch?v=IobLWVuD-CQ
  • 跳到上面视频中的24:25(对于那些不想听55分钟的人)
【解决方案2】:

警告: 谨慎包含讽刺! YMMV...

Luigi's answer 完整且正确。这只是通过一个示例来扩展它,说明如何过度使用 implicits,因为它在 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")
}

【讨论】:

  • 哈哈。很好的幽默感。
  • 我很欣赏这种幽默。这种事情是我多年前停止尝试学习 Scala 并且现在才重新开始学习的原因之一。我从不知道我正在查看的代码中的一些(许多)隐式来自哪里。
【解决方案3】:

在 scala 中隐式工作为

转换器

参数值注入器

扩展方法

隐式有一些用途

  1. 隐式类型转换:它将产生错误的赋值转换为预期类型

    val x :String = "1"
    
    val y:Int = x
    

String 不是 Int 的 子类型,因此在第 2 行会发生错误。为了解决错误,编译器将在具有隐式关键字并将 String 作为参数并返回 Int

所以

implicit def z(a:String):Int = 2

val x :String = "1"

val y:Int = x // compiler will use z here like val y:Int=z(x)

println(y) // result 2  & no error!
  1. 隐式接收器转换:我们一般通过接收器调用对象的属性,例如。方法或变量。因此,要由接收者调用任何属性,该属性必须是该接收者的类/对象的成员。

     class Mahadi{
    
     val haveCar:String ="BMW"
    
     }
    

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

这里 ma​​hadi.haveTv 会产生错误。因为 scala 编译器会首先寻找 haveTv 属性给 ma​​hadi 接收器。它不会找到。其次,它将在具有 隐式关键字 的范围内寻找一个方法,该方法以 Mahadi 对象 作为参数并返回 Johnny 对象。但是这里没有。所以它会产生错误。但是下面的没问题。

class Mahadi{

val haveCar:String ="BMW"

}

class Johnny{

val haveTv:String = "Sony"

}

val mahadi = new Mahadi

implicit def z(a:Mahadi):Johnny = new Johnny

mahadi.haveTv // compiler will use z here like new Johnny().haveTv

println(mahadi.haveTv)// result Sony & no error
  1. 隐式参数注入:如果我们调用一个方法并且不传递它的参数值,就会导致错误。 scala 编译器的工作方式是这样的——首先会尝试传递值,但它不会得到参数的直接值。

     def x(a:Int)= a
    
     x // ERROR happening
    

其次,如果参数有任何隐式关键字,它将在 范围 中查找具有 相同类型 值的任何 val。如果没有得到它会导致错误。

def x(implicit a:Int)= a

x // error happening here

为了解决这个问题,编译器会寻找一个 int 类型的 implicit val,因为参数 aimplicit 关键字

def x(implicit a:Int)=a

implicit val z:Int =10

x // compiler will use implicit like this x(z)
println(x) // will result 10 & no error.

另一个例子:

def l(implicit b:Int)

def x(implicit a:Int)= l(a)

我们也可以这样写-

def x(implicit a:Int)= l

因为l有一个隐式参数并且在方法x的主体范围内,有一个隐式局部变量(参数是局部变量) ax的参数,所以在x的主体 方法方法签名l的隐含参数值x方法的局部隐含变量(参数)a隐含归档。

所以

 def x(implicit a:Int)= l

将像这样在编译器中

def x(implicit a:Int)= l(a)

另一个例子:

def c(implicit k:Int):String = k.toString

def x(a:Int => String):String =a

x{
x => c
}

这会导致错误,因为 x{x=>c} 中的 c 需要在参数中显式传递值或在范围内隐式传递值强>。

所以当我们调用方法x

时,我们可以使函数字面量的parameter显式implicit
x{
implicit x => c // the compiler will set the parameter of c like this c(x)
}

这已经用于Play-Framework的action method

in view folder of app the template is declared like
@()(implicit requestHreader:RequestHeader)

in controller action is like

def index = Action{
implicit request =>

Ok(views.html.formpage())  

}

如果你没有明确提到请求参数是隐式的,那么你一定是被写了-

def index = Action{
request =>

Ok(views.html.formpage()(request))  

}
  1. 扩展方法

想一想,我们想用 Integer 对象添加新方法。该方法的名称将是meterToCm,

> 1 .meterToCm 
res0 100 

为此,我们需要在 object/class/trait 中创建一个隐式类。这个类不能是case类。

object Extensions{
    
    implicit class MeterToCm(meter:Int){
        
        def  meterToCm={
             meter*100
        }

    }

}

注意隐式类只会采用一个构造函数参数

现在在你想要使用的范围内导入隐式类

import  Extensions._

2.meterToCm // result 200

【讨论】:

  • 感谢您的详细回答。真的很有帮助 +1
【解决方案4】:

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

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

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

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

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

您提示编译器“可以”在任何需要的地方使用 Play 框架发送的请求对象(我们将其命名为“request”,但可以只使用“r”或“req”),“on狡猾的”。

myForm.bindFromRequest()

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

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

myForm.bindFromRequest()(request)

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

何时您应该将请求标记为implicit?只有在使用声明 需要请求实例的隐式参数列表的方法时,才真正需要这样做。但为了简单起见,您可以养成将请求标记为implicitalways 的习惯。这样你就可以写出漂亮简洁的代码了。

【讨论】:

  • "这样你就可以写出漂亮简洁的代码。"或者,正如@DanielDinnyes 指出的那样,精美的混淆代码。追踪隐式的来源可能会非常痛苦,如果您不小心,它们实际上会使代码更难阅读和维护。
【解决方案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

【讨论】:

    【解决方案6】:

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

    在 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!
    

    希望这能有所帮助。

    【讨论】:

      【解决方案7】:

      一个非常基本的 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函数传递一个double值时,编译器会尝试在当前作用域内寻找转换隐式函数,将Int转换为Double(如函数multiply 接受Int 参数)。如果没有隐式convert 函数,那么编译器将不会编译代码。

      【讨论】:

        猜你喜欢
        • 2018-09-23
        • 2019-01-26
        • 1970-01-01
        • 2017-01-30
        • 1970-01-01
        • 1970-01-01
        • 2020-12-08
        • 2019-12-24
        • 1970-01-01
        相关资源
        最近更新 更多