Ao nos depararmos com implicit a primeira vez, pode ser algo um pouco confuso. Vou tentar explicar e exemplificar o uso de implicits.
Quando queremos definir uma variável ou método como privado/protegido, basta somente adicionarmos private/protected na frente, certo? Isso informa ao compilador que teremos aquelas variáveis e métodos privados ou protegidos. Trabalhamos com implicit da mesma forma, sendo que o seu funcionamento é completamente diferente. Iremos ver 3 maneiras de utilizar o implicit, mas em todos os casos com o mesmo objetivo, tornar algo implícito!
Antes de entrarmos a fundo no assunto, precisamos entender como funciona o implicit.
O implicit de maneira geral funciona da mesma forma. Quando definimos algo como implícito, o compilador registra tudo que é implícito em uma lista de “coisas implícitas”. A partir daí, quando invocamos um método qualquer, por exemplo, o compilador sempre irá verificar em sua lista os objetos implícitos, caso algum atenda a sua condição implícita ele irá invocar o que foi definido como implícito.
Confuso? A baixo iremos ver na prática como o implicit funciona:
Classes implícitas
O primeiro tipo de implicit que iremos aprender é o implicit de classe. Com ele conseguimos de maneira fácil adicionar ou modificar o comportamento de uma classe, seja em seus atributos ou métodos.
Adicionando um método na classe String
implicit class MyString(s: String) {
def my_method = s + " !!!!"
}
val s = "Olá"
s.my_method
O que aconteceu aqui?
Primeiro criamos uma classe(MyString) que tem o mesmo construtor de uma classe String, onde recebe uma String como parâmetro. Ao criar um objeto String e chamar o método “meu_metodo”, o scala irá verificar se o “meu_metodo” pertence a classe nativa String, caso não encontre, o Scala irá pesquisar nos métodos de conversão implícitos(lista de coisas implícitas) e irá ver que o o método recebe uma String e possui um metodo “meu_metodo”.
Isso funciona bem, porém pode ser um pouco limitado ao seu escopo, onde esta declarado. Para resolver isso, podemos colocar nossa implicit class dentro de um objeto e importar.
package com.projeto.utils
object NewString {
implicit class MinhaString(s: String) {
def meu_metodo = s + " !!!!"
}
}
Agora podemos trabalhar de forma mais completa, apenas importando o objeto.
import com.projeto.utils.NewString._
val s = "Olá"
s.meu_metodo
Métodos implícitos
Antes do Scala 2.10, não existia o implicit class. Porém podemos nos beneficiar do implicit, mas com o implicit def.
O que acontece se tentarmos atribuir um objeto double em uma variavel int?
val i: Int = 2.30 // Erro
Da erro né? Porém podemos criar um método implicito que diz que, se for atribuido algum valor double a uma variável int, ele deve automaticamente converter o double para int. vejam?
implicit def doubleToInt(d: Double) = d.toInt
Pronto, criamos a conversão, agora experimente rodar o código acima que deu erro:
val i: Int = 2.30 // Foi!!
Agora sim! O que aconteceu foi o seguinte: Ao tentar atribuir um valor do tipo Doble em uma variável Int, naturalmente são tipos diferentes, o Scala iria lançar uma exceção. Porém, antes de lançar uma exceção, ele vai buscar primeiro se não existe nenhum método implícito que recebe um double e retorne um Int.
Entenda da seguinte maneira. Ao tentarmos adicionar uma variavel do tipo double em uma do tipo int, um bub, porem o scala primeiro verifica se há alguma vacina(correção) para caso tenhamos esse bug.
Adicionando métodos sem implicit class
class NewString(val s: String) {
def meu_metodo = s + " !!!!"
}
implicit def meu_metodo_implicit(s: String) = new NewString(s)
val a = "Diego"
a.meu_metodo
Parâmetros implícitos
Permite referenciar um valor para um parâmetro do método em questão. Caso seja passado algum valor para o parametro, este sempre terá prioridade sobre o implicit.
def my_name(n: String) = println("my name is " + n + " !!!")
my_name("diego")
my_name // Erro!
Definimos um método my_name que recebe uma String como parâmetro e depois exibe uma String como retorno. A primeira chamada do método funciona como esperado, já a segunda da um erro, por conta de não termos passado uma String como esperada no método. Agora vamos utilizar um parâmetro implícito:
def my_name(implicit n: String) = println("my name is " + n + " !!!")
implicit val name = "Nome implícito"
my_name("diego")
my_name // Não da mais erro!
Agora podemos chamar nossa função my_name sem nenhum parâmetro!
Mas porque funcionou agora?
Ao definir o parâmetro como implícito, ele nos permite definir fora de seu escopo, variáveis implícitas também. Como o parâmetro é do tipo String, qualquer variável do tipo String que definírmos como implicit, irá servir de valor default para o parâmetro, caso não seja passado nenhum valor no momento da chamada do método.
Um exemplo de utilização do implicit, está no http://www.playframework.com. Quando criamos uma Action em um controller nosso, é uma boa prática utilizar o implicit request, pois em qualquer lugar que precise utilizar o request em questão, irá utilizar o request já alocado.
def index = Action { implicit request =>
Ok(views.html.home.index())
}
É isso ai, até a próxima!