了解Scala中的隐式

本文翻译自:Understanding implicit in Scala

I was making my way through the Scala playframework tutorial and I came across this snippet of code which had me puzzled: 我正在通过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())
        } 
  )
}

So I decided to investigate and came across this post . 因此,我决定进行调查并发现这篇文章

I still don't get it. 我还是不明白。

What is the difference between this: 这有什么区别:

implicit def double2Int(d : Double) : Int = d.toInt

and

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

other than the obvious fact they have different method names. 除了显而易见的事实,它们具有不同的方法名称。

When should I use implicit and why? 什么时候应该使用implicit ,为什么?


#1楼

参考:https://stackoom.com/question/HxaV/了解Scala中的隐式


#2楼

I'll explain the main use cases of implicits below, but for more detail see the relevant chapter of Programming in Scala . 我将在下面解释隐式的主要用例,但更多详细信息请参见《 Scala中的编程》相关章节

Implicit parameters 隐式参数

The final parameter list on a method can be marked implicit , which means the values will be taken from the context in which they are called. 方法的最终参数列表可以标记为“ implicit ,这意味着这些值将从调用它们的上下文中获取。 If there is no implicit value of the right type in scope, it will not compile. 如果范围内没有正确类型的隐式值,则不会编译。 Since the implicit value must resolve to a single value and to avoid clashes, it's a good idea to make the type specific to its purpose, eg don't require your methods to find an implicit Int ! 由于隐式值必须解析为单个值并避免冲突,因此,使类型特定于其用途是一个好主意,例如,不需要您的方法找到隐式Int

example: 例:

  // 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 隐式转换

When the compiler finds an expression of the wrong type for the context, it will look for an implicit Function value of a type that will allow it to typecheck. 当编译器为上下文找到错误类型的表达式时,它将查找该类型的隐式Function值,以允许其进行类型检查。 So if an A is required and it finds a B , it will look for an implicit value of type B => A in scope (it also checks some other places like in the B and A companion objects, if they exist). 因此,如果需要A并找到B ,它将在范围内查找类型B => A的隐式值(它还会检查BA随播对象中是否存在其他一些地方)。 Since def s can be "eta-expanded" into Function objects, an implicit def xyz(arg: B): A will do as well. 由于def可以“扩展到” Function对象中,因此implicit def xyz(arg: B): A也可以。

So the difference between your methods is that the one marked implicit will be inserted for you by the compiler when a Double is found but an Int is required. 因此,您的方法之间的区别在于,当找到Double且需要Int时,编译器将为您插入一个标记为implicit方法。

implicit def doubleToInt(d: Double) = d.toInt
val x: Int = 42.0

will work the same as 将与

def doubleToInt(d: Double) = d.toInt
val x: Int = doubleToInt(42.0)

In the second we've inserted the conversion manually; 在第二篇中,我们手动插入了转换; in the first the compiler did the same automatically. 首先,编译器会自动执行相同的操作。 The conversion is required because of the type annotation on the left hand side. 由于左侧有类型注释,因此需要进行转换。


Regarding your first snippet from Play: 关于Play的第一个片段:

Actions are explained on this page from the Play documentation (see also API docs ). 播放文档在此页面上说明了操作(另请参阅API docs )。 You are using 您正在使用

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

on the Action object (which is the companion to the trait of the same name). Action对象上(它是同名特性的伴侣)。

So we need to supply a Function as the argument, which can be written as a literal in the form 因此,我们需要提供一个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. 在函数文字中, =>之前的部分是值声明,并且可以根据需要将其标记为implicit ,就像在其他任何val声明中一样。 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). 在这里, request 没有被标记implicit了这类型检查,但它这样做将可为可能需要它的函数中的任何方法的隐含价值 (当然,它可以明确地作为好)。 In this particular case, this has been done because the bindFromRequest method on the Form class requires an implicit Request argument. 在这种特殊情况下,这样做是因为Form类上的bindFromRequest方法需要一个隐式的Request参数。


#3楼

Why and when you should mark the request parameter as implicit : 为什么以及何时将request参数标记为implicit

Some methods that you will make use of in the body of your action have an implicit parameter list like, for example, Form.scala defines a method: 您将在操作主体中使用的某些方法具有隐式参数列表 ,例如Form.scala定义了一个方法:

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

You don't necessarily notice this as you would just call myForm.bindFromRequest() You don't have to provide the implicit arguments explicitly. 您不一定会注意到这一点,因为您只需要调用myForm.bindFromRequest()就不必显式提供隐式参数。 No, you leave the compiler to look for any valid candidate object to pass in every time it comes across a method call that requires an instance of the request. 不,您需要让编译器在每次遇到需要该请求实例的方法调用时寻找要传递的任何有效候选对象。 Since you do have a request available, all you need to do is to mark it as implicit . 由于您确实有一个可用的请求,因此您要做的就是将其标记为“ implicit

You explicitly mark it as available for implicit use. 您将其明确标记为可用于隐式使用。

You hint the compiler that it's "OK" to use the request object sent in by the Play framework (that we gave the name "request" but could have used just "r" or "req") wherever required, "on the sly". 您暗示编译器使用Play框架发送的请求对象(确定名称为“ request”,但可能只使用了“ r”或“ req”)是“确定”的,在“狡猾”的地方。

myForm.bindFromRequest()

see it? 看见? it's not there, but it is there! 它不在那里,但是它那里!

