Comprensione implicita in Scala


308

Mi stavo facendo strada attraverso il tutorial di Playframework di Scala e mi sono imbattuto in questo frammento di codice che mi ha lasciato perplesso:

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

Così ho deciso di indagare e mi sono imbattuto in questo post .

Ancora non capisco.

Qual è la differenza tra questo:

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

e

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

oltre all'ovvio fatto hanno nomi di metodi diversi.

Quando dovrei usare implicite perché?


Risposte:


391

Spiegherò di seguito i principali casi d'uso di impliciti, ma per maggiori dettagli consultare il capitolo relativo alla Programmazione in Scala .

Parametri impliciti

L'elenco dei parametri finale su un metodo può essere contrassegnato implicit, il che significa che i valori verranno presi dal contesto in cui vengono chiamati. Se non esiste un valore implicito del tipo giusto nell'ambito, non verrà compilato. Poiché il valore implicito deve risolversi in un singolo valore ed evitare conflitti, è una buona idea rendere il tipo specifico per il suo scopo, ad esempio non è necessario che i metodi lo trovino implicito Int!

esempio:

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

Conversioni implicite

Quando il compilatore trova un'espressione di tipo errato per il contesto, cercherà un Functionvalore implicito di un tipo che gli consentirà di effettuare il controllo del tipo. Quindi se un Aè richiesto e trova un B, cercherà un valore implicito di tipo B => Anell'ambito (controlla anche alcuni altri posti come negli oggetti companion Be A, se esistono). Poiché defs può essere "eta-espanso" in Functionoggetti, anche una implicit def xyz(arg: B): Avolontà lo farà.

Quindi la differenza tra i tuoi metodi è che quello contrassegnato implicitverrà inserito per te dal compilatore quando Doubleviene trovato un ma Intè richiesto.

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

funzionerà come

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

Nel secondo abbiamo inserito manualmente la conversione; nel primo il compilatore ha fatto lo stesso automaticamente. La conversione è necessaria a causa dell'annotazione del tipo sul lato sinistro.


Per quanto riguarda il tuo primo frammento di Play:

Le azioni sono spiegate in questa pagina dalla documentazione di Play (vedi anche i documenti API ). Tu stai usando

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

sul Actionoggetto (che è il compagno al tratto dello stesso nome).

Quindi dobbiamo fornire una Funzione come argomento, che può essere scritta come letterale nella forma

request => ...

In una funzione letterale, la parte prima di =>è una dichiarazione di valore e può essere contrassegnata implicitse lo si desidera, proprio come in qualsiasi altra valdichiarazione. Qui, request non è necessario contrassegnare implicitquesto per digitare check, ma in questo modo sarà disponibile come valore implicito per tutti i metodi che potrebbero averne bisogno all'interno della funzione (e, naturalmente, può essere utilizzato anche in modo esplicito) . In questo caso particolare, questo è stato fatto perché il bindFromRequestmetodo sulla classe Form richiede un Requestargomento implicito .


12
Grazie per la risposta. Il link per il capitolo 21 è davvero fantastico. Apprezzalo.
Clive,

14
Solo per aggiungere questo, il seguente video fornisce un'eccellente spiegazione degli impliciti oltre ad alcune altre funzionalità di scala youtube.com/watch?v=IobLWVuD-CQ
Shakti,

Vai alle 24:25 nel video sopra (per coloro che non vogliono ascoltare attraverso i 55 minuti)
papigee

36

ATTENZIONE: contiene sarcasmo con giudizio! YMMV ...

La risposta di Luigi è completa e corretta. Questo è solo per estenderlo un po 'con un esempio di come puoi gloriosamente abusare degli impliciti , come accade abbastanza spesso nei progetti Scala. In realtà così spesso, probabilmente puoi persino trovarlo in una delle guide "Best Practice" .

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. Buon senso dell'umorismo.
Det

1
Apprezzo l'umorismo. Questo genere di cose è uno dei motivi per cui ho smesso di provare a imparare la Scala molti anni fa e solo ora sto tornando su di esso. Non ero mai sicuro di dove (alcuni) degli impliciti provenissero dal codice che stavo guardando.
Melston,

7

Perché e quando contrassegnare il requestparametro come implicit:

Alcuni metodi che utilizzerai nel corpo della tua azione hanno un elenco di parametri implicito come, ad esempio, Form.scala definisce un metodo:

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

Non lo noti necessariamente come chiameresti myForm.bindFromRequest()Non devi fornire esplicitamente gli argomenti impliciti. No, lasci il compilatore per cercare qualsiasi oggetto candidato valido da passare ogni volta che incontra una chiamata di metodo che richiede un'istanza della richiesta. Dal momento che si fare una richiesta disponibile, tutto quello che dovete fare è quello di segnare come implicit.

Lo contrassegni esplicitamente come disponibile per l' uso implicito .

Indichi al compilatore che è "OK" usare l'oggetto richiesta inviato dal framework Play (che abbiamo dato il nome "richiesta" ma che avremmo potuto usare solo "r" o "req") dove richiesto, "di nascosto" .

myForm.bindFromRequest()

Guardalo? non è lì, ma è lì!

Succede semplicemente senza doverlo inserire manualmente in ogni luogo necessario (ma puoi passarlo esplicitamente, se lo desideri, non importa se è contrassegnato implicito meno):

myForm.bindFromRequest()(request)

Senza marcatura come implicita, si dovrebbe avere per ottenere il risultato. Contrassegnandolo come implicito non è necessario.

Quando contrassegnare la richiesta come implicit? È necessario solo se si utilizzano metodi che dichiarano un elenco di parametri implicito in attesa di un'istanza della richiesta . Ma per semplificare, potresti semplicemente prendere l'abitudine di contrassegnare implicit sempre la richiesta . In questo modo puoi semplicemente scrivere un bellissimo codice conciso.


2
"In questo modo puoi semplicemente scrivere un bellissimo codice conciso." Oppure, come sottolinea @DanielDinnyes, codice splendidamente offuscato. Può essere una vera seccatura rintracciare da dove viene un implicito e possono effettivamente rendere il codice più difficile da leggere e mantenere se non stai attento.
Melston,

7

In scala implicit funziona come :

Converter

Iniettore valore parametro

Esistono 3 tipi di utilizzo di implicito

  1. Conversione del tipo implicita : converte l'errore che produce l'assegnazione nel tipo previsto

    val x: String = "1"

    val y: Int = x

String non è il sottotipo di Int , quindi l'errore si verifica nella riga 2. Per risolvere l'errore, il compilatore cercherà un tale metodo nell'ambito che ha una parola chiave implicita e accetta una stringa come argomento e restituisce un Int .

così

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. Conversione implicita del ricevitore : generalmente tramite il destinatario chiamiamo le proprietà dell'oggetto, ad es. metodi o variabili. Quindi, per chiamare qualsiasi proprietà da un destinatario, la proprietà deve essere il membro della classe / oggetto di quel destinatario.

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

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

Qui mahadi.haveTv produrrà un errore. Perché scala compilatore cercherà prima la haveTv proprietà Mahadi ricevitore. Non troverà. In secondo luogo cercherà un metodo nell'ambito con una parola chiave implicita che prende l' oggetto Mahadi come argomento e restituisce l' oggetto Johnny . Ma non ha qui. Quindi creerà un errore . Ma quanto segue va bene.

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. Iniezione implicita di parametri : se chiamiamo un metodo e non passiamo il suo valore di parametro, causerà un errore. Il compilatore scala funziona in questo modo: per prima cosa proverà a passare il valore, ma non otterrà alcun valore diretto per il parametro.

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

In secondo luogo, se il parametro ha una qualsiasi parola chiave implicita cercherà qualsiasi val nel campo di applicazione , che hanno lo stesso tipo di valore. In caso contrario, si verificherà un errore.

def x(implicit a:Int)= a

x // error happening here

Per risolvere questo problema, il compilatore cercherà una val implicita con il tipo di Int perché il parametro a ha una parola chiave implicita .

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.

Un altro esempio:

def l(implicit b:Int)

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

possiamo anche scriverlo come-

def x(implicit a:Int)= l

Poiché ho un parametro implicito e nell'ambito del corpo del metodo x , esiste una variabile locale implicita (i parametri sono variabili locali ) a che è il parametro di x , quindi nel corpo del metodo x il valore dell'argomento implicito della firma del metodo è presentato dalla variabile del metodo locale x implicita (parametro) aimplicitamente .

Così

 def x(implicit a:Int)= l

sarà nel compilatore in questo modo

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

Un altro esempio:

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

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

x{
x => c
}

causerà un errore, perché c in x {x => c} ha bisogno di passare esplicitamente valore nell'argomento o val implicita nell'ambito .

Quindi possiamo rendere esplicitamente implicito il parametro letterale della funzione quando chiamiamo il metodo x

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

Questo è stato usato nel metodo di azione di Play-Framework

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

}

se non si menziona esplicitamente il parametro di richiesta come implicito, è necessario che sia stato scritto-

def index = Action{
request =>

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

}

4

Inoltre, nel caso precedente dovrebbe esserci una only onefunzione implicita il cui tipo è double => Int. Altrimenti, il compilatore viene confuso e non verrà compilato correttamente.

//this won't compile

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

0

Un esempio molto semplice di Implicits in scala.

Parametri impliciti :

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

Nota: qui multiplierverrà implicitamente passato alla funzione multiply. I parametri mancanti nella chiamata di funzione vengono cercati per tipo nell'ambito corrente, il che significa che il codice non verrà compilato se non esiste una variabile implicita di tipo Int nell'ambito.

Conversioni implicite :

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

Nota: quando chiamiamo la multiplyfunzione che passa un doppio valore, il compilatore tenterà di trovare la funzione implicita di conversione nell'ambito corrente, che viene convertito Intin Double(Come parametro multiplyaccetta la funzione Int). Se non è presente alcuna convertfunzione implicita , il compilatore non compilerà il codice.


0

Ho avuto la stessa identica domanda che hai avuto e penso che dovrei condividere il modo in cui ho iniziato a capirlo con alcuni esempi davvero semplici (nota che copre solo i casi d'uso comuni).

Esistono due casi d'uso comuni in Scala implicit.

  • Usandolo su una variabile
  • Usandolo su una funzione

Gli esempi sono i seguenti

Usandolo su una variabile . Come puoi vedere, se la implicitparola chiave viene utilizzata nell'ultimo elenco di parametri, verrà utilizzata la variabile più vicina.

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

Usandolo su una funzione . Come puoi vedere, se implicitviene utilizzato sulla funzione, verrà utilizzato il metodo di conversione del tipo più vicino.

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!

Spero che questo possa aiutare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.