In Scala implizites Verständnis


308

Ich machte mich auf den Weg durch das Scala Playframework-Tutorial und stieß auf diesen Codeausschnitt, der mich verwirrt hatte:

def newTask = Action { implicit request =>
taskForm.bindFromRequest.fold(
        errors => BadRequest(views.html.index(Task.all(), errors)),
        label => {
          Task.create(label)
          Redirect(routes.Application.tasks())
        } 
  )
}

Also entschied ich mich zu untersuchen und stieß auf diesen Beitrag .

Ich verstehe es immer noch nicht.

Was ist der Unterschied zwischen diesen:

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

und

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

abgesehen von der offensichtlichen Tatsache, dass sie unterschiedliche Methodennamen haben.

Wann soll ich verwenden implicitund warum?


Antworten:


391

Ich werde die Hauptanwendungsfälle von Implikits unten erklären, aber für weitere Details siehe das relevante Kapitel der Programmierung in Scala .

Implizite Parameter

Die endgültige Parameterliste einer Methode kann markiert werden. Dies implicitbedeutet, dass die Werte aus dem Kontext entnommen werden, in dem sie aufgerufen werden. Wenn der Gültigkeitsbereich keinen impliziten Wert des richtigen Typs enthält, wird er nicht kompiliert. Da der implizite Wert in einen einzelnen Wert aufgelöst werden muss und um Konflikte zu vermeiden, ist es eine gute Idee, den Typ für seinen Zweck spezifisch zu machen, z. B. erfordern Sie nicht, dass Ihre Methoden einen impliziten Wert finden Int!

Beispiel:

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

Implizite Konvertierungen

Wenn der Compiler einen Ausdruck des falschen Typs für den Kontext findet, sucht er nach einem impliziten FunctionWert eines Typs, der eine Typprüfung ermöglicht. Wenn Aalso a erforderlich ist und a findet B, sucht es B => Aim Gültigkeitsbereich nach einem impliziten Wert vom Typ type (überprüft auch einige andere Stellen wie in den Bund ACompanion-Objekten, falls vorhanden). Da defs zu FunctionObjekten "eta-erweitert" werden kann, ist dies auch ein implicit def xyz(arg: B): AWille.

Der Unterschied zwischen Ihren Methoden besteht also darin, dass die markierte Methode implicitvom Compiler für Sie eingefügt wird, wenn eine Doublegefunden wird, aber eine Interforderlich ist.

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

wird genauso funktionieren wie

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

Im zweiten haben wir die Konvertierung manuell eingefügt. Im ersten Fall hat der Compiler dasselbe automatisch getan. Die Konvertierung ist aufgrund der Typanmerkung auf der linken Seite erforderlich.


In Bezug auf Ihren ersten Ausschnitt aus Play:

Aktionen werden auf dieser Seite in der Play-Dokumentation erläutert (siehe auch API-Dokumente ). Du benutzt

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

auf dem ActionObjekt (das der Begleiter des gleichnamigen Merkmals ist).

Wir müssen also eine Funktion als Argument angeben, die als Literal in der Form geschrieben werden kann

request => ...

In einem Funktionsliteral ist der Teil vor dem =>eine Wertdeklaration und kann implicitwie in jeder anderen valDeklaration markiert werden, wenn Sie möchten . Hier request muss es nicht markiert sein, implicitdamit die Typprüfung erfolgt. Auf diese Weise wird es jedoch als impliziter Wert für alle Methoden verfügbar , die es möglicherweise innerhalb der Funktion benötigen (und natürlich kann es auch explizit verwendet werden). . In diesem speziellen Fall wurde dies durchgeführt, weil die bindFromRequestMethode für die Form- Klasse ein implizites RequestArgument erfordert .


12
Danke für die Antwort. Der Link zu Kapitel 21 ist wirklich großartig. Bin dankbar.
Clive

14
Um dies hinzuzufügen, bietet das folgende Video eine hervorragende Erklärung der Implikationen sowie einige andere Funktionen von scala youtube.com/watch?v=IobLWVuD-CQ
Shakti

Springe im obigen Video zu 24:25 (für diejenigen, die die 55 Minuten nicht hören wollen)
Papigee

36

WARNUNG: Enthält Sarkasmus mit Bedacht! YMMV ...

