Entendendo implícito no Scala

308

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

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 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étodos diferentes.

Quando devo usar implicite por quê?

Clive
fonte
Encontrei este tutorial realmente útil: Scala Tutorial - Aprenda a criar Função Implícita
Adrian Moisa

Respostas:

391

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

Parâmetros implícitos

A lista final de parâmetros em um método pode ser marcada implicit, o que significa que os valores serão obtidos no contexto em que são chamados. Se não houver valor implícito do tipo certo no escopo, ele não será compilado. Como o valor implícito deve ser resolvido com um único valor e evitar conflitos, é uma boa idéia tornar o tipo específico para sua finalidade, por exemplo, não exija que seus métodos encontrem um implícito Int!

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 encontra uma expressão do tipo errado para o contexto, ele procura um Functionvalor implícito de um tipo que permitirá que ele verifique tipicamente. Portanto, se um Afor necessário e encontrar um B, ele procurará um valor implícito do tipo B => Ano escopo (ele também verifica alguns outros lugares, como nos objetos complementares Be A, se houver). Uma vez que defs pode ser "expandido" em Functionobjetos, o que implicit def xyz(arg: B): Aserá feito também.

Portanto, a diferença entre seus métodos é que o marcado implicitserá inserido pelo compilador quando um Doublefor encontrado, mas um Intfor necessário.

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

funcionará da mesma maneira 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 devido à anotação de tipo no lado esquerdo.


Em relação ao seu primeiro trecho do Google Play:

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

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

no Actionobjeto (que é o companheiro da característica de mesmo nome).

Portanto, precisamos fornecer uma função como argumento, que pode ser escrito como literal na forma

request => ...

Em uma literal de função, a parte anterior a =>é uma declaração de valor e pode ser marcada implicitse você desejar, como em qualquer outra valdeclaração. Aqui, request não precisa ser marcado implicitpara que isso digite check, mas, ao fazer isso, estará disponível como um valor implícito para qualquer método que possa ser necessário na função (e, é claro, também pode ser usado explicitamente) . Nesse caso específico, isso foi feito porque o bindFromRequestmétodo na classe Form requer um Requestargumento implícito .

Luigi Plinge
fonte
12
Obrigado pela resposta. O link para o capítulo 21 é realmente incrível. Aprecie isso.
Clive
14
Só para acrescentar este, vídeo a seguir dá excelente explicação do implícitos além de algumas outras características do scala youtube.com/watch?v=IobLWVuD-CQ
Shakti
Vá para 24:25 no vídeo acima (para aqueles que não querem ouvir os 55 minutos)
papigee
36

AVISO: contém sarcasmo criteriosamente! YMMV ...

A resposta de Luigi está completa e correta. Este é apenas para estendê-lo um pouco, com um exemplo de como você pode usar de forma gloriosa o uso implícito , como acontece com frequência nos projetos Scala. Na verdade, com tanta frequência, você provavelmente pode 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")
}
Daniel Dinnyes
fonte
1
Haha Bom senso de humor.
Det
1
Eu aprecio o humor. Esse tipo de coisa é uma das razões pelas quais parei de tentar aprender Scala há muitos anos e só agora estou voltando a ela. Eu nunca tinha certeza de onde alguns (muitos) implícitos vinham do código que eu estava olhando.
melston
7

Por que e quando você deve marcar o requestparâmetro comoimplicit :

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

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

Você não percebe isso necessariamente, como chamaria myForm.bindFromRequest()Você não precisa fornecer os argumentos implícitos explicitamente. Não, você deixa o compilador para procurar qualquer objeto candidato válido a ser transmitido sempre que encontrar uma chamada de método que exija uma instância da solicitação. Desde que você faz tem um pedido disponível, tudo que você precisa fazer é para marcá-lo comoimplicit .

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

Você sugere ao compilador que está "OK" usar o objeto de solicitação enviado pela estrutura do Play (que denominamos "request", mas que poderia ter usado apenas "r" ou "req") sempre que necessário "às escondidas" .

myForm.bindFromRequest()

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

Isso acontece sem que você precise encaixá-lo manualmente em todos os lugares necessários (mas você pode transmiti-lo explicitamente, se desejar, não importa se está marcado implicitou não):

myForm.bindFromRequest()(request)

Sem marcar como implícito, você teria que fazer o acima. Marcando-o como implícito, você não precisa.

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

