Funções independentes

Começaremos por abordar o tema das funções com casos simples onde as funções são definidas à custa de uma expressão (capítulo anterior). Designamos as funções desta secção como independentes pois não dependem de outras funções.

Definição

Uma função, no sentido matemático do termo, permite obter um valor (contradomínio) dado um valor do seu domínio. Por exemplo, a função f(x) = x2 fornece o quadrado de x, e desta forma, f(4) correspondendo a 16, f(5) a 32, etc. Em programação, x é designado por parâmetro, ao passo que valores concretos para o mesmo (4, 5, etc) são referidos como argumentos.

Utilizando a palavra reservada fun (abreviatura de function), é possível definir a função acima. O identificador f corresponde ao nome da função, entre parêntesis está definido o parâmetro x do tipo Double. À direita do símbolo = está a expressão que define a função.

fun f(x: Double) = x * x

Note-se que enquanto que no capítulo anterior as expressões podiam depender de outras definições de valores, aqui a situação é semelhante, no sentido em que a expressão que define a função depende dos seus parâmetros (x no exemplo).

Identificador da função

Ao definir uma função, é necessário definir um identificador, i.e. um nome que a identifica. No primeiro exemplo apresentado, a escolha do nome f baseou-se apenas no exemplo da Matemática. Na verdade, não é uma boa escolha, pois f não informa nada sobre o que a função calcula.

O nome de uma função é muito importante, e deve refletir o que a mesma calcula. Existem duas razões principais para esta importância:

À luz deste princípio, o último exemplo não tem certamente um identificador apropriado. Algo como o seguinte seria uma escolha melhor, dado que square informa que se trata do quadrado de um número.

fun square(x: Double) = x * x

Parâmetros

Uma função poderá ter um ou mais parâmetros, sendo que cada um terá um identificador único no conjunto de parâmetros. É obrigatório indicar qual o tipo de cada parâmetro.

fun avg(a: Double, b: Double) = (a + b) / 2.0

Tipo de retorno

Uma função devolve um valor de um determinado tipo. Nalguns casos, como os exemplos acima, esse tipo pode ser inferido através da expressão, tal como na definição de valores. No caso de square o tipo de retorno será inteiro (Int), ao passo que o de avg será decimal (Double).

Instrução de Retorno

Nos casos onde a função não pode ser definida apenas através de uma expressão, teremos de definir o corpo da função entre chavetas, e utilizar a instrução return para definir o valor a devolver. Nesta situação, é obrigatório indicar explicitamente o tipo da função após a lista de parâmetros, tal como no exemplo seguinte.

fun square(x: Double): Double { return x * x }

Invocação

À utilização de uma função no contexto de um programa designamos por invocação, vulgarmente também referida como chamar uma função (do Inglês, call). Dado que é devolvido um valor, a invocação consiste numa expressão cujo tipo é dado pelo tipo de retorno da função.

A sintaxe para uma invocação consiste no nome da função, seguido de parêntesis contendo uma lista de n argumentos separados por vírgula, sendo n o número de parâmetros da função. Cada argumento é uma expressão cujo tipo tem de ser o mesmo que o tipo do parâmetro correspondente (ou compatível).

Três exemplos de invocação utilizando três tipos de expressão decimal como argumento. a) valor literal decimal; b) valor do tipo decimal, obtido pela invocação; c) expressão aritmética do tipo decimal, pois b e 3.5 são decimais. As invocações de apenas têm um argumento (pois a função tem apenas um parâmetro).

val a = square(2.0) val b = square(a) val c = square(b * 3.5)

A forma de calcular os valores em sequência é a mesma explicada anteriormente, mas neste caso a obtenção do valor é delegada na função em questão.

As invocações são expressões, e desta forma, podem ser utilizadas como argumentos. Perante uma expressão f(g(…)), onde o argumento para uma invocação de uma função f é uma invocação de uma função g, g(…) irá primeiro ser avaliada para dar origem ao argumento de f, para depois então f(…) poder ser avaliada.

O cálculo do seguintes valores desenvolvem-se da seguinte forma:
square(avg(4.0, 5.0))) → square(4.5) → 20.25
square(square(square(2.0))) → square(square(4.0)) → square(16.0) → 256.0

val a = square(avg(4.0, 5.0)) val b = square(square(square(2.0)))

Erros

Quando uma invocação não obedece aos tipos dos parâmetros da função que está a ser chamada, teremos um erro de compilação.


Erro: Não correspondência de argumentos/parâmetros.
a) um valor decimal está a ser fornecido (2.5), ao invés de um inteiro; b) estão a ser fornecidos dois inteiros, ao passo que a função apenas tem um parâmetro.

val a = square(2) val b = square(2.0, 4.0)

Por outro lado, poderemos ter um desalinhamento entre o tipo declarado de um valor e da função que o define. A invocação em si não tem qualquer problema, mas a compatibilidade entre o tipo do valor e o retorno.


Erro: Desalinhamento entre tipo de valor e invocação. A função devolve Double, ao passo que o valor c está declarado como Boolean.

val c: Boolean = square(2)

Funções baseadas em expressão condicional

Muitas funções simples podem ser definidas utilizando uma expressão condicional (if-else). Apresentamos aqui três exemplos de tais funções.

Exemplo: Funções para obter o valor absoluto (módulo) de um número inteiro ou decimal.

fun abs(n: Int) = if(n < 0) -n else n fun abs(n: Double) = if(n < 0) -n else n

Exemplo: Função para obter o valor máximo entre dois inteiros.

fun max(a: Int, b: Int) = if(a > b) a else b

Exemplo: Obter código de escalão etário com base numa idade. Este caso reflete uma situação típica que consiste em estabelecer uma correspondência entre valores.

fun ageGroup(age: Int) = if(age < 12) 'i' else if(age < 15) 'c' else if(age < 18) 'j' else 's'

Funções baseadas em expressão booleana

Exemplo: Função para verificar se um número é par, recorrendo ao resto da divisão por 2.

fun isEven(n: Int) = n % 2 == 0

Exemplo: Função para verificar se um número n está contido num intervalo [min, max]

fun inInterval(n: Int, min: Int, max: Int) = n >= min && n <= max

As funções independentes simples, tais como as baseadas em expressões, são bastante utilizadas em programas reais, apesar da sua simplicidade. A possibilidade de “dar um nome” a um simples cálculo pode facilita a comunicação da intenção do código. À medida que lidamos com situações mais complexas, necessitaremos de funções dependentes que se baseiam noutras funções.