Luigis Antwort ist vollständig und richtig. Dies ist nur, um es ein wenig zu erweitern, mit einem Beispiel dafür, wie Sie implizite Implikationen herrlich überbeanspruchen können , wie es in Scala-Projekten ziemlich häufig vorkommt. Eigentlich so oft, können Sie es wahrscheinlich sogar in einem der "Best Practice" -Handbücher finden.

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

1
Haha. Guter Sinn für Humor.
Det

1
Ich schätze den Humor. Diese Art von Dingen ist einer der Gründe, warum ich vor vielen Jahren aufgehört habe, Scala zu lernen, und erst jetzt darauf zurückkomme. Ich war mir nie sicher, woher einige (viele) der Implikationen in dem Code kamen, den ich mir ansah.
Melston

7

Warum und wann sollten Sie den requestParameter markieren als implicit:

Einige Methoden, die Sie im Hauptteil Ihrer Aktion verwenden, verfügen über eine implizite Parameterliste , z. B. definiert Form.scala eine Methode:

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

Sie bemerken dies nicht unbedingt, wie Sie es einfach aufrufen würden. myForm.bindFromRequest()Sie müssen die impliziten Argumente nicht explizit angeben. Nein, Sie lassen den Compiler jedes Mal nach einem gültigen Kandidatenobjekt suchen, das übergeben werden soll, wenn es auf einen Methodenaufruf stößt, für den eine Instanz der Anforderung erforderlich ist. Da Sie tun eine Anfrage haben, alles , was Sie tun müssen, ist es so zu markieren implicit.

Sie markieren es explizit als für die implizite Verwendung verfügbar .

Sie weisen den Compiler an, dass es "OK" ist, das vom Play-Framework gesendete Anforderungsobjekt (das wir mit dem Namen "request" angegeben haben, aber nur "r" oder "req" hätten verwenden können) zu verwenden, wo immer dies erforderlich ist, "schlau". .

myForm.bindFromRequest()

siehst du es? es ist nicht da, aber es ist da!

Es passiert einfach, ohne dass Sie es manuell an jeder Stelle einstecken müssen, die es benötigt (aber Sie können es explizit übergeben, wenn Sie dies wünschen, egal ob es markiert ist implicitoder nicht):

myForm.bindFromRequest()(request)

Ohne sie als implizite Markierung, würden Sie haben die oben zu tun. Wenn Sie es als implizit markieren, müssen Sie es nicht.

Wann sollten Sie die Anfrage als markieren implicit? Dies ist nur dann wirklich erforderlich, wenn Sie Methoden verwenden, die eine implizite Parameterliste deklarieren, die eine Instanz der Anforderung erwartet . Aber um es einfach zu halten, könnten Sie es sich zur Gewohnheit machen, die Anfrage implicit immer zu markieren . Auf diese Weise können Sie einfach schönen knappen Code schreiben.


2
"Auf diese Weise können Sie einfach schönen knappen Code schreiben." Oder, wie @DanielDinnyes betont, wunderschön verschleierter Code. Es kann sehr schmerzhaft sein, herauszufinden, woher ein Implizit kommt, und sie können das Lesen und Verwalten des Codes erschweren, wenn Sie nicht vorsichtig sind.
Melston

7

In Scala funktioniert implizit wie folgt :

Konverter

Parameterwert Injektor

Es gibt 3 Arten der impliziten Verwendung

  1. Implizite Typkonvertierung : Konvertiert die fehlererzeugende Zuordnung in den beabsichtigten Typ

    val x: String = "1"

    val y: Int = x

String ist nicht der Untertyp von Int , daher tritt ein Fehler in Zeile 2 auf. Um den Fehler zu beheben, sucht der Compiler nach einer solchen Methode im Bereich, die ein implizites Schlüsselwort enthält, einen String als Argument verwendet und einen Int zurückgibt .

damit

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. Implizite Empfängerkonvertierung : Wir rufen im Allgemeinen die Eigenschaften des Empfängeraufrufobjekts auf, z. Methoden oder Variablen. Um eine Eigenschaft von einem Empfänger aufzurufen, muss die Eigenschaft Mitglied der Klasse / des Objekts dieses Empfängers sein.

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

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