Peter Perháč
fonte
2
"Dessa forma, você pode simplesmente escrever um belo código conciso". Ou, como @DanielDinnyes aponta, código lindamente ofuscado. Pode ser muito difícil rastrear a origem de um implícito e eles podem tornar o código mais difícil de ler e manter, se você não tomar cuidado.
19419 melston
7

Em scala, o implícito funciona como :

Conversor

Injetor de valor de parâmetro

Existem 3 tipos de uso do Implicit

  1. Conversão implícita de tipos : converte o erro na produção da atribuição no tipo pretendido

    val x: String = "1"

    val y: Int = x

String não é o subtipo de Int , portanto, o erro ocorre na linha 2. Para resolver o erro, o compilador procurará um método no escopo que tenha uma palavra-chave implícita e use uma String como argumento e retorne um Int .

tão

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. Conversão implícita no receptor : Geralmente, pelo receptor chamamos as propriedades do objeto, por exemplo. métodos ou variáveis. Portanto, para chamar qualquer propriedade por um receptor, a propriedade deve ser o membro da classe / objeto desse receptor.

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

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

Aqui mahadi.haveTv produzirá um erro. Porque compilador scala procurará primeiro para o haveTv propriedade para Mahadi receptor. Não vai encontrar. Segundo, procurará um método no escopo que possua uma palavra-chave implícita que tome o objeto Mahadi como argumento e retorne o objeto Johnny . Mas não tem aqui. Então, isso criará erro . Mas o seguinte está bem.

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. Injeção implícita de parâmetros : se chamarmos um método e não passarmos seu valor, isso causará um erro. O compilador scala funciona assim - primeiro tentará passar valor, mas não obterá valor direto para o parâmetro.

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

Em segundo lugar, se o parâmetro tem qualquer palavra-chave implícita ele vai olhar para qualquer val no âmbito que têm o mesmo tipo de valor. Caso contrário, causará erro.

def x(implicit a:Int)= a

x // error happening here

Para reduzir esse problema, o compilador procurará um valor implícito com o tipo de Int, porque o parâmetro a possui uma palavra-chave implícita .

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.

Outro exemplo:

def l(implicit b:Int)

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

também podemos escrever como-

def x(implicit a:Int)= l

Porque eu possui um parâmetro implícito e no escopo do corpo do método x , existe uma variável local implícita ( parâmetros são variáveis ​​locais ) a que é o parâmetro de x , portanto, no corpo do método x , o valor implícito do argumento da assinatura do método l é arquivado pela variável implícita local do método x (parâmetro) aimplicitamente .

assim

 def x(implicit a:Int)= l

estará no compilador como este

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

Outro exemplo:

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

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

x{
x => c
}

causará erro, porque c em x {x => c} precisa passar explicitamente o valor ou argumento implícito no escopo .

Assim, podemos fazer a função do literal parâmetro explicitamente implícita quando chamamos os métodos x

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

Isso foi usado no método de ação do 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 você não mencionar o parâmetro request como implícito explicitamente, deverá ter sido escrito-

def index = Action{
request =>

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

}
MHJ
fonte
4

Além disso, no caso acima, deve haver only onefunção implícita cujo tipo é double => Int. Caso contrário, o compilador ficará confuso e não será compilado corretamente.

//this won't compile

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

Um exemplo muito básico de implícitos em scala.

Parâmetros implícitos :

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: Aqui multiplierserá implicitamente passado para a função multiply. Os parâmetros ausentes na chamada de função são pesquisados ​​por tipo no escopo atual, o que significa que o código não será compilado se não houver variável implícita do tipo Int no escopo.

Conversões implícitas :

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 chamamos a multiplyfunção que passa um valor duplo, o compilador tenta encontrar a função implícita de conversão no escopo atual, que é convertida Intem Double(como parâmetro de multiplyaceitação da função Int). Se não houver convertfunção implícita , o compilador não compilará o código.

Keshav Lodhi
fonte
0

Eu tinha exatamente a mesma pergunta que você e acho que devo compartilhar como comecei a entendê-lo com alguns exemplos realmente simples (observe que ele cobre apenas os casos de uso comuns).

Existem dois casos de uso comuns no Scala usando implicit.

  • Utilizando-o em uma variável
  • Usando-o em uma função

Exemplos são os seguintes

Utilizando-o em uma variável . Como você pode ver, se a implicitpalavra-chave for usada na última lista de parâmetros, a variável mais próxima será usada.

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

Utilizando-o em uma função . Como você pode ver, se o implicitfor usado na função, será usado o método de conversão de tipo mais próximo.

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!

Espero que isso possa ajudar.

RobotCharlie
fonte