理解 Scala 中的隐式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10375633/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:07:37  来源:igfitidea点击:

Understanding implicit in Scala

scalasyntaxplayframeworkkeyword

提问by Clive

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 implicitand why?

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

回答by Luigi Plinge

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. 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, e.g. don't require your methods to find an implicit Int!

方法的最终参数列表可以被标记implicit,这意味着这些值将从调用它们的上下文中获取。如果作用域中没有正确类型的隐式值,它将不会编译。由于隐式值必须解析为单个值并避免冲突,因此最好使类型特定于其目的,例如不需要您的方法来查找隐式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 Functionvalue of a type that will allow it to typecheck. So if an Ais required and it finds a B, it will look for an implicit value of type B => Ain scope (it also checks some other places like in the Band Acompanion objects, if they exist). Since defs can be "eta-expanded" into Functionobjects, an implicit def xyz(arg: B): Awill do as well.

当编译器为上下文找到错误类型的表达式时,它将查找Function允许它进行类型检查的类型的隐式值。因此,如果 anA是必需的并且它找到了 a B,它将B => A在范围内查找类型的隐式值(它还检查其他一些地方,如在BA伴随对象中,如果它们存在)。由于defs 可以“eta 扩展”为Function对象,因此 animplicit def xyz(arg: B): A也可以。

So the difference between your methods is that the one marked implicitwill be inserted for you by the compiler when a Doubleis found but an Intis required.

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

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 pagefrom the Play documentation (see also API docs). You are using

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

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

on the Actionobject (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

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

request => ...

In a function literal, the part before the =>is a value declaration, and can be marked implicitif you want, just like in any other valdeclaration. Here, requestdoesn'thave to be marked implicitfor this to type check, but by doing so it will be available as an implicit valuefor 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 bindFromRequestmethod on the Formclass requires an implicit Requestargument.

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

回答by Daniel Dinnyes

WARNING:contains sarcasm judiciously! YMMV...

警告:明智地包含讽刺!天啊...

Luigi's answeris 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. Actually so often, you can probably even find it in one of the "Best Practice"guides.

Luigi 的回答是完整且正确的。这个只是用一个例子来扩展它,你可以如何光荣地过度使用隐含,因为它在 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")
}

回答by Peter Perhá?

Why and when you should mark the requestparameter as implicit:

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

Some methods that you will make use of in the body of your action have an implicit parameter listlike, 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. No, you leave the compilerto 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 dohave a request available, all you need to do is to mark it as implicit.

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

You explicitlymark it as available for implicituse.

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

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 框架发送的请求对象(我们命名为“请求”,但也可以只使用“r”或“req”)在任何需要的地方,“偷偷地” .

myForm.bindFromRequest()

see it? it's not there, but it isthere!

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

It just happens without your having to slot it in manually in every place it's needed (but you canpass it explicitly, if you so wish, no matter if it's marked implicitor not):

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

myForm.bindFromRequest()(request)

Without marking it as implicit, you would have todo the above. Marking it as implicit you don't have to.

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

Whenshould you mark the request as 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. But to keep it simple, you could just get into the habit of marking the request implicitalways. That way you can just write beautiful terse code.

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

回答by MHJ

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 y:Int = x

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

    val x :String = "1"

    val y:Int = x

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

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


    class Johnny{

    val haveTv:String = "Sony"

    }


   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

Here mahadi.haveTvwill produce an error. Because scala compiler will first look for the haveTvproperty to mahadireceiver. It will not find. Second it will look for a method in scope having implicit keywordwhich take Mahadi objectas argument and returns Johnny object. But it does not have here. So it will create error. But the following is okay.

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

    def x(a:Int)= a
    
    x // ERROR happening
    
  1. 隐式参数注入:如果我们调用一个方法并且不传递它的参数值,就会导致错误。scala 编译器的工作原理是这样的——首先会尝试传递值,但它不会直接获取参数值。

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

Second if the parameter has any implicit keyword it will look for any valin the scopewhich have the same typeof value. If not get it will cause error.

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

def x(implicit a:Int)= a

x // error happening here

To slove this problem compiler will look for a implicit valhaving the type of Intbecause the parameter ahas 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 lhas a implicit parameterand in scope of method x's body, there is an implicit local variable(parameters are local variables) awhich is the parameter of x, so in the body of xmethodthe method-signature l's implicit argument valueis filed by the x method's local implicit variable(parameter) aimplicitly.

因为具有隐含的参数和在范围方法X的主体,有一个隐含的本地变量参数是局部变量一个其为参数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 cin x{x=>c}needs explicitly-value-passing in argument or implicit val in scope.

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

So we can make the function literal's parameterexplicitly implicitwhen 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 methodof Play-Framework

这已在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())  

}

if you do not mention request parameter as implicit explicitly then you must have been written-

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

def index = Action{
request =>

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

}

回答by rileyss

Also, in the above case there should be only oneimplicit function whose type is double => Int. Otherwise, the compiler gets confused and won't compile properly.

此外,在上述情况下,应该有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

回答by Keshav Lodhi

A very basic example of Implicits in scala.

Scala 中隐式的一个非常基本的例子。

Implicit parameters:

隐式参数

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

Note:Here multiplierwill be implicitly passed into the function multiply. Missing parameters to the function call are looked up by type in the current scope meaning that code will not compile if there is no implicit variable of type Int in the scope.

注意:这里multiplier会隐式传入函数multiply。函数调用的缺失参数在当前作用域中按类型查找,这意味着如果作用域中没有 Int 类型的隐式变量,代码将无法编译。

Implicit conversions:

隐式转换

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

Note:When we call multiplyfunction passing a double value, the compiler will try to find the conversion implicit function in the current scope, which converts Intto Double(As function multiplyaccept Intparameter). If there is no implicit convertfunction then the compiler will not compile the code.

注意:当我们调用multiply传递 double 值的函数时,编译器会尝试在当前作用域内寻找转换隐式函数,将其转换IntDouble(As function multiplyaccept Intparameter)。如果没有隐式convert函数,那么编译器将不会编译代码。

回答by RobotCharlie

I had the exact same question as you had and I think I should share how I started to understand it by a few really simple examples (note that it only covers the common use cases).

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

There are two common use cases in Scala using implicit.

在 Scala 中有两个常见用例使用implicit.

  • Using it on a variable
  • Using it on a function
  • 在变量上使用它
  • 在函数上使用它

Examples are as follows

例子如下

Using it on a variable. As you can see, if the implicitkeyword is used in the last parameter list, then the closest variable will be used.

在变量上使用它。如您所见,如果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

Using it on a function. As you can see, if the implicitis used on the function, then the closest type conversion method will be used.

在函数上使用它。如您所见,如果在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!

Hope this can help.

希望这能有所帮助。