Introdução
Continuando aprendizado e descoberta sobre Kotlin. Falaremos sobre scope functions. Esse post, especificamente, basicamente foi uma tradução da excelente documentação da linguagem sobre os recursos. Existem outras referências que usei para complementar, entretanto achei muito boa, a qualidade da documentação){:target=”_blank”}.
Recursos da linguagens observadores e experimentados
Recurso de scope function é um recurso de linguagens funcionais onde é possível aplicar transformações e tratamentos em um objeto em um escopo de função. A biblioteca padrão de Kotlin, oferece funções diversas para executar um bloco de código dentro do contexto de um objeto. Dentro desse contexto, é possível utilizar esse objeto sem seu nome. São 5 funções: let, run, with, apply e also. Particulamente vejo 5 formas diferentes de fazer a mesma coisa com pequenas diferenças, aumentando apenas a gama de vocabulário, acredito que com objetivo de oferecer melhores possibilidades de legibilidade do código, mas na documentação oficial, a linguagem fala sobre como o objeto se torna disponível após o bloco e qual resultado da expressão.
O uso dessas funções não incrementa nenhuma capacidade técnica adicional, o objetivo principal é fornecer uma opção de escrita de código mais consistente e com melhor legibilidade.
Basicamente tem duas principais diferenças entre essas funções:
- A forma que o contexto do objeto é referenciado
- O valor retornado pela função
Para cada função o contexto do objeto é disponibilizado por uma referência ao objeto a partir de seu nome atual. Cada scope function usa uma das duas formas para acessar o contexto do objeto:
- Como uma função lambda, onde o objeto em questão será referenciado como this.
- Como parâmetro de uma função lambida , onde o objeto em questão será referenciado como it.
Exemplo:
Abaixo, falamos um pouco sobre this e it.
this
Usado nas funções run, with e apply, nessas funções a referência do contexto do objeto é uma função lambda. Na maioria dos casos this pode ser omitido quando se tenta acessar os membros do objeto recebido, tornando o código mais enxuto. Porém, essa prática que pode dificultar legibilidade e entendimento do código porque dificulta diferenciar quais são os atributos/valores externos ao objeto e quais são interno. Esse cenário, é recomendado para lambadas que operam os atributos do objeto em questão.
it
Usado nas funções let e also, nessas funções, o contexto do objeto é um argumento da função lambda e não a função em si. Se o nome do argumento não é indicado, o nome default it é usado. Essas formas são mais legíveis, porque oferecem possibilidade de nomear como quiser, e it é menos confuso que this. Esse uso é mais recomendado que o objeto é usado como arugmento da função. É recomendado também se for utilizado para múltiplos trechos no código.
Return value
- apply e also returna o contexto do objeto.
Exemplo de uso:
- let, run e with returna o resultado da função lambda.
Baseado nesses dois critérios acima explicados, você deve avaliar e utilizar a função que faz mais sentido para seu cenário de uso.
Uso de cada função
Explicado o contexto, a idéia é mostrar exemplos mais detalhados de cada função. Tecnicamente as funções são intercambiáveis na maioria dos cenários.
let
- Contexto do objeto é disponibilizado como um argumento (it).
- Valor de retorno é o resultado de uma lambda.
- Sugestão de uso: Invocar uma ou mais funções no resultado de uma chamada aninhada. O exemplo abaixo imprime o resultado de duas operações em uma collection.
Normalmente let é usado para executar blocos de código com valores não nulos. Para objetos em objetos que podem ser null, deve ser usado o operador ?.
with
- Contexto do objeto é passado como argumento, dentro de uma lambda e está disponível através de this.
- O retorno do valor é o resultado do lambda.
- Sugestão de uso: Chamada de função no contexto do objeto sem oferecer o resultado da função lambda. O exemplo abaixo pode ser lido como “com este objeto, faça o seguinte”.
run
- Contexto do objeto é passado como argumento, dentro de uma lambda e está disponível através de this.
- O retorno do valor é o resultado do lambda.
- run faz o mesmo de with mas ele invoca let como uma extension function do contexto do objeto
- Sugestão de uso: Quando a função lambda contém ambos, a inicialização do objeto e o resultado da computação, que é retornado no valor.
apply
- Contexto do objeto é passado como argumento, dentro de uma lambda e está disponível através de this.
- O retorno do valor é o próprio objeto
- Sugestão de uso: Normalmente usado para configuração do objeto.
also
- Contexto do objeto é disponível como argumento através de it.
- O retorno do valor é o próprio objeto.
- Sugestão de uso: Para ações que é necessário o contexto do objeto como argumento. Necessário para ações que é importante ter acesso as propriedades e funções.
Resumo da ópera
Function | Object reference | Return value | É uma extension function? |
---|---|---|---|
let | it | Resultado da lambda | Sim |
run | this | Resultado da lambda | Sim |
run | - | Resultado da lambda | Não: chamado sem contexto do objeto |
with | this | Resultado da lambda | Não: usa o contexto como argumento |
apply | this | Objeto | Sim |
also | it | Objeto | Sim |
let
- Execução de lambdas em objetos não nulos.
- Introduz uma expressão como uma variável em escopo local.
apply
- Configuração de objeto.
run
- Configuração de objeto e computação de resultado.
- Execução de comandos onde uma expressão é requerida.
also
- Configurações adicionais
with
- Agrupamento de chamada de funções em um objeto
takeIf e takeUnless
Kotlin oferece ainda em sua biblioteca padrão, as funções takeIf e takeUnless que basicamente servem para verificação de estados de objeto a partir de uma chamada de função.
- takeIf: Returna o objeto se o resultado do predicado é verdadeiro, caso contrário retorna null.
- takeUnless: Returna o objeto se o resultado do predicado não é verdadeiro, caso contrário retorna null.
Conclusão
Até o momento, me parece que Kotlin resolve muitos problemas de design do Java e vem como uma opção muito moderna para quem trabalha usando a JVM. Até o momento, o aprendizado tem sido bastante produtivo. Essas funções são bastante produtivas e otimizam a legibilidade do código.
A figura abaixo, sintetiza bacana, ela foi extraída do texto do post.
Outras Fontes:
- https://kotlinlang.org/docs/reference/scope-functions.html
- https://www.baeldung.com/kotlin-scope-functions
- https://proandroiddev.com/kotlin-scope-functions-made-simple-c59b97a04ca2
- https://dzone.com/articles/examining-kotlins-also-apply-let-run-and-with-intentions
- https://medium.com/androiddevelopers/kotlin-demystified-scope-functions-57ca522895b1
- https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84