Pular para o conteúdo principal

Recursos da Linguagem Kotlin - Infix

 Olá pessoal, hoje continuando falando sobre os recursos da linguagem Kotlin, vamos falar sobre o uso de funções infix.



O que é o Infix

Ao utilizar algumas bibliotecas famosas do mundo Kotlin nos deparamos com códigos sendo utilizados da seguinte forma:

Uso do Assertion da biblioteca Kotest.

No exemplo acima, temos uma expressão diferente, que é a expect shouldBe "Kotlin", aqui temos o uso de uma função infix, que basicamente irá testar se a variável expect possui o valor "Kotlin", 

Ao olhar para esta expressão sem conhecer o infix, não percebemos que na verdade o shouldBe é uma função da variável expect, e recebe como argumento a String Kotlin, abaixo temos o mesmo efeito do código mostrado anteriormente:

Uso do Assertion da biblioteca Kotest sem o uso de Infix.

Como já podemos notar, o uso do infix é uma forma de deixar a chamada de uma função mais expressiva, ou seja, a chamada da função é fluente sem um contrato explicito de código funcional.

Esta abordagem de fluência na chamada é muito comum quando usamos DSL (Domain, Specification Language) ou Linguagem de Domínio Especifico, para a criação de DSL usando Kotlin saber funções infix é fundamental.

Criando uma Função Infix

Para criar uma função infix, antes precisamos entender as regras para poder utilizar este recurso, que são eles:
  • Pode ser utilizada em Extension Functions;
  • Pode ser utilizada em Funções membros;
  • A função deve ser declarada com um único parâmetro;
  • O parâmetro da função não podem ter valor padrão;
  • O parâmetro da função não pode ser um vararg.
Vamos agora ver como utilizar o infix em conjunto com as extension functions:

data class Computer(val itens:List<String>)

infix fun Computer.hasItem (item:String) = this.itens.contains(item)

fun main() {
    
	val computer = Computer(listOf("Teclado", "Mouse", "Monitor"))
	
	//Chamada Tradicional
	val result = computer.hasItem("Teclado")
	
	//Chamada usando o recurso do modificador infix
	val resultInfix = computer hasItem "Teclado"
	
	println(result)
	println(resultInfix)
}

Neste exemplo temos a classe Computer, com uma propriedade chamada itens do tipo List<String>, logo após, temos a declaração de uma extension function chamada hasItem, e perceba que ela possui o modificador infix em sua declaração, esta apenas verifica se a propriedade itens possui a String encaminhada como argumento.

Função hasItem declarada com o modificador infix.

Dentro do método main realizamos a chamada de uma função infix de 2 formas diferentes, vale lembrar que mesmo a função sendo declarada como infix, ela ainda pode ser executada da forma tradicional.

Chamada da função infix hasItem.

De primeira vista, parece algo que não agrega muito para os códigos do dia a dia, mas ao escrever DSLs ou Libraries, você nota que o ganho é alto, tanto na escrita reduzida de código, como também no entendimento das regras de negocio.

Função membro com Infix

Agora no segundo exemplo iremos usar o infix em uma função membro:

data class Car(val name:String){
	
	infix fun nameStartsWith(letter:Char) = this.name.startsWith(letter)
}

fun main() {
    
	val car = Car("Azera")
	
	//Chamada Tradicional
	val result = car.nameStartsWith('B')
	
	//Chamada usando o recurso do modificador infix
	val resultInfix = car nameStartsWith 'A'
	
	println(result)
	println(resultInfix)
}

Neste exemplo criamos uma classe Car com uma propriedade name, dentro da classe Car também declaramos a função infix chamada nameStartsWith, o objetivo é verificar se o nome do Carro começa com uma determinada letra.

A declaração com relação a extension function não muda, a única diferença é que a função infix nameStartsWith pertence a classe Car.

Declaração da função infix.

A chamada acontece da seguinte forma:

Chamada da função infix nameStartsWith.

Nestes exemplos, podemos perceber o potencial que temos com o uso de funções infix, agora vamos ver um exemplo mais complexo para termos uma visão ampla de como este recurso pode nos auxiliar no dia a dia.

Chamadas encadeadas com Infix

Além das chamadas que vimos anteriormente, podemos realizar chamadas encadeadas que usam infix, este recurso é um ponta pé inicial para entrar no mundo das DSLs, vamos analisar o exemplo abaixo:


data class Product(val name:String, val category:Category)

data class Category(val name:String){
		
	infix fun product(name:String) = Product(name, this)
}

class Store{
	
	infix fun addCategory(name:String) = Category(name)
}

fun main(){
	
	val store = Store()
	
	val productInfix = store addCategory "Eletrônicos" product "TV"
	
	println("productInfix = $productInfix")
	
	val productWithoutInfix = store.addCategory("Eletrônicos").product("TV")
	
	println("productWithoutInfix = $productWithoutInfix")
}

No exemplo criamos 3 classes, onde a classe Store possui uma função infix chamada addCategory responsável por criar uma instância da classe Category, na classe Category temos uma função chamada product, esta recebe uma String como argumento e cria uma instância da classe Product.

O uso das funções infix acontece da seguinte forma:

Criação de um Product com chamadas infix em cascata.

Ao analisar esta expressão, tudo parece uma simples frase dizendo como queremos montar nosso produto, vamos analisar a chamada convencional:

Criação de um Product sem o uso da chamada infix.

O recurso de chamadas infix ajuda em vários aspectos, sendo os principais:
  • Criação de modelos de domínio complexos;
  • Criação de funções helpers;
  • Criação de DSLs;
  • Criação de Libraries ou Frameworks.
Um ponto que vale lembrar é: USE COM CAUTELA, porque apesar de ser um recurso interessante, seu uso deliberado pode deixar o código mais complexo, principalmente para novos integrantes de uma equipe ou para desenvolvedores que não estejam familiares com este recurso.

Código Fonte

Referências

Comentários

Postagens mais visitadas do Blog