5.4 Funções de Primeira Classe e de Ordem Superior
Nas linguagens de programação em geral, um valor, entidade ou objeto é dito como cidadão de primeira classe (first-class citizen) se este puder ser passado como parâmetro para uma função, ser retornado por uma função, bem como ser atribuído a uma variável ou estrutura de dados . Um bom exemplo são inteiros e caracteres, que são objetos de primeira classe para a maioria das linguagens de programação.
No paradigma funcional funções são tratadas como cidadãos de primeira classe, e com isso podemos ter uma função que receba uma ou mais funções como argumento, ou ainda, que retorna outra função como resposta. Estas funções são chamadas funções de ordem superior.
Um exemplo simples de uma função de ordem superior é a função map, no Exemplo 39, que recebe como argumento uma função e uma lista, e retorna uma nova lista formada pela aplicação da função a cada elemento da lista passada a ela. Para uma linguagem suportar map, ela deve tratar funções como cidadãos de primeira classe.
Exemplo 39:
map f [] = []
map f (x:xs) = f x : map f xs
A primeira linha nos diz que se a função map receber uma função e uma lista vazia, deve retornar uma lista vazia, uma vez que não há elementos para aplicar a função f. A segunda linha diz que se map receber uma função e uma lista não vazia, ele deve entender x como o primeiro elemento da lista, e todo resto como xs, a partir disso, aplicará a função f à x e adicionará o novo valor gerado por f à lista gerada pelo uso recursivo da função map. No Exemplo 40, vamos criar uma função que retorna o quadrado de um número, e então passar essa função para map junto com uma lista de números. O programa retorna como resultado a seguinte lista: [1,4,9,16,25].
Exemplo 40:
square x = x * x
map square [1,2,3,4,5]
Funções de ordem superior são extremamente úteis para a abstração de um modo geral, pois estas nos permitem não mais abstrair apenas sobre valores, mas também sobre ações. Segundo Hughes, estas funções aumentam a modularidade durante a programação , o qual este considera ter grande importância para uma programação bem sucedida. Por exemplo, considere que precisamos criar duas funções, uma que retorne a soma dos valores de uma lista e outra que retorne o produto também de uma lista. O Exemplo 41 ilustra esse cenário.
Exemplo 41:
sum [] = 0
sum (x:xs) = add x (sum xs)
prod [] = 1
prod (x:xs) = mul x (prodxs)
Contudo, percebemos que há um padrão na criação dessas funções e que possivelmente podemos criar outras funções seguindo essa mesma linha. É fácil perceber que a função (add/mul) e o valor inicial (0/1) são elementos variáveis e poderíamos parametrizá-los. Com isso, é fácil criar uma função genérica, a qual chamaremos de fold.
Exemplo 42:
(fold f init) [] = init
(fold f init) (x:xs) = f x ((fold f init) xs)
Os parênteses em (fold f init) são desnecessários, e foram utilizados apenas para ênfase. A partir de fold podemos criar as definições para sum e prod de uma forma bem mais simples:
Exemplo 43:
sum = fold add 0
prod = fold mul 1