Hier wird mahadi.haveTv einen Fehler erzeugen. Weil der Scala-Compiler zuerst nach der haveTv- Eigenschaft für den Mahadi- Empfänger sucht . Es wird nicht finden. Zweitens wird nach einer Methode im Gültigkeitsbereich mit implizitem Schlüsselwort gesucht, die das Mahadi-Objekt als Argument verwendet und das Johnny-Objekt zurückgibt . Aber es hat hier nicht. Es wird also ein Fehler entstehen . Aber das Folgende ist in Ordnung.

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. Implizite Parameterinjektion : Wenn wir eine Methode aufrufen und ihren Parameterwert nicht übergeben, verursacht dies einen Fehler. Der Scala-Compiler funktioniert folgendermaßen - zuerst wird versucht, einen Wert zu übergeben, es wird jedoch kein direkter Wert für den Parameter abgerufen.

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

Zweitens , wenn die Parameter jedes implizites Schlüsselwort hat , wird es für jeden aussieht val im Rahmen , die die haben gleiche Art von Wert. Wenn nicht, wird es Fehler verursachen.

def x(implicit a:Int)= a

x // error happening here

Slove dieses Problem Compiler für eine aussieht implizite val den mit Typ Int , weil der Parameter a hat implizites Schlüsselwort .

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.

Ein anderes Beispiel:

def l(implicit b:Int)

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

wir können es auch schreiben wie-

def x(implicit a:Int)= l

Da L hat einen impliziten Parameter und im Rahmen der körpereigenen Methode x gibt es einen impliziten lokalen Variable ( Parameter sind lokale Variablen ) ein , die die Parameter der ist , x , so in dem Körper der x - Methode der Methodensignatur Ls impliziter Argumentwert ist durch die eingereichten x - Methode der lokalen impliziten Variable (Parameter) aimplizit .

Damit

 def x(implicit a:Int)= l

wird im Compiler so sein

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

Ein anderes Beispiel:

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

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

x{
x => c
}

Dies führt zu Fehlern, da c in x {x => c} eine explizite Wertübergabe im Argument oder einen impliziten Wert im Gültigkeitsbereich erfordert .

Wir können also den Parameter des Funktionsliteral explizit implizit machen, wenn wir die Methode x aufrufen

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

Dies wurde in der Aktionsmethode von Play-Framework verwendet

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

}

Wenn Sie den Anforderungsparameter nicht explizit als implizit erwähnen, müssen Sie geschrieben worden sein.

def index = Action{
request =>

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

}

4

Im obigen Fall sollte es auch eine only oneimplizite Funktion geben, deren Typ ist double => Int. Andernfalls wird der Compiler verwirrt und nicht richtig kompiliert.

//this won't compile

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

0

Ein sehr einfaches Beispiel für Implicits in Scala.

Implizite Parameter :

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

Hinweis: Hier multiplierwird implizit an die Funktion übergeben multiply. Fehlende Parameter für den Funktionsaufruf werden im aktuellen Bereich nach Typ gesucht, was bedeutet, dass Code nicht kompiliert wird, wenn keine implizite Variable vom Typ Int im Bereich vorhanden ist.

Implizite Konvertierungen :

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

Hinweis: Wenn wir eine multiplyFunktion aufrufen , die einen doppelten Wert übergibt, versucht der Compiler, die implizite Konvertierungsfunktion im aktuellen Bereich zu finden, die Intin Double(As function multiplyaccept Intparameter) konvertiert wird . Wenn keine implizite convertFunktion vorhanden ist, kompiliert der Compiler den Code nicht.


0

Ich hatte genau die gleiche Frage wie Sie und ich denke, ich sollte anhand einiger wirklich einfacher Beispiele mitteilen, wie ich sie zu verstehen begann (beachten Sie, dass sie nur die gängigen Anwendungsfälle abdeckt).

In Scala gibt es zwei häufige Anwendungsfälle implicit.

  • Verwenden Sie es für eine Variable
  • Verwenden Sie es für eine Funktion

Beispiele sind wie folgt

Verwenden Sie es für eine Variable . Wie Sie sehen können implicit, wird die nächstgelegene Variable verwendet , wenn das Schlüsselwort in der letzten Parameterliste verwendet wird.

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

Verwenden Sie es für eine Funktion . Wie Sie sehen können, implicitwird bei Verwendung der Funktion für die Funktion die nächstgelegene Typkonvertierungsmethode verwendet.

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!

Hoffe das kann helfen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.