Expressão lambda, tipo função e funções de alta ordem no Kotlin
Tags: kotlin, programming
Published: 2023-06-12 11:36:08

Antes de começar a estudar Kotlin, eu tive uma boa experiência com Java, e uma das dúvidas que mais me pegou foi:

> _"Como implemento um método/função que recebe uma expressão lambda?"_

Se pensarmos em um código Java, basicamente, precisamos receber uma interface com apenas uma assinatura, como por exemplo, uma interface para calcular uma taxa:

```java
interface Tax {

    double calculate();

}
```

E podemos implementar essa interface da seguinte forma:

```java
class TaxCalculator {
    public double calculate(double value, Tax tax) {
        return tax.calculate(value);
    }
}
```

Uma calculadora de taxa que recebe o valor e oferece a interface para fazer o cálculo. Então podemos implementar a interface via expressão lambda passando uma regra de 10% adicionais:

```java
var value = 100.0;
var calculator = new TaxCalculator();
var calculatedValue = calculator.calculate(value,
        (v) -> v + (v * 0.1)
);
System.out.println(calculatedValue);
// 110.0
```

Esse é um dos exemplos de expressão lambda no Java, mas existe uma série de possibilidades, principalmente se considermos a classe [`Stream`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html) introduzida desde o Java 8...

## Utilizando expressão lambda no Kotlin a partir de código Java

Com esse mesmo código podemos utilizar expressões lambda no Kotlin:

```kotlin
val value = 100.0
val calculator = TaxCalculator()
val calculatedValue = calculator
    .calculate(value) { v: Double -> v + v * 0.1 }
println(calculatedValue)
// 110.0
```

