Compreensão implícita em Scala

Eu estava fazendo o meu caminho através do tutorial do playframework Scala e me deparei com este trecho de código que me intrigou:

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

Então eu decidi investigar e me deparei com este post .

Eu ainda não entendi.

Qual é a diferença entre isso:

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

e

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

além do fato óbvio, eles têm nomes de método diferentes.

Quando devo usar implicit e por quê?

Vou explicar os principais casos de uso de implícitos abaixo, mas para mais detalhes, consulte o capítulo relevante de Programação no Scala .

parameters implícitos

A lista final de parâmetros em um método pode ser marcada como implicit , o que significa que os valores serão tirados do contexto no qual eles são chamados. Se não houver um valor implícito do tipo correto no escopo, ele não será compilado. Como o valor implícito deve ser resolvido para um valor único e para evitar conflitos, é uma boa ideia tornar o tipo específico para seu propósito, por exemplo, não exija que seus methods encontrem um Int implícito!

exemplo:

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

Conversões implícitas

Quando o compilador encontrar uma expressão do tipo errado para o contexto, ele procurará um valor de Function implícito de um tipo que permitirá a verificação do tipo. Portanto, se um A for necessário e encontrar um B , ele procurará um valor implícito do tipo B => A no escopo (ele também verifica alguns outros locais, como nos objects complementares B e A , se existirem). Como def s pode ser “eta-expandido” em objects Function , um implicit def xyz(arg: B): A também funcionará.

Portanto, a diferença entre seus methods é que o marcado como implicit será inserido para você pelo compilador quando um Double for encontrado, mas um Int for necessário.

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

vai funcionar o mesmo que

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

No segundo, inserimos a conversão manualmente; no primeiro o compilador fez o mesmo automaticamente. A conversão é necessária por causa da anotação de tipo no lado esquerdo.


Quanto ao seu primeiro snippet do Play:

As ações são explicadas nesta página a partir da documentação do Play (consulte também documentos da API ). Você está usando

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

no object Action (que é o companheiro para o traço do mesmo nome).

Então, precisamos fornecer uma function como o argumento, que pode ser escrito como um literal na forma

 request => ... 

Em uma function literal, a parte antes de => é uma declaração de valor, e pode ser marcada como implicit se você quiser, assim como em qualquer outra declaração val . Aqui, a request não precisa ser marcada como implicit para isso, mas, ao fazer isso, ela estará disponível como um valor implícito para quaisquer methods que possam precisar dela dentro da function (e, é claro, ela pode ser usada explicitamente como bem). Nesse caso específico, isso foi feito porque o método bindFromRequest na class Form requer um argumento de Request implícito.

AVISO: contém sarcasmo judiciosamente! YMMV …

A resposta de Luigi está completa e correta. Este é apenas para estender um pouco com um exemplo de como você pode gloriosamente overuse implícitos , como acontece com bastante frequência em projetos Scala. Na verdade, muitas vezes, você pode até encontrá-lo em um dos guias de “Melhores práticas” .

 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") } 

Por que e quando você deve marcar o parâmetro de request como implicit :

Alguns methods que você utilizará no corpo de sua ação têm uma lista de parâmetros implícita , como, por exemplo, Form.scala define um método:

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

Você não necessariamente percebe isso como você chamaria myForm.bindFromRequest() Você não precisa fornecer os argumentos implícitos explicitamente. Não, você deixa o compilador para procurar por qualquer object candidato válido a ser transmitido toda vez que se deparar com uma chamada de método que requeira uma instância da solicitação. Como você tem uma solicitação disponível, tudo o que você precisa fazer é marcá-la como implicit .

Você marca explicitamente como disponível para uso implícito .

Você sugere ao compilador que é “OK” usar o object de solicitação enviado pela estrutura do Play (que fornecemos o nome “request” mas poderia ter usado apenas “r” ou “req”) sempre que necessário, “às escondidas” .

 myForm.bindFromRequest() 

Veja? não está lá, mas está lá!

Acontece apenas sem você ter que inseri-lo manualmente em todos os lugares necessários (mas você pode passá-lo explicitamente, se quiser, não importa se está marcado como implicit ou não):

 myForm.bindFromRequest()(request) 

Sem marcá-lo como implícito, você teria que fazer o que precede. Marcando como implícito você não precisa.

Quando você deve marcar o pedido como implicit ? Você só precisa realmente se estiver usando methods que declarem uma lista de parâmetros implícita esperando uma instância da Solicitação . Mas, para simplificar, você pode simplesmente criar o hábito de marcar sempre a solicitação implicit . Dessa forma, você pode simplesmente escrever um belo código.

Além disso, no caso acima, deve haver only one function implícita cujo tipo é double => Int . Caso contrário, o compilador fica confuso e não compilará corretamente.

 //this won't compile implicit def doubleToInt(d: Double) = d.toInt implicit def doubleToIntSecond(d: Double) = d.toInt val x: Int = 42.0