理解Scala中的隐式

Cli*_*ive 293 syntax scala keyword playframework

我正在通过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())
        } 
  )
}
Run Code Online (Sandbox Code Playgroud)

所以我决定调查并发现这篇文章.

我还是不明白.

这有什么区别:

implicit def double2Int(d : Double) : Int = d.toInt
Run Code Online (Sandbox Code Playgroud)

def double2IntNonImplicit(d : Double) : Int = d.toInt
Run Code Online (Sandbox Code Playgroud)

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

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

Lui*_*hys 370

我将在下面解释implicits的主要用例,但有关详细信息,请参阅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"
Run Code Online (Sandbox Code Playgroud)

隐含的转换

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

因此,您的方法之间的区别在于,implicitDouble找到a但是Int需要时,编译器将为您插入标记的那个.

implicit def doubleToInt(d: Double) = d.toInt
val x: Int = 42.0
Run Code Online (Sandbox Code Playgroud)

会像以前一样工作

def doubleToInt(d: Double) = d.toInt
val x: Int = doubleToInt(42.0)
Run Code Online (Sandbox Code Playgroud)

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


关于Play的第一个片段:

Play文档在此页面上解释了操作(另请参阅API文档).您正在使用

apply(block: (Request[AnyContent]) ? Result): Action[AnyContent]
Run Code Online (Sandbox Code Playgroud)

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

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

request => ...
Run Code Online (Sandbox Code Playgroud)

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

  • 只是为了添加这个,下面的视频给出了很好的解释,以及scala的一些其他功能http://www.youtube.com/watch?v=IobLWVuD-CQ (14认同)
  • 感谢您的答复.第21章的链接非常棒.欣赏它. (12认同)
  • 跳转到上面视频中的*24:25*(适合那些不想听完 55 分钟的人) (2认同)

Dan*_*yes 35

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

路易吉的答案是完整和正确的.这个只是为了扩展它,以一个如何可以光荣地过度使用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")
}
Run Code Online (Sandbox Code Playgroud)

  • 我欣赏幽默。这种事情是我多年前停止尝试学习 Scala 并且现在才回到它的原因之一。我一直不确定我正在查看的代码中的一些(许多)隐式来自哪里。 (2认同)

MHJ*_*MHJ 24

在 Scala 中,隐式作品为

转换器

参数值注入器

扩展方式

有 3 种类型的使用隐式

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

    val x :String = "1"
    
    val y:Int = x
    
    Run Code Online (Sandbox Code Playgroud)

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!
Run Code Online (Sandbox Code Playgroud)
  1. 隐式接收器转换:我们通常通过接收器调用对象的属性,例如。方法或变量。因此,要由接收者调用任何属性,该属性必须是该接收者的类/对象的成员。

     class Mahadi{
    
     val haveCar:String ="BMW"
    
     }
    
    Run Code Online (Sandbox Code Playgroud)

    class Johnny{

    val haveTv:String = "Sony"

    }
Run Code Online (Sandbox Code Playgroud)
   val mahadi = new Mahadi



   mahadi.haveTv // Error happening
Run Code Online (Sandbox Code Playgroud)

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

class Mahadi{

val haveCar:String ="BMW"

}
Run Code Online (Sandbox Code Playgroud)
class Johnny{

val haveTv:String = "Sony"

}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
  1. 隐式参数注入:如果我们调用一个方法并且不传递它的参数值,就会导致错误。scala 编译器是这样工作的——首先会尝试传递值,但它不会直接获得参数值。

     def x(a:Int)= a
    
     x // ERROR happening
    
    Run Code Online (Sandbox Code Playgroud)

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

def x(implicit a:Int)= a

x // error happening here
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,编译器将寻找一个具有Int 类型的隐式 val,因为参数a具有隐式关键字

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.
Run Code Online (Sandbox Code Playgroud)

另一个例子:

def l(implicit b:Int)

def x(implicit a:Int)= l(a)
Run Code Online (Sandbox Code Playgroud)

我们也可以这样写——

def x(implicit a:Int)= l
Run Code Online (Sandbox Code Playgroud)

因为具有隐含的参数和在范围方法X的主体,有一个隐含的本地变量参数是局部变量一个其为参数X,所以在x的身体 的方法的方法签名L的隐含参数值是由x 方法的局部隐式变量(参数)a隐式归档。

所以

 def x(implicit a:Int)= l
Run Code Online (Sandbox Code Playgroud)

将像这样在编译器中

def x(implicit a:Int)= l(a)
Run Code Online (Sandbox Code Playgroud)

另一个例子:

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

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

x{
x => c
}
Run Code Online (Sandbox Code Playgroud)

它会导致错误,因为ÇX {X => C ^}需要显式传值在参数或隐式VAL范围

所以我们可以在调用方法 x时显式隐式函数字面量的参数

x{
implicit x => c // the compiler will set the parameter of c like this c(x)
}
Run Code Online (Sandbox Code Playgroud)

这已用于Play-Framework 的action 方法

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())  

}
Run Code Online (Sandbox Code Playgroud)

如果您没有明确地将请求参数作为隐式提及,那么您一定是这样写的——

def index = Action{
request =>

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

}
Run Code Online (Sandbox Code Playgroud)
  1. 扩展方法

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

> 1 .meterToCm 
res0 100 
Run Code Online (Sandbox Code Playgroud)

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

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

    }

}
Run Code Online (Sandbox Code Playgroud)

注意隐式类只会接受一个构造函数参数

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

import  Extensions._

2.meterToCm // result 200
Run Code Online (Sandbox Code Playgroud)


Pet*_*háč 6

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

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

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

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

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

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

myForm.bindFromRequest()
Run Code Online (Sandbox Code Playgroud)

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

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

myForm.bindFromRequest()(request)
Run Code Online (Sandbox Code Playgroud)

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

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

  • “这样你就可以写出漂亮的简洁代码。” 或者,正如@DanielDinnyes 指出的那样,精美的混淆代码。追踪隐式的来源可能真的很痛苦,如果您不小心,它们实际上会使代码更难阅读和维护。 (2认同)

ril*_*yss 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
Run Code Online (Sandbox Code Playgroud)