It just happens without your having to slot it in manually in every place it's needed (but you can pass it explicitly, if you so wish, no matter if it's marked implicit or not): 它只是在不需要手动将其插入所需的每个位置的情况下发生(但是,如果愿意, 可以将其显式传递,无论是否将其标记为implicit ):

myForm.bindFromRequest()(request)

Without marking it as implicit, you would have to do the above. 如果不将其标记为隐式, 则必须执行上述操作。 Marking it as implicit you don't have to. 不必将其标记为隐式。

When should you mark the request as implicit ? 何时应将请求标记为implicit You only really need to if you are making use of methods that declare an implicit parameter list expecting an instance of the Request . 仅在使用声明隐式参数列表(需要Request实例)的方法时才需要。 But to keep it simple, you could just get into the habit of marking the request implicit always . 但是为了简单起见,您可以养成将请求始终标记为implicit的习惯。 That way you can just write beautiful terse code. 这样,您就可以编写漂亮的简洁代码。


#4楼

WARNING: contains sarcasm judiciously! 警告:明智地包含讽刺! YMMV... YMMV ...

Luigi's answer is complete and correct. 路易吉的答案是完整而正确的。 This one is only to extend it a bit with an example of how you can gloriously overuse implicits , as it happens quite often in Scala projects. 这只是通过举例说明如何光荣地过度使用隐式实例来扩展它,因为它在Scala项目中经常发生。 Actually so often, you can probably even find it in one of the "Best Practice" guides. 实际上,您经常甚至可以在“最佳实践”指南之一中找到它。

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

#5楼

Also, in the above case there should be only one implicit function whose type is double => Int . 同样,在上述情况下,应该only one隐式函数,其类型为double => Int Otherwise, the compiler gets confused and won't compile properly. 否则,编译器会感到困惑,并且无法正确编译。

//this won't compile

implicit def doubleToInt(d: Double) = d.toInt
implicit def doubleToIntSecond(d: Double) = d.toInt
val x: Int = 42.0

#6楼

In scala implicit works as : 在scala中,隐式的工作方式是

Converter 转换器

Parameter value injector 参数值注入器

There are 3 types of use of Implicit 隐式使用有3种类型

  1. Implicitly type conversion : It converts the error producing assignment into intended type 隐式类型转换 :它将产生错误的赋值转换为预期的类型

    val x :String = "1" val x:String =“ 1”

    val y:Int = x 值y:Int = x

String is not the sub type of Int , so error happens in line 2. To resolve the error the compiler will look for such a method in the scope which has implicit keyword and takes a String as argument and returns an Int . 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!
  1. Implicitly receiver conversion : We generally by receiver call object's properties, eg. 隐式的接收者转换 :我们通常通过接收者调用对象的属性,例如。 methods or variables . 方法或变量。 So to call any property by a receiver the property must be the member of that receiver's class/object. 因此,要由接收方调用任何属性,该属性必须是该接收方的类/对象的成员。

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

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

Here mahadi.haveTv will produce an error. 在这里mahadi.haveTv将产生错误。 Because scala compiler will first look for the haveTv property to mahadi receiver. 因为scala编译器将首先向mahadi接收器寻找haveTv属性。 It will not find. 找不到。 Second it will look for a method in scope having implicit keyword which take Mahadi object as argument and returns Johnny object . 其次,它将寻找具有隐式关键字的范围内的方法,该方法以Mahadi对象作为参数并返回Johnny对象 But it does not have here. 但是这里没有。 So it will create error . 因此会产生错误 But the following is okay. 但是以下是可以的。

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. Implicitly parameter injection : If we call a method and do not pass its parameter value, it will cause an error. 隐式参数注入 :如果我们调用方法而不传递其参数值,则将导致错误。 The scala compiler works like this - first will try to pass value, but it will get no direct value for the parameter. Scala编译器的工作方式如下-首先会尝试传递值,但不会获得参数的直接值。

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

Second if the parameter has any implicit keyword it will look for any val in the scope which have the same type of value. 其次,如果参数具有任何隐式关键字,它将在作用域中查找具有相同类型值的任何val If not get it will cause error. 如果没有得到它将导致错误。

def x(implicit a:Int)= a

x // error happening here

To slove this problem compiler will look for a implicit val having the type of Int because the parameter a has implicit keyword . 为了解决这个问题,编译器将查找具有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.

Another example: 另一个例子:

def l(implicit b:Int)

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

we can also write it like- 我们也可以这样写:

def x(implicit a:Int)= l

Because l has a implicit parameter and in scope of method x's body , there is an implicit local variable ( parameters are local variables ) a which is the parameter of x , so in the body of x method the method-signature l's implicit argument value is filed by the x method's local implicit variable(parameter) a implicitly . 因为l有一个隐式参数,并且在方法x的主体范围内,所以存在一个隐式局部变量参数是局部变量a ,它是x的参数,因此在x 方法主体中 ,方法签名l的隐式参数值是由x方法的局部隐式变量(参数) a隐式提交。

So 所以

 def x(implicit a:Int)= l

will be in compiler like this 将像这样在编译器中

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

Another example: 另一个例子:

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

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

x{
x => c
}

it will cause error, because c in x{x=>c} needs explicitly-value-passing in argument or implicit val in scope . 它会导致错误,因为CX {X => C ^}需要显式传值在参数或隐式VAL 范围

So we can make the function literal's parameter explicitly implicit when we call the method x 因此,当我们调用方法x时,可以使函数文字的参数式隐式

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

This has been used in action method of Play-Framework 这已在“播放框架”的动作方法中使用

def index = Action{
implicit request =>

Ok(views.html.formpage)

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值