我在学习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中有两种使用隐式的常见用例。

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

例子如下

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

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

希望这能有所帮助。

其他回答

在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

同样,在上述情况下,应该只有一个隐式函数,其类型为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 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形参)。如果没有隐式转换函数,则编译器将不会编译代码。

警告:含有讽刺的明智!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")
}

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

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

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

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

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

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

myForm.bindFromRequest()

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

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

myForm.bindFromRequest()(request)

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

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