Programação Funcional em Kotlin — Parte 2

Rômulo Eduardo Garcia Moraes
5 min readNov 28, 2019

--

Se você não ainda leu a parte 1, ou quer relembrar o que foi abordado, clique no link abaixo, caso contrário, vamos à parte 2!

Anteriormente, vimos que um dos principais conceitos da programação funcional eram as Higher-Order Functions (HOF), que são funções que podem receber/e ou retornar outras funções. Nessa segunda parte, vamos entrar em mais detalhes sobre como criá-las e utilizá-las em Kotlin, porém, antes de prosseguirmos precisamos ver um assunto importante e que é indispensável para entender as HOFs: Function Types.

Function Types

Em Kotlin, as funções são first-class citizens, isso significa que elas são tratadas como um tipo da linguagem, assim como Int, String, Long, Boolean, etc. No caso das funções, quando elas são representadas como um tipo, elas são chamadas de function types. O fato de podermos representar funções dessa maneira, nos permite utilizá-las como:

  • Tipo de uma propriedade:
    val f: FunctionType
  • Tipo de entrada e/ou retorno de outra função:
    fun execute(f: FunctionType): FunctionType

Obs.: FunctionType não é um tipo válido em Kotlin, ele só foi utilizado para exemplificar a declaração de function types.

Criando Function Types

Uma das maneiras de criar function types em Kotlin, é utilizando a seguinte notação: val f: (A) -> Z = { a -> *z* } , onde:

  • f é o nome da propriedade que irá referenciar a function type. Ela também pode ser um var, mas como vimos, é sempre bom garantir a imutabilidade.
  • (A) -> Z é o formato da function type, sendo A o tipo do parâmetro de entrada da função e Z o tipo de retorno dela;
  • { a -> *z* } é a definição da função, sendo a a referência ao parâmetro de entrada de tipo A, e *z* o bloco de código que será executado. Esse bloco possui acesso ao parâmetro de entrada a, e deve retornar o tipo Z.

Vamos exercitar essa notação criando nossa primeira function type. Para deixar o processo mais claro, vamos utilizar como referência a função square abaixo:

Para escrevermos uma function type equivalente a ela, basta mapearmos seus elementos para a notação val f: (A) -> Z = { a -> *z* }. O mapeamento pode ser feito da seguinte maneira:

  • Como o nome da função é square, podemos utilizá-lo como nome da propriedade que irá referenciar a função: val square ;
  • O tipo do parâmetro de entrada e saída da função é um Int, portanto o formato da function type será (Int) -> Int ;
  • Como o parâmetro de entrada é x e o corpo da função é x * x, basta mapeá-los para o formato { a -> *z* } , ficando { x -> x * x }

Nosso mapeamento final fica da seguinte maneira:

Com a nossa function type squarecompletamente definida, podemos executá-la através da propriedade square, para isso basta chamá-la passando os parâmetros necessários, assim como fazemos com as funções definidas com fun. Além disso, por ser uma propriedade, também podemos atribuí-la a outras propriedades.

Simples, não é? Mas ainda existem outras maneiras de tornar essa declaração ainda mais simples, vejamos:

Se nossa função tem apenas um parâmetro de entrada, nós podemos omiti-lo, e referenciá-lo utilizando o identificador it.

Além disso, como Kotlin possui inferência de tipo, não precisamos declarar formato da function type diretamente como tipo da propriedade. No entanto, dessa maneira o compilador não tem como inferir qual é o tipo de entrada da função, portanto precisamos declarar o tipo dos parâmetros de entrada na definição da função.

Bem, vamos explorar agora mais algumas definições de function types:

  • Function type com múltiplos parâmetros de entrada
  • Function type sem parâmetros de entrada
  • Function type "sem retorno"

Na prática, toda função retorna Unit, mas diferente das funções definidas com fun, não é permitido omitir a declaração do Unit em function types.

  • Function Type a partir de uma função declarada com fun.

Para "converter" uma função definida com fun em uma function type, basta colocar :: antes do nome da função, sem passar parâmetro algum:

Apesar dessa notação ser mais simples, o recomendável é utilizar a notação val f: (A) -> Z = { a -> *z* } por uma questão de padrão e legibilidade.

Higher-Order Functions

Bem, agora que já sabemos definir function types, vamos ver como utilizá-las para criar Higher-Order Functions que recebem funções como parâmetro e/ou retornam funções.

Como primeiro exemplo, vamos mostrar como criar HOFs que recebem funções como parâmetro. Para isso, vamos escrever uma função que, dados dois Ints e uma função, ele executa essa função sobre os dois Ints e retorna o resultado dela.

Primeiramente, vamos definir duas funções que poderão ser utilizadas como parâmetro e que irão manipular os dois Ints de entrada: addition e subtraction.

Agora, vamos definir nossa HOF, vamos chamá-la de executeOperation. Como as funções que serão passadas como parâmetro possuem o formato (Int, Int) -> Int, o parâmetro que receberá a função deve ser uma function type no formato (Int, Int) -> (Int). Vamos chamar esse parâmetro de operation.

O exemplo completo fica assim:

Acabamos de criar nossa primeira Higher-Order Function!

Mais uma vez, podemos simplificar as declarações, pois podemos passar diretamente a definição da função como parâmetro da executeOperation. Além disso, sempre que o último parâmetro de uma HOF for uma function type, a definição pode ser declarada fora dos parênteses.

Vamos ao próximo exemplo, agora definindo uma HOF que retorna uma função. Para isso, vamos criar uma função generateOperation que, dado um determinado símbolo (+ ou - ) , retorna a função de addition ou subtraction, respectivamente. Caso o símbolo não seja reconhecido, ela irá retornar uma função que recebe também recebe dois Ints como parâmetro, mas independente dos valores, retorna 0.

Como essa HOF vai retornar uma das três funções acima, todas no formato (Int, Int) -> Int, então o tipo de retorno da generateOperation deve ser uma function type no formato (Int, Int) -> Int. A HOF pode ser escrita e utilizada da seguinte maneira:

Uma outra maneira de executar as funções retornadas pelas HOFs é chamá-las imediatamente após a chamada da HOF. No caso da generateOperation acima, a chamada ficaria assim:

Conclusão

Nessa segunda parte vimos o que são function types e como utilizá-las para declarar e utilizar Higher-Order Functions, que são imprescindíveis para as linguagens funcionais.
A partir de agora, já temos o básico para criar códigos funcionais em Kotlin! Porém, nem só do básico vive o programador, afinal inevitavelmente vamos esbarrar em problemas complexos que exigem soluções mais elaboradas. Por conta disso, iremos abordar algumas técnicas interessantes que tornarão nosso código funcional mais completo e útil em diferentes cenários. Portanto, fique ligado nas próximas postagens!

--

--