'''Programação Funcional em Python - Rudá Moura <
> Dezembro de 2003. Revisado em 2011.''' Resumo: O objetivo desse artigo é explicar de maneira rápida e de fácil entendimento as características funcionais da Linguagem Python (lambda, map, filter e zip). Entende-se aqui como funcional o paradigma de programação utilizado em linguagens como Lisp e Scheme. O leitor não deve se assustar pois não será necessário contar parênteses para tirar proveito dessas características em Python, (((isto foi uma piada))). O aspecto funcional de Python permite escrever código compacto e eventualmente mais rápido, por outro lado corre-se o risco de deixar o código obscuro e de manutenção mais complicada. Tenha bom senso e cuidado com o seu uso. <> = Primeiras Palavras = Uma das vantagens da linguagem Python está no seu rico conjunto de estruturas de dados disponíveis ao programador. Entre essas estruturas destaca-se a lista. A ''lista'' é um tipo de dado dinâmico (pode aumentar ou diminuir de tamanho) e mutável (seus elementos individuais podem ser alterados) utilizado para armazenar outros tipos de dados, inclusive outras listas. O Python provê um conjunto de funções que podem ser aplicados as listas. Pode-se inserir ou remover elementos, classificar/ordenar, tratar a lista como se fosse uma pilha entre outros. É comum também "caminhar" entre os seus elementos, como no exemplo a seguir. {{{ #!python lista = [0, 1, 2, 3, 4, 5] soma = 0 for elem in lista: soma = soma + elem }}} Uma ''tupla'' é semelhante a uma lista mas esta é estática (não possui métodos para inserir ou remover elementos) e imutável (os elementos individuais não podem ser alterados). Um exemplo de tupla pode ser um par de elementos, como em (-1, 1) ou um conjunto de pares, como em ( (0, 0), (1, 1), (-1, -1) ). Uma ''string'', que também é um tipo estático e imutável e pode ter um tratamento análogo ao de uma lista. Define-se então o termo genérico ''seqüência'' para indicar uma tupla, uma string ou uma lista. Note que não existe um tipo seqüência em Python. {{{ #!python # Seqüências tupla = (0, 1, 2) lista = [0, 1, 2] string = 'doesnotcompute' }}} = A Expressão Lambda = Uma ''expressão lambda'' permite escrever funções anônimas/sem-nome usando apenas uma linha de código. Sintaxe: {{{ lambda [ var1, var2, ..., varN ]: expr -> expr_ret }}} Onde ''var1, var2, ..., varN'' são variáveis que representam o argumento da função e ''expr'' é qualquer expressão válida em Python envolvendo essas variáveis. O resultado é uma nova expressão ''expr_ret''. Um exemplo trivial de lamba é a função identidade, que retorna o próprio elemento. Um exemplo prático de lambda é uma função que recebe dois valores (x e y) e retorna a soma desses elementos. Exemplos: Função identidade. {{{ #!python >>> lambda x: x at 0x1e3630> }}} Soma de dois valores. {{{ #!python >>> lambda x, y: x+y at 0x1e3670> }}} Note que o resultado é uma expressão do tipo lambda. Mas como posso obter algo útil, por exemplo, calcular 1+2 com lambda? {{{ #!python >>> (lambda x, y: x+y)(1,2) 3 }}} Uma maneira de "lembrar" uma expressão lambda é guardá-la em uma variável. {{{ #!python >>> identidade = lambda x: x >>> identidade('Python') 'Python' >>> soma = lambda x, y: x+y >>> soma(1, 2) 3 }}} Aparentemente lambda sozinho não tem muita graça ou utilidade, mas nas seções que se seguem faremos melhor uso dela. O importante é ter em mente que uma expressão lambda pode responder igual a qualquer função em Python. = As Funções Embutidas Map, Reduce, Filter e Zip = == Map == A forma mais simples de se usar o map é aplicando uma função ''func'' a uma seqüência ''seq'', o resultado é sempre uma lista cujo os elementos são obtidos aplicando-se individualmente cada elemento de seq a função func. Ao mapear-se uma lista com ''None'' retorna-se os elementos originais da seqüência. Sintaxe: {{{ #!python map(func ou None, seq) -> lista }}} Exemplos: {{{ #!python >>> map(None, [1, 3, 5]) [1, 3, 5] >>> map(abs, [-1, -2, -3]) [1, 2, 3] >>> map(str, [2, 4, 6]) ['2', '4', '6'] >>> map(hex, (10, 11, 12)) ['0xa', '0xb', '0xc'] }}} Um exemplo mais interessante com uso da expressão lambda. {{{ #!python >>> map(lambda x: x*x*x, [1, 2, 3, 4]) [1, 8, 27, 64] }}} Em resumo, se oferecermos uma função ao map, teremos que: {{{ #!python lista[0] = func(seq[0]) lista[1] = func(seq[1]) ... lista[M-1] = func(seq[M-1]) }}} Onde ''M'' é o tamanho de seq. === Forma Completa de Map === A forma completa de map admite uma função ''func'' (ou ''None'') e um conjunto de seqüencias (''seq1, seq2, ..., seqN'') como parâmetros. Sintaxe: {{{ #!python map(func ou None, seq1, seq2, ..., seqN) -> lista }}} Atribuindo-se ''None'' ao map este retorna sempre uma tupla de N elementos que são os elementos cuja ordem deles é a ordem das seqüencias até o tamanho da maior lista, índices maiores que as seqüências são retornados como None. Exemplos: {{{ #!python >>> map(None, [1, 3, 5], [2, 4, 6]) [(1, 2), (3, 4), (5, 6)] >>> map(None, [1, 3, 5], [2, 4, 6, 8, 10]) [(1, 2), (3, 4), (5, 6), (None, 8), (None, 10)] >>> map(None, (1, 3, 5), (2, 4, 6), (2, 4, 8, 16, 32)) [(1, 2, 2), (3, 4, 4), (5, 6, 8), (None, None, 16), (None, None, 32)] }}} Se atribuirmos uma função para map, teremos sempre que: {{{ #!python lista[0] = func(seq1[0], seq2[0], ..., seqN[0]) lista[1] = func(seq1[1], seq2[1], ..., seqN[1]) ... lista[M-1] = func(seq1[M-1], seq2[M-1], ..., seqN[M-1]) }}} Onde ''M'' é o tamanho da maior lista entre seq1, seq2, ..., seqN. Fica fácil entender que se oferecermos ao map três listas é necessário então que a função aceite três variáveis como parâmetro. As seqüencias menores que M são automaticamente preenchidas com o valor None e isso pode ser um inconveniente caso se tente aplicar a uma função que espera valores numéricos, pois None nunca é tratado como um valor numérico. Exemplos: {{{ #!python >>> map(lambda x, y: x*y, [1, 3, 5], [2, 4, 6]) [2, 12, 30] >>> map(lambda a, b, x: a*x+b, [1, 3, 5], [2, 4, 8], [0, 0, 0]) [2, 4, 8] >>> map(pow, [2, 4, 6], [2, 2, 2, 2]) Traceback (most recent call last): File "", line 1, in ? TypeError: unsupported operand type(s) for ** or pow(): 'NoneType' and 'int' }}} A lista [2, 4, 6] tem um elemento a menos do que [2, 2, 2, 2] e é preenchida com None, por isso o erro. == Reduce == A função ''reduce'' aplica acumuladamente os ítens de uma seqüência de entrada ''seq'' (da esquerda para a direita) a uma função ''func'' de dois parâmetros até reduzir esse cálculo a um único valor de resposta. Opcionalmente pode-se atribuir um valor ''inicial'' como parâmetro. Sintaxe: {{{ #!python reduce(func, seq[, inicial]) -> valor }}} Exemplos: {{{ #!python >>> reduce(lambda x, y: x+y, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 55 >>> reduce(lambda x, y: x+y, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 100) 155 }}} == Filter == A função ''filter'' retorna uma seqüência ''seq_ret'' cujos valores são os elementos da seqüência de entrada ''seq'' que respeitam o seguinte critério: se a função for ''None'' os elementos que são verdadeiros são adicionados, caso uma função ''func'' esteja definida o valor de retorno da função é utilizado como valor verdade e apenas esses elementos vão fazer parte da seqüência de retorno. Sintaxe: {{{ #!python filter(func ou None, seq) -> seq_ret }}} A seqüência ''seq_ret'' tem sempre o mesmo tipo de ''seq''. Exemplos: {{{ #!python >>> filter(None, [0, 1, 2, None, 2>1, '', 'ola']) [1, 2, True, 'ola'] >>> filter(lambda x: x > 3, [0, 1, 2, 3, 4, 5]) [4, 5] >>> filter(lambda s: s > 'a', 'python r0cks!') 'pythonrcks' }}} == Zip == A função ''zip'' retorna uma seqüência ''seq_ret'' cujos elementos são tuplas resultantes de cada um dos elementos de uma ou mais seqüências de entrada ''seq1, seq2, ..., seqN''. A seqüência resultante é sempre truncada ao tamanho da menor seqüência apresentada. Sintaxe: {{{ #!python zip(seq1, seq2, ..., seqN) -> seq_ret }}} Onde ''seq_ret'' é obtida através do seguinte logicismo: {{{ #!python seq_ret[0] = (seq1[0], seq2[0], ..., seqN[0]) seq_ret[1] = (seq1[1], seq2[1], ..., seqN[1]) ... seq_ret[m-1] = (seq1[m-1], seq2[m-1], ..., seqN[m-1]) }}} O valor de ''m'' é o comprimento da menor lista em ''seq''. Exemplos: {{{ #!python >>> zip([1, 3, 5], [2, 4, 6]) [(1, 2), (3, 4), (5, 6)] >>> zip([1, 3, 5], [2, 4, 6, 8, 10]) [(1, 2), (3, 4), (5, 6)] }}} = Exemplos Mais Complexos = Retirado do "Programming FAQ" em python.org, seção 2.12: {{{ #!python # Primes < 1000 print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0, map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000))) # First 10 Fibonacci numbers print map(lambda x,f=lambda x,f:(x<=1) or (f(x-1,f)+f(x-2,f)): f(x,f), range(10)) # Mandelbrot set print (lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y, Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM, Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro, i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y >=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr( 64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy ))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24) # \___ ___ \___ ___ | | |__ lines on screen # V V | |______ columns on screen # | | |__________ maximum of "iterations" # | |_________________ range on y axis # |____________________________ range on x axis }}} Magia negra? Concordo!