Ricerca…


Sintassi

  • implicito val x: T = ???

Osservazioni

Le classi implicite consentono di aggiungere metodi personalizzati a tipi esistenti, senza dover modificare il loro codice, arricchendo così i tipi senza dover controllare il codice.

L'uso di tipi impliciti per arricchire una classe esistente viene spesso definito come un modello "arricchisci la mia biblioteca".

Restrizioni su classi implicite

  1. Le classi implicite possono esistere solo all'interno di un'altra classe, oggetto o tratto.
  2. Le classi implicite possono avere solo un parametro di costruttore primario non implicito.
  3. Potrebbe non esserci un'altra definizione di oggetto, classe, tratto o membro della classe all'interno dello stesso ambito che ha lo stesso nome della classe implicita.

Conversione implicita

Una conversione implicita consente al compilatore di convertire automaticamente un oggetto di un tipo in un altro tipo. Ciò consente al codice di trattare un oggetto come un oggetto di un altro tipo.

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

La conversione è a senso unico: in questo caso non è possibile convertire 42 indietro a Foo(42) . Per fare ciò, è necessario definire una seconda conversione implicita:

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

Si noti che questo è il meccanismo mediante il quale un valore float può essere aggiunto ad un valore intero, per esempio.

Le conversioni implicite dovrebbero essere usate con parsimonia perché offuscano ciò che sta accadendo. È consigliabile utilizzare una conversione esplicita tramite una chiamata al metodo, a meno che non si ottenga un guadagno di leggibilità tangibile dall'utilizzo di una conversione implicita.

Non vi è alcun impatto significativo sulle prestazioni delle conversioni implicite.

Scala importa automaticamente una varietà di conversioni implicite in scala.Predef , incluse tutte le conversioni da Java a Scala e scala.Predef . Questi sono inclusi di default in qualsiasi compilazione di file.

Parametri impliciti

I parametri impliciti possono essere utili se un parametro di un tipo deve essere definito una volta nell'ambito e quindi applicato a tutte le funzioni che utilizzano un valore di quel tipo.

Una normale chiamata di funzione è simile a questa:

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

Ora diciamo che abbiamo alcuni metodi che hanno tutti una durata di timeout e vogliamo chiamare tutti quei metodi che usano lo stesso timeout. Possiamo definire il timeout come variabile implicita.

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

Il modo in cui funziona è che il compilatore scalac cerca un valore nell'ambito che è contrassegnato come implicito e il cui tipo corrisponde a quello del parametro implicito. Se ne trova uno, lo applicherà come parametro implicito.

Si noti che questo non funzionerà se si definiscono due o anche più impliciti dello stesso tipo nell'ambito.

Per personalizzare il messaggio di errore, utilizzare l'annotazione implicitNotFound sul tipo:

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

Un timeout è un caso d'uso normale per questo, o per esempio in Akka ActorSystem è (il più delle volte) sempre lo stesso, quindi di solito è passato implicitamente. Un altro caso d'uso sarebbe la progettazione di librerie, più comunemente con librerie FP che si basano su typeclass (come scalaz , gatti o estasi ).

Generalmente è considerata una cattiva pratica l'utilizzo di parametri impliciti con tipi di base come Int , Long , String ecc. Poiché creerà confusione e renderà il codice meno leggibile.

Classi implicite

Le classi implicite rendono possibile aggiungere nuovi metodi a classi precedentemente definite.

La classe String non ha alcun metodo senza withoutVowels . Questo può essere aggiunto in questo modo:

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

La classe implicita ha un singolo parametro costruttore ( str ) con il tipo che si desidera estendere ( String ) e contiene il metodo che si desidera "aggiungere" al tipo (senza withoutVowels ). I nuovi metodi definiti possono ora essere utilizzati direttamente sul tipo avanzato (quando il tipo avanzato è in ambito implicito):

import StringUtil.StringEnhancer // Brings StringEnhancer into implicit scope

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

Sotto il cofano, le classi implicite definiscono una conversione implicita dal tipo avanzato alla classe implicita, come questa:

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

Le classi implicite sono spesso definite come classi Value per evitare la creazione di oggetti runtime e quindi la rimozione del sovraccarico di runtime:

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

Con la definizione migliorata sopra, non è necessario creare una nuova istanza di StringEnhancer ogni volta che viene richiamato il metodo withoutVowels .

Risolvere i parametri impliciti usando 'implicitamente'

Assumendo un elenco di parametri impliciti con più di un parametro implicito:

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

Ora, supponendo che una delle istanze implicite non sia disponibile ( SomeCtx1 ) mentre tutte le altre istanze implicite necessarie sono in-scope, per creare un'istanza della classe deve essere fornita un'istanza di SomeCtx1 .

Questo può essere fatto preservando l'una l'altra istanza implicita nell'ambito usando la parola chiave implicitly :

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

Implicita nella REPL

Per visualizzare tutti gli implicits nell'ambito durante una sessione REPL:

scala> :implicits

Per includere anche le conversioni implicite definite in Predef.scala :

scala> :implicits -v

Se uno ha un'espressione e desidera visualizzare l'effetto di tutte le regole di riscrittura applicabili ad esso (compresi gli impliciti):

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

(Esempio:

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow