我在学习Scala游戏框架教程时,遇到了这样一段让我困惑的代码:

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

and

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

除了它们有不同的方法名之外。

什么时候应该使用隐式,为什么?


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

隐式参数

方法的最后一个参数列表可以标记为隐式的,这意味着这些值将从调用它们的上下文中获取。如果作用域中没有正确类型的隐式值,则不会编译。因为隐式值必须解析为单个值,为了避免冲突,让类型特定于它的目的是一个好主意,例如,不需要你的方法找到一个隐式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的隐式值(它还检查其他一些地方,如B和A伴生对象,如果它们存在的话)。由于defs可以被“扩展”为Function对象,隐式def xyz(arg: B): A也可以。

因此,你的方法之间的区别是,标记为隐式的方法将被编译器插入,当你找到一个Double,但Int是必需的。

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文档解释了操作(参见API文档)。你正在使用

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

Action对象(它是同名trait的同伴)。

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

request => ...

In a function literal, the part before the => is a value declaration, and can be marked implicit if you want, just like in any other val declaration. Here, request doesn't have to be marked implicit for this to type check, but by doing so it will be available as an implicit value for any methods that might need it within the function (and of course, it can be used explicitly as well). In this particular case, this has been done because the bindFromRequest method on the Form class requires an implicit Request argument.


为什么以及何时应该将请求参数标记为隐式:

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

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

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

您显式地将其标记为可用于隐式使用。

你暗示编译器,它是“OK”使用的请求对象发送的Play框架(我们给了名字“请求”,但可以只使用“r”或“req”)在任何需要的地方,“偷偷地”。

myForm.bindFromRequest()

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

它发生的时候,你不需要在每个需要它的地方手动插入它(但你可以显式地传递它,如果你愿意,不管它是否标记为隐式):

myForm.bindFromRequest()(request)

如果不将其标记为隐式,则必须执行上述操作。你不需要把它标记为隐式。

什么时候应该将请求标记为隐式?只有在使用声明隐式参数列表期望Request实例的方法时才真正需要这样做。但是为了保持简单,您可以养成总是将请求标记为隐式的习惯。这样你就可以写出漂亮简洁的代码。


警告:含有讽刺的明智!YMMV……

路易吉的回答是完整而正确的。这篇文章只是通过一个示例对它进行了一点扩展,该示例展示了如何光荣地过度使用隐式,因为这种情况在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")
}

同样,在上述情况下,应该只有一个隐式函数,其类型为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

在scala中,隐式工作方式为:

转换器

参数值注入器

扩展方法

这里有一些隐式的用法

隐式类型转换:将产生错误的赋值转换为预期的类型 val x:字符串= "1" val y:Int = x

String不是Int的子类型,所以错误发生在第2行。为了解决这个错误,编译器将在范围内寻找这样一个方法,该方法具有隐式关键字,并以String作为参数并返回Int。

so

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!

隐式接收器转换:我们通常通过接收器调用对象的属性,如。方法或变量。因此,接收器要调用任何属性,该属性必须是接收器的类/对象的成员。 类Mahadi { val haveCar:字符串="宝马" }


    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

这里mahadi。haveTv将产生一个错误。因为scala编译器将首先寻找mahadi接收器的haveTv属性。它不会找到。其次,它将在范围内寻找具有隐式关键字的方法,该方法以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

隐式参数注入:如果我们调用一个方法而不传递它的参数值,它将导致一个错误。scala编译器的工作方式是这样的——首先将尝试传递值,但它不会获得参数的直接值。 def x(a:Int)= a x //错误发生

其次,如果形参有任何隐式关键字,它将在作用域中查找具有相同类型值的任何val。如果得不到就会造成错误。

def x(implicit a:Int)= a

x // error happening here

为了解决这个问题,编译器将寻找具有Int类型的隐式值,因为形参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.

另一个例子:

def l(implicit b:Int)

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

我们也可以写成-

def x(implicit a:Int)= l

由于l有一个隐式参数,并且在方法x的方法体的范围内,有一个隐式局部变量(参数为局部变量)a是x的参数,因此在x方法的方法体中,方法签名l的隐式参数值由x方法的局部隐式变量(参数)a隐式填写。

So

 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需要在参数中显式地传递值,或在作用域中隐式地传递val。

因此,当我们调用方法x时,我们可以将函数字面量的形参显式地设为隐式

x{
implicit x => c // the compiler will set the parameter of c like this c(x)
}

这已经在Play-Framework的动作方法中得到了应用

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

}

扩展方法

想一下,我们想添加一个具有Integer对象的新方法。这个方法的名字是meterToCm,

> 1 .meterToCm 
res0 100 

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

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

    }

}

注意,隐式类只接受一个构造函数形参。

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

import  Extensions._

2.meterToCm // result 200

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(作为函数乘接受Int形参)。如果没有隐式转换函数,则编译器将不会编译代码。


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

在Scala中有两种使用隐式的常见用例。

在变量上使用它 在函数上使用它

例子如下

在变量上使用它。如您所见,如果在最后一个参数列表中使用隐式关键字,那么将使用最接近的变量。

// 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

在函数上使用它。如您所见,如果在函数上使用隐式,则将使用最接近的类型转换方法。

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!

希望这能有所帮助。