Zoeken…


Syntaxis

  • impliciete val x: T = ???

Opmerkingen

Met impliciete klassen kunnen aangepaste methoden worden toegevoegd aan bestaande typen, zonder dat de code hoeft te worden gewijzigd, waardoor typen worden verrijkt zonder dat de code hoeft te worden beheerd.

Het gebruik van impliciete typen om een bestaande klasse te verrijken wordt vaak een 'verrijk mijn bibliotheek'-patroon genoemd.

Beperkingen op impliciete klassen

  1. Impliciete klassen kunnen alleen bestaan binnen een andere klasse, object of eigenschap.
  2. Impliciete klassen mogen slechts één niet-impliciete primaire constructorparameter hebben.
  3. Er is mogelijk geen ander object, klasse, eigenschap of klassedefinitie binnen hetzelfde bereik met dezelfde naam als de impliciete klasse.

Impliciete conversie

Met een impliciete conversie kan de compiler automatisch een object van het ene type naar een ander type converteren. Hierdoor kan de code een object behandelen als een object van een ander type.

case class Foo(i: Int)

// without the implicit
Foo(40) + 2    // compilation-error (type mismatch)

// defines how to turn a Foo into an Int
implicit def fooToInt(foo: Foo): Int = foo.i

// now the Foo is converted to Int automatically when needed
Foo(40) + 2    // 42

De conversie is in één richting: in dit geval kunt u 42 terug converteren naar Foo(42) . Om dit te doen, moet een tweede impliciete conversie worden gedefinieerd:

implicit def intToFoo(i: Int): Foo = Foo(i)

Merk op dat dit het mechanisme is waarmee bijvoorbeeld een floatwaarde kan worden toegevoegd aan een geheel getal.

Impliciete conversies moeten spaarzaam worden gebruikt, omdat ze verdoezelen wat er gebeurt. Het is een best practice om een expliciete conversie te gebruiken via een methodeaanroep, tenzij er een tastbare leesbaarheidswinst is door het gebruik van een impliciete conversie.

Er is geen significante invloed op de prestaties van impliciete conversies.

Scala importeert automatisch een verscheidenheid aan impliciete conversies in scala.Predef , inclusief alle conversies van Java naar Scala en terug. Deze zijn standaard opgenomen in elke bestandscompilatie.

Impliciete parameters

Impliciete parameters kunnen handig zijn als een parameter van een type eenmaal in het bereik moet worden gedefinieerd en vervolgens moet worden toegepast op alle functies die een waarde van dat type gebruiken.

Een normale functieaanroep ziet er ongeveer zo uit:

// import the duration methods
import scala.concurrent.duration._

// a normal method:
def doLongRunningTask(timeout: FiniteDuration): Long = timeout.toMillis

val timeout = 1.second
// timeout: scala.concurrent.duration.FiniteDuration = 1 second

// to call it
doLongRunningTask(timeout) // 1000

Laten we nu zeggen dat we enkele methoden hebben die allemaal een time-outduur hebben, en we willen al die methoden met dezelfde time-out aanroepen. We kunnen time-out definiëren als een impliciete variabele.

// import the duration methods
import scala.concurrent.duration._

// dummy methods that use the implicit parameter
def doLongRunningTaskA()(implicit timeout: FiniteDuration): Long = timeout.toMillis
def doLongRunningTaskB()(implicit timeout: FiniteDuration): Long = timeout.toMillis

// we define the value timeout as implicit
implicit val timeout: FiniteDuration = 1.second

// we can now call the functions without passing the timeout parameter
doLongRunningTaskA() // 1000
doLongRunningTaskB() // 1000

De manier waarop dit werkt is dat de scalac-compiler een waarde in het bereik zoekt die als impliciet is gemarkeerd en waarvan het type overeenkomt met die van de impliciete parameter. Als het er een vindt, past het het toe als de impliciete parameter.

Merk op dat dit niet zal werken als u twee of zelfs meer implicaties van hetzelfde type in de scope definieert.

Gebruik de implicitNotFound annotatie voor het type om het foutbericht aan te passen:

@annotation.implicitNotFound(msg = "Select the proper implicit value for type M[${A}]!")
case class M[A](v: A) {}

