Sök…


Syntax

  • implicit val x: T = ???

Anmärkningar

Implicita klasser tillåter anpassade metoder att läggas till befintliga typer, utan att behöva modifiera sin kod, och därmed berika typer utan att behöva kontrollera koden.

Att använda implicita typer för att berika en befintlig klass benämns ofta ett "berika mitt bibliotek" -mönster.

Begränsningar av implicita klasser

  1. Implicita klasser kan bara existera inom en annan klass, objekt eller drag.
  2. Implicita klasser kan bara ha en icke-implicit primär konstruktörsparameter.
  3. Det kan inte finnas någon annan objekt-, klass-, drag- eller klassmedlemdefinition inom samma räckvidd som har samma namn som den implicita klassen.

Implicit konvertering

En implicit konvertering tillåter kompilatorn att automatiskt konvertera ett objekt av en typ till en annan typ. Detta tillåter koden att behandla ett objekt som ett objekt av en annan typ.

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

Konverteringen är enkelriktad: i det här fallet kan du inte konvertera 42 tillbaka till Foo(42) . För att göra det måste en andra implicit konvertering definieras:

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

Observera att det här är den mekanism som till exempel ett flottörvärde kan läggas till till ett heltalvärde.

Implicita konverteringar bör användas sparsamt eftersom de döljer vad som händer. Det är en bra praxis att använda en uttrycklig konvertering via ett metodsamtal såvida det inte finns en konkret läsbarhetsvinst genom att använda en implicit konvertering.

Det finns ingen betydande resultatpåverkan av implicita konverteringar.

Scala importerar automatiskt en mängd implicita konverteringar i scala.Predef , inklusive alla konverteringar från Java till Scala och tillbaka. Dessa ingår som standard i alla filsamlingar.

Implicita parametrar

Implicita parametrar kan vara användbara om en parameter av en typ ska definieras en gång i omfattningen och sedan tillämpas på alla funktioner som använder ett värde av den typen.

Ett normalt samtal ser ut så här:

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

Låt oss nu säga att vi har några metoder som alla har en tidsgräns och vi vill ringa alla dessa metoder med samma tidsgräns. Vi kan definiera timeout som en implicit variabel.

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

Så här fungerar det är att scalac-kompilatorn letar efter ett värde i omfånget som är markerat som implicit och vars typ matchar den för den implicita parametern. Om den hittar en kommer den att använda den som den implicita parametern.

Observera att detta inte fungerar om du definierar två eller ännu fler implikationer av samma typ i omfattningen.

Om du vill anpassa felmeddelandet använder du implicitNotFound kommentaren på typen:

@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]!

En timeout är ett vanligt användningsfall för detta, eller till exempel i Akka är ActorSystem (de flesta gånger) alltid detsamma, så det passeras vanligtvis underförstått. Ett annat användningsfall är biblioteksdesign, oftast med FP-bibliotek som förlitar sig på typglasögon (som scalaz , katter eller rapture ).

Det anses i allmänhet dålig praxis att använda implicita parametrar med grundläggande typer som Int , Long , String etc. eftersom det kommer att skapa förvirring och göra koden mindre läsbar.

Implicita klasser

Implicita klasser gör det möjligt att lägga till nya metoder till tidigare definierade klasser.

String klassen har ingen metod utan withoutVowels . Detta kan läggas till så:

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

Den implicita klassen har en enda konstruktörsparameter ( str ) med den typ som du vill utöka ( String ) och innehåller metoden du vill "lägga till" till typen ( withoutVowels ). De nyligen definierade metoderna kan nu användas direkt på den förbättrade typen (när den förbättrade typen är i implicit omfattning):

import StringUtil.StringEnhancer // Brings StringEnhancer into implicit scope

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

Under huven definierar implicita klasser en implicit konvertering från den förbättrade typen till den implicita klassen, så här:

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

Implicita klasser definieras ofta som värdeklasser för att undvika att skapa runtime-objekt och därmed ta bort runtime-overhead:

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

Med den förbättrade definitionen StringEnhancer behöver inte en ny instans av StringEnhancer skapas varje gång withoutVowels metoden åberopas.

Lösa implicita parametrar med 'implicit'

Antagande av en implicit parameterlista med mer än en implicit parameter:

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

Förutsatt att en av de implicita instanserna inte är tillgänglig ( SomeCtx1 ) medan alla andra implicita instanser som behövs är inom räckvidd, för att skapa en instans av klassen måste en instans av SomeCtx1 tillhandahållas.

Detta kan göras medan du bevarar varandra implicit instans i omfattningen med hjälp av det implicitly nyckelordet:

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

Konsekvenser i REPL

Så här visar du alla implicits inom räckvidden under en REPL-session:

scala> :implicits

För att även inkludera implicita konverteringar definierade i Predef.scala :

scala> :implicits -v

Om man har ett uttryck och vill se effekten av alla omskrivningsregler som gäller för det (inklusive implikationer):

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

(Exempel:

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow