Programação funcional em Python

O paradigma funcional é um paradigma que trata a computação como uma sequência de funções e não como uma sequência de acções que mudam o estado do programa. Neste paradigma não há dados mutáveis, tudo é constante: se x é definido como sendo 3, x nunca vai poder ser 4, 5 ou 6.  As variáveis são apenas nomes para valores (vulgo dados), e não uma caixa para o que lá quisermos colocar dentro, seja uma bola azul ou amarela. Esta abordagem é o que diferencia o paradigma funcional do imperativo: no paradigma funcional “transformam-se” valores, aplicam-se em novas situações, no paradigma imperativo alteram-se estados (o conteúdo das variáveis por exemplo).

O Python é uma linguagem que, no meu ponto de vista, extremamente flexível, adaptando-se facilmente às nossas necessidades, e que suporta 3 paradigmas: o funcional, o imperativo e o orientado a objectos. É dada uma grande ênfase à programação orientada a objectos (uma vez que tudo em Python são objectos e a própria linguagem “obriga” a perceber a lógica da programação orientada a objectos), mas não dá tanta à programação funcional. Neste artigo pretendo apenas elucidar o leitor sobre alguns dos recursos disponibilizados pela linguagem para a programação no paradigma funcional e o seu funcionamento, estando a explicação/análise da programação funcional em si fora do objectivo deste artigo.

Caso esteja interessado em ler sobre a programação funcional em si, devo referir que o artigo da Wikipedia em inglês (http://en.wikipedia.org/wiki/Functional_programming) está bastante completo e é um bom ponto de partida.

Funções anónimas (lambda)

No Python, à semelhança de outras linguagens, existem funções anónimas. A vantagem das funções anónimas sobre as funções normais (as definidas com o def statement) em Python (e em todas as outras linguagens que suportam funções anónimas) é a possibilidade de definir rotinas sem as prender a um nome, são rotinas que não têm identidade, são abstractas, e podem ser usadas em qualquer lado. Para as funções descritas a seguir, vou usar funções anónimas, mas poderia usar igualmente funções normais. Se não conhece as funções anónimas em Python, os seguintes blocos de código são equivalentes (o primeiro usa funções anónimas, o segundo as funções ditas normais):

transformar = lambda x, y: x**y
transformar(3, 4) #isto retorna 81
def transformar(x, y): return x**y
transformar(3, 4) #isto também retorna 81

A vantagem do primeiro bloco de código sobre o segundo é o facto da função não estar presa àquele nome, ou seja, de a poder “destruir” quando quiser ou deixar de precisar dela, enquanto que com a definição tradicional não o consegue fazer.

Um outro exemplo:

iguais = lambda x, y: "Sim" if x == y else "Nao" iguais(54, 55) #isto retorna "Nao"
def iguais(x, y): if x == y: return "Sim" else: return "Nao" iguais(54, 55) #isto também retorna "Nao"

Sintaxe: lambda <argumentos separados por vírgulas> : <o que a função deve retornar>

Map

O map() é uma função que recebe um ou mais objectos iteráveis, e itera os objectos aplicando-lhe uma função definida pelo programador, retornando uma lista com os elementos modificados.

Exemplo: map(lambda x: x**2, [1, 2, 3, 4, 5])

O map neste caso vai retornar uma lista com todos os elementos da lista inicial elevados ao quadrado ([1, 4, 9, 16, 25]).

Podemos passar mais do que um objecto iterável, no entanto, a função que é passada ao map tem de receber tantos argumentos tantos os objectos iteráveis passados ao map.

Exemplo: map(lambda x, y: [x**2, str(y)], [1, 2, 3, 4, 5], [3.5, 7, 5.1, 8, ‘a’])

Neste caso, o map retornaria [[1, ‘3.5'], [4, ‘7'], [9, ‘5.1'], [16, ‘8'], [25, ‘a’]].

Uma situação que por vezes acontece é passarmos objectos iteráveis com um número diferente de elementos. Quando o map tenta aceder ao quinto elemento de 2 objectos, e um desses objectos não o possui, é passado à função de controlo (a função que passamos ao map) um None.

Exemplo: map(lambda x, y, z: [x**2, str(y), z], [1, 2, 3, 4, 5], [3.5, 7, 5.1, 8, ‘a’], [True, False])

O retorno do map neste caso seria [[1, ‘3.5', True], [4, ‘7', False], [9, ‘5.1', None], [16, ‘8', None], [25, ‘a’, None]].

Situações onde é possível usar o map, e as respectivas alternativas

Quando necessitamos de converter várias strings numéricas para inteiros:

map(lambda x: int(x), lista) #sendo lista uma lista de strings numéricas - > ['1', '3']

Uma alternativa seria o uso de listas por compreensão:

[int(x) for x in lista]

Ou então o uso do ciclo for:

novaLista = []
for x in lista:
  novaLista.append(int(x))

Publicado na edição 23 (PDF) da Revista PROGRAMAR.