def usage[O](implicit x: M[O]): O = x.v

//Does not work because no implicit value is present for type `M[Int]`
//usage[Int]   //Select the proper implicit value for type M[Int]!
implicit val first: M[Int] = M(1)
usage[Int]     //Works when `second` is not in scope
implicit val second: M[Int] = M(2)
//Does not work because more than one implicit values are present for the type `M[Int]`
//usage[Int]   //Select the proper implicit value for type M[Int]!

Een time-out is hiervoor een gebruikelijk geval, of bijvoorbeeld in Akka is het ActorSystem (meestal) altijd hetzelfde, dus het wordt meestal impliciet doorgegeven. Een ander gebruikscasus zou het bibliotheekontwerp zijn, meestal met FP-bibliotheken die afhankelijk zijn van typeklassen (zoals scalaz , katten of opname ).

Het wordt algemeen beschouwd als een slechte gewoonte om impliciete parameters te gebruiken met basistypen zoals Int , Long , String enz. Omdat dit verwarring zal veroorzaken en de code minder leesbaar zal maken.

Impliciete klassen

Impliciete klassen maken het mogelijk om nieuwe methoden toe te voegen aan eerder gedefinieerde klassen.

De klasse String heeft geen methode zonder withoutVowels . Dit kan als volgt worden toegevoegd:

object StringUtil {
  implicit class StringEnhancer(str: String) {
    def withoutVowels: String = str.replaceAll("[aeiou]", "")
  }
}

De impliciete klasse heeft één constructorparameter ( str ) met het type dat u wilt uitbreiden ( String ) en bevat de methode die u aan het type wilt toevoegen (zonder withoutVowels ). De nieuw gedefinieerde methoden kunnen nu rechtstreeks op het verbeterde type worden gebruikt (wanneer het verbeterde type een impliciet bereik heeft):

import StringUtil.StringEnhancer // Brings StringEnhancer into implicit scope

println("Hello world".withoutVowels) // Hll wrld

Onder de motorkap definiëren impliciete klassen een impliciete conversie van het verbeterde type naar de impliciete klasse, als volgt:

implicit def toStringEnhancer(str: String): StringEnhancer = new StringEnhancer(str)

Impliciete klassen worden vaak gedefinieerd als waardeklassen om te voorkomen dat runtime-objecten worden gemaakt en dus de runtime-overhead wordt verwijderd:

implicit class StringEnhancer(val str: String) extends AnyVal {
    /* conversions code here */
}

Met de bovenstaande verbeterde definitie StringEnhancer er niet StringEnhancer een nieuwe instantie van StringEnhancer te worden gemaakt wanneer de methode withoutVowels wordt aangeroepen.

Impliciete parameters oplossen 'impliciet' gebruiken

Uitgaande van een impliciete parameterlijst met meer dan één impliciete parameter:

case class Example(p1:String, p2:String)(implicit ctx1:SomeCtx1, ctx2:SomeCtx2)

SomeCtx1 uitgaande dat een van de impliciete instanties niet beschikbaar is ( SomeCtx1 ), terwijl alle andere impliciete instanties nodig zijn, moet een instantie van SomeCtx1 worden verstrekt om een instantie van de klasse te maken.

Dit kan worden gedaan met behoud van elkaar in het bereik impliciete instantie met behulp van het implicitly trefwoord:

Example("something","somethingElse")(new SomeCtx1(), implicitly[SomeCtx2])

Gevolgen in de REPL

Alle implicits binnen het bereik bekijken tijdens een REPL-sessie:

scala> :implicits

Om ook impliciete conversies op te nemen die zijn gedefinieerd in Predef.scala :

scala> :implicits -v

Als iemand een uitdrukking heeft en het effect wil zien van alle herschrijfregels die daarop van toepassing zijn (inclusief implicaties):

scala> reflect.runtime.universe.reify(expr) // No quotes. reify is a macro operating directly on code.

(Voorbeeld:

scala> import reflect.runtime.universe._
scala> reify(Array("Alice", "Bob", "Eve").mkString(", "))
resX: Expr[String] = Expr[String](Predef.refArrayOps(Array.apply("Alice", "Bob", "Eve")(Predef.implicitly)).mkString(", "))

)



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow