Comprensione implicita in Scala

Mi stavo facendo strada attraverso il tutorial su 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 ho trovato 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 

a parte il fatto ovvio che hanno nomi di metodi diversi.

Quando dovrei usare implicit e perché?

Spiegherò i principali casi d’uso impliciti di seguito, ma per maggiori dettagli si veda il capitolo pertinente di Programming in Scala .

Parametri impliciti

L’elenco dei parametri finali su un metodo può essere contrassegnato come implicit , il che significa che i valori verranno presi dal contesto in cui sono 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 e per evitare interferenze, è una buona idea rendere il tipo specifico per il suo scopo, ad esempio non richiedono i metodi per trovare un Int implicito!

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 del tipo errato per il contesto, cercherà un valore Function implicito di un tipo che gli consentirà di digitare typececk. Quindi se un A è richiesto e trova un B , cercherà un valore implicito di tipo B => A in ambito (controlla anche altri posti come negli oggetti B e A companion, se esistono). Dato che def s può essere “eta-expanded” in oggetti Function , un implicit def xyz(arg: B): A farà altrettanto.

Quindi la differenza tra i tuoi metodi è che il compilatore contrassegnato come implicit verrà inserito dal compilatore quando viene trovato un Double ma è richiesto un Int .

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

funzionerà allo stesso modo di

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

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


Per quanto riguarda il tuo primo frammento di Play:

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

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

sull’object Action (che è il compagno della caratteristica con lo stesso nome).

Quindi abbiamo bisogno di fornire una funzione come argomento, che può essere scritta letteralmente nella forma

 request => ... 

In una funzione letterale, la parte prima di => è una dichiarazione di valore e può essere contrassegnata come implicit se lo si desidera, proprio come in qualsiasi altra dichiarazione val . Qui, la request non deve essere contrassegnata come implicit per questo tipo di controllo, ma così facendo sarà disponibile come valore implicito per qualsiasi metodo che potrebbe averne bisogno all’interno della funzione (e, naturalmente, può essere usato esplicitamente come bene). In questo caso particolare, questo è stato fatto perché il metodo bindFromRequest sulla class Form richiede un argomento di Request implicito.

ATTENZIONE: contiene sarcasmo giudiziosamente! YMMV …

La risposta di Luigi è completa e corretta. Questo è solo per estenderlo un po ‘con un esempio di come si può gloriosamente esagerare con gli impliciti , come spesso accade nei progetti Scala. In realtà così spesso, probabilmente lo puoi trovare anche 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") } 

Perché e quando è necessario contrassegnare il parametro di request 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 si nota necessariamente come si chiamerebbe semplicemente myForm.bindFromRequest() Non è necessario fornire esplicitamente gli argomenti impliciti. No, lasci il compilatore alla ricerca di qualsiasi object candidato valido da passare ogni volta che incontra una chiamata al metodo che richiede un’istanza della richiesta. Poiché hai una richiesta disponibile, tutto ciò che devi fare è contrassegnarlo come implicit .

Lo contrassegni esplicitamente come disponibile per uso implicito .

Indica al compilatore che è “OK” utilizzare l’object richiesta inviato dal framework Play (che abbiamo dato il nome “request” ma che potremmo usare solo “r” o “req”) ovunque richiesto, “in sordina” .

 myForm.bindFromRequest() 

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

Succede semplicemente senza doverlo inserire manualmente in ogni posto necessario (ma puoi passarlo esplicitamente, se lo desideri, indipendentemente dal fatto che sia contrassegnato come implicit o meno):

 myForm.bindFromRequest()(request) 

Senza contrassegnarlo come implicito, si dovrebbe fare quanto sopra. Contrassegnandolo come implicito non devi.

Quando dovresti contrassegnare la richiesta come implicit ? Ne hai davvero bisogno solo se stai facendo uso di metodi che dichiarano un elenco di parametri impliciti in attesa di un’istanza della Richiesta . Ma per semplificare, potresti sempre prendere l’abitudine di segnare sempre la richiesta implicit . In questo modo puoi semplicemente scrivere un bel codice terso.

Inoltre, nel caso precedente dovrebbe esserci only one funzione implicita il cui tipo è double => Int . Altrimenti, il compilatore si confonde 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