E isso é possível, pois interfaces de um único método abstrado ([Single Abstract Method - SAM](https://kotlinlang.org/docs/fun-interfaces.html)) ou também conhecidas como interfaces funcionais, podem ser convertidas em expressões lambda.

O grande detalhe é que a interface "precisa ser implementada em Java", ou seja, se a calculadora e interface de taxa forem em Kotlin:

```kotlin
interface Tax {
    fun calculate(value: Double): Double
}

class TaxCalculator {
    fun calculate(value: Double, tax: Tax): Double {
        return tax.calculate(value)
    }
}
```

Recebemos o seguinte problema de compilação:

![Mensagem de tipo incompatível, espera Tax mas foi enviado (Double) -> Double](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g44xu6r2e45fyh190gpg.png)

Isso mesmo! Uma incompatibilidade de tipos indicando que é esperado um `Tax` ao invés de `(Double) -> Double`. 

Nesse momento você pode se perguntar: 

> _"Que tipo estranho é esse?"_ 🤔

Inicialmente posso indicar que é um tipo especial do Kotlin, mas, antes de falarmos dele, vamos entender como podemos usar interfaces funcionais e expressões lambda apenas no Kotlin.

## Interfaces funções

A partir da versão 1.4 do Kotlin, foram introduzidas as interfaces funcionais! Para implementá-las, basta apenas adicionar o `fun` no início da assinatura:

```kotlin
fun interface Tax {
    fun calculate(value: Double): Double
}
```

Pronto! Apenas com esse ajuste podemos usar a expressão lambda sem problemas 😄

Só que não vamos parar por aqui! Agora chegou o momento de entender aquele tipo estranho que vimos anteriormente 😅

## O tipo função

Esse tipo com essa sintax diferente, é uma notação do tipo função ou [Function Type](https://kotlinlang.org/docs/lambdas.html#function-types). Basicamente, essa notação simplifica a implementação de funções permitindo armazená-las em variáveis ou parâmetros.

Em outras palavras, a expressão lambda que escrevemos em Kotlin poderia ser representada pela seguinte variável:

```kotlin
val calculateTax: (Double) -> Double = { v: Double -> v + v * 0.1 }
```

E podemos simplificar a implementação da calculadora:

```kotlin
class TaxCalculator {
    fun calculate(
        value: Double,
        calculate: (Double) -> Double
    ): Double {
        return calculate(value)
    }
}
```

E com apenas esse ajuste, ainda mantemos a mesma chamada via expressão lambda! Se preferir, pode até mesmo enviar a variável do tipo `(Double) -> Double`:

```kotlin
val value = 100.0
val calculateTax: (Double) -> Double = { v: Double -> v + v * 0.1 }
val calculator = TaxCalculator()
val calculatedValue = calculator
    .calculate(value, calculateTax)
println(calculatedValue)
// 110.0
```

## Funções de alta ordem

A partir do momento que declaramos parâmetros ou retorno do tipo função, significa que criamos uma função de alta ordem ou [Higher-Order Functions - HOF](https://kotlinlang.org/docs/lambdas.html). 

HOF são bastante comuns em diversos códigos em Kotlin, principalmente no ambiente do Android! Se você já teve a experiência com o [Jetpack Compose](https://developer.android.com/jetpack/compose), muito provavelmente já viu códigos similares a esses:

```
Column {
    Row {

    }
    Button(onClick = { }) {

    }
}
```

E se eu te disser que todos esses composables são HOF? Vamos verificar a implementação de cada um:

```kotlin
@Composable
inline fun Column(
    ...,
    content: @Composable ColumnScope.() -> Unit
) {
    ...
}

@Composable
inline fun Row(
    ...,
    content: @Composable RowScope.() -> Unit
) {
    ...
}

@Composable
fun Button(
    onClick: () -> Unit,
    ...
) {
    ...
}
```

Veja que todos esperam uma função via parâmetro! O único detalhe é que o `Button` não é capaz de receber a lambda à direita (famoso trailing lambda) assim como vemos no `Column` ou `Box`. O motivo dessa diferença é, apenas as HOFs com o último parâmetro sendo uma função, podem implementar o trailing lambda.

Embora você viu o que é o tipo função, HOF etc, pode ser que não seja tão claro a utilidade no dia a dia, concorda? Sendo assim, vamos fazer algumas conclusões e ver mais exemplos. 😎

## Quando usar o tipo função?

HOFs em geral, são utilizadas para códigos que devem ser executados em um escopo específico ou em eventos. Um dos exemplos bastante utilizados são em funções de escopo ([scope functions](https://kotlinlang.org/docs/scope-functions.html)) em valores não nulos:

```kotlin
val words = listOf(
    "alex",
    "felipe",
    "instrutor"
)
val name = words
    .find { it == "alex" }
    ?.let { name ->
        println(name)
        // alex
    }
```

Nesta amostra de código, só vai chegar no `println()`, quando o valor não for nulo. Outro caso são listeners, como `onClick` do `Button`:

```kotlin
Button(
    onClick = {
        authenticate()
    }) {
    Text(text = "Login")
}
```

O `authenticate()` só vai ser executado quando o evento de clique acontecer! E essa técnica pode ser estendida para códigos que implementamos! Um dos casos muito comuns são os famosos _callbacks_:

```kotlin
class UserRepository(
    private val userAPI: UserAPI
) {
    fun findUserById(
        id: String,
        onSuccess: (User) -> Unit = {},
        onFailure: (Throwable) -> Unit = {}
    ) {
        userAPI.findUserById(id).enqueue(
            object : Callback<User?> {
                override fun onResponse(
                    call: Call<User?>,
                    response: Response<User?>
                ) {
                    if (response.isSuccessful) {
                        response.body()?.let { user ->
                            onSuccess(user)
                        }
                    }
                }
                override fun onFailure(
                    call: Call<User?>,
                    t: Throwable
                ) {
                    onFailure(t)
                }
            }
        )
    }
}
```

Essa é uma chamada assíncrona com o Retrofit, basicamente, chamamos o `enqueue()` de `Call` que exige a implementação de `Callback` e nele temos 2 eventos:

- `onResponse()` -> quando ocorre resposta
- `onFailure()` -> quando ocorre a falha

Então, você pode fazer as validações que deseja e rodar a função `onSuccess` enviando o usuário necessário, como também, no momento da falha, pode simplesmente chamar a função `onFailure` enviando o `Throwable`. O uso desse método fica da seguinte maneira:

```kotlin
val repository = UserRepository(
     //some implementation of UserAPI
)
repository.findUserById("some id",
    onSuccess = { user ->
        println("user found: $user")
    },
    onFailure = { t ->
        println("failure to find user")
        t.printStackTrace()
    })
```

E é por conta dessas possibilidades que o uso do tipo função, higher-order functions e expressão lambda é BASTANTE comum em códigos Kotlin 😄

O que achou dessas técnicas? Já usa no seu dia a dia? Compartilhe nos comentários.