associação pythonbrasil[11] django zope/plone planet Início Logado como (Entrar)

PrincipiosFuncionais

Principios da Programação Funcional em Python

# E alguns Linguagismos Pythonicos sobre Funções em geral ;)

Introdução

O conceito de funções em programação vem da matemática. Uma função serve para relacionar um conjunto (chamado de domínio) a outro (Imagem). Em programação, o domínio de uma função seriam os valores que pode-se passar como argumentos, e a Imagem é o conjunto dos possíveis valores de retorno. Para cada elemento no conjunto Domínio, deve haver apenas um elemento correspondente no conjunto imagem. Em programação funcional (e na matemática) esse é o princípio de idempotência, ou seja, para determinado valor de argumento só há uma possibilidade de retorno a cada vez que se passar um este valor como argumento.

Esse conceito foi introduzido na programação no final dos anos 50 pela linguagem Lisp, numa época em que a linguagem de mais alto nível que existia era o primeiro Fortran. Muitas linguagens desde então sofreram algum tipo de influência de Lisp e seus idiomas, direta ou indiretamente. Inclusive Python, que, por exemplo, tem um tipo função.

Segundo o artigo da Wikipedia Python programming language - seção "Philosophy", Python é uma linguagem multiparadigma. Então por que não aprender mais um? :)

Definindo Funções em Python

Antes de explorar os conceitos funcionais, vamos deixar os mecanismos de PYthon para criação e uso de funções bem claros.

As funções em Python são definidas através da palavra chave def. Sua sintaxe é a seguinte:

   1 def NomeDaFuncao(arg1, arg2, argn):
   2     <codigo>
   3     return NomeDoObjetoARetornar

Argumentos

Em Python, os argumentos podem ou não ter um valor inicial. Quando se dá um valor inicial na definição, não é obrigatório passar nenhum valor para esse argumento na chamada da função.

>>> def func(a,b='mundo',c='!'):
...  print a, b, c
...
>>> func('olá')
olá mundo !
>>> func('olá', 'pessoal')
olá pessoal !
>>> func()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: func() takes at least 1 argument (0 given)

Quando houver mais de um argumento opcional na função e você quiser especificar apenas um ou mais deles, você pode chamar a função dizendo qual argumento você quer especificar:

>>> func('adeus',c='?')
adeus mundo ?

É possível declarar um argumento para pegar os parâmetros extras passados à função, precedendo-o com um asterisco:

>>> def func(a,b='b',*args,**kwargs):
...  print 'a = %s\nb = %s\nargs = %s\nkwargs = %s' % (a,b,args,kwargs)
...
>>> func('a','c','d','e',par1='f',par2='g')
a = a
b = c
args = ('d', 'e')
kwargs = {'par2': 'g', 'par1': 'f'}

Note que foram declarados dois argumentos precedidos por asteriscos. O primeiro, precedido por um asterisco, irá receber os parâmetros extras passados em forma de tupla, e o segundo irá receber os parâmetros extras nomeados em forma de dicionário. Experimente outras variações no shell do python.

   1 # Esta função é levemente semelhante ao "cdr" do lisp, mas o cdr do Lisp recebe uma lista
   2 # Esta recebe o que seriam os elementos da lista
   3 def cdr(arg1, *argN):
   4     "Retorna tupla com os argumentos, excluindo o primeiro"
   5     return argN

Ao contrário de algumas linguagens que não lhe permitem passar uma matriz como argumento, Python não faz restrições quanto a passar uma sequência (strings, listas, tuplas, unicodes) como argumento.

   1 def spam(x=[1, 2, 3]):
   2     if isinstance(x, list):
   3         return "É possível passar uma lista como argumento em Python!"

Você pode ainda se aproveitar do polimorfismo da linguagem e permitir que sejam passados "tipos afins", como tipos que podem ser usados numa iteração, e não só um tipo, como lista por exemplo, aumentado a possibilidade de reuso do código.

Retornando valores

Você também pode retornar qualquer objeto (até mesmo uma função) de uma função.

   1 def RetornaFuncao(x=type):
   2     return x
   3 
   4 RetornaFuncao()(list)

Nesse exemplo, a ultima linha deve retornar <type 'type'>, por que list é um tipo

Sua função pode ainda ter varias vezes a palavra return.

   1 def isDead(parrot):
   2     "Retorna True se parrot avaliar como False"
   3     if not obj:
   4         return True
   5     else:
   6         return False

Caso existam returns alinhados, no mesmo bloco, apenas o primeiro funcionará:

   1 def DoisReturns(x, y):
   2     return x
   3     return y

Python permite que sejam criadas funções HighOrder, ou seja, uma funções que recebem como argumento ou retornam outras funções. Isso é especialmente interessante de ser usado em conjunto com @Decorators.

   1 

Chamadas de Função

Na hora de chamar uma função, você pode passar como argumento outra chamada de função, e será passado o valor de retorno dessa. Assim os valores fluirão de uma função para outra, sem necessidade uma variável de "transporte".

   1 #Exemplo mais inútil impossível, mas espero que você pegue a idéia :)
   2 range(sum([0, 1, 2]))

Entre outras coisas "pensar funcionalmente" pode ser definido assim: projetar algoritmos onde os valores fluem do retorno de uma função para a chamada de outra.

Você pode usar um asterisco antes de um argumento na chamada da função para passar os argumentos da função atravez de uma lista.

   1 #lembra do nosso cdr?
   2 cdr(*range(5))

"Side-Effects"

"Side-Effect", ou algo como 'efeito colateral', é um termo usado para indicar que uma função faz uma operação "destrutiva" , ou seja, ela altera, por exemplo, o valor de um objeto global, ou faz alguma operação de IO.

   1 x = 1
   2 def our_double_a():
   3     global x
   4     x += x
   5 
   6 our_double_a()
   7 print x
   8 #Isso deve imprimir 2 n tela

   1 def our_double_b(y):
   2     y += y
   3     return y
   4 #Se o que quisermos e apenas alterar o valor de x...
   5 x = 1
   6 x = our_double_b(x)
   7 #Se o que quisermos e apenas imprimir o valor de retorno...
   8 x = 1
   9 print our_double_b(x)

Evite Side-Effects em suas funções. Sua função fica mais difícil de se adivinhar o que faz do que se você retornasse um valor que pudesse ser explicitamente atribuído a um nome. Procure usar sempre os argumentos e os returns. Seguindo essa regra suas funções serão inclusive mais uteis em outras ocasiões (maior reaproveitamento de código)

Por exemplo, na nossa função "our_double_a" é difícil de imaginar que ela alteraria o valor de x. Além disso, ela fica limitada a isso: alterar o valor do nome x. Compare com a função our_double_b: fica claro quando eu estou alterando o valor de x ou não.

Se você realmente pretende fazer uma função que tem Side-Effects, deixe isso bem claro no nome (adicionando um prefixo "set" e o que ele "seta", com "setName", por exemplo) e na docstring da função.

<exemplo>

Recursão

Uma função que chama a si mesma é uma função recursiva.

Muitas pessoas acham extremamente complicado aplicar o conceito de recursão, muitos alunos de programação não conseguem desenvolver algoritmos recursivos sem gastar **muito** grafite e papel antes. Muitos programadores acostumados com paradigmas estruturados também tem essa dificuldade, A maioria dos programadores evitam implementações recursivas de seus algoritmos, por motivos de performance também, mas muitas vezes nem percebemos que um código poderia usar recursão.

Certas linguagens funcionais, como Scheme, prevêem até mesmo que seus interpretadores tenham algum tipo de otimização de recursão.

Pergunte a um estudante dedicado de programação "Quando colocar um trecho de código em um bloco separado?" e ele dirá algo como "Quando o código se repete". Mas quando o código se repete sucessivamente, normalmente usamos for ou while. Quando não houverem grandes problemas para a performance, a recursão pode valer a pena se trouxer um melhor estilo.

   1 def factorial(x):
   2     if x == 0:
   3         return 1
   4     else:
   5         return x * factorial(x-1)

No livro capitulo 2.7 ANSI Common Lisp, Recursion, PaulGraham diz: (livre tradução)

(...) Muitas pessoas pessoas acham difícil a recurção no inicio. Muita dessa dificuldade vem do uso de uma metafora enganosa para funções. Há uma tendencia a pensar em funções como um tipo de maquinas. Matéria prima entra como os parâmetros; Algumas tarefas são repassadas para outras funções; finalmente o produto acabado é montado e entregue como o valor de retorno. se nós usarmos essa metáfora para funções, a recursão se torna um paradoxo. Como uma maquina pode enviar tarefas para ela mesma? Ela já esta ocupada.

Uma melhor metáfora para a função seria pensar nela como um processo que se passa. Recursão é natural em um processo(...)

Closures

Python suporta Closures (ou lexical closures) desde a versão 2.0, atravéz dos recurso de Nested Scopes.

Exemplo:

   1 <exemplo>

Estilo

Procure usar o caracter \ para quebrar a linha e evitar linhas muito longas e ilegiveis. Exemplo:

   1 function1(\
   2           function11("foo"),\
   3           function12(\
   4                      function121(1, "bar"),\
   5                      function122("foobar")),\
   6           function13(84))

Conclusão

Links

Na Wiki

PythonFuncional

ProgramacaoFuncional

UsandoGenerators

No resto da Web

Dive into Python - Chapter 16. Functional Programming

Wikipedia - Python programming language - Functional programming

C2 Wiki - Higher Order Function

Python na Wikipedia - Seção Functional Programming

Por fazer:

  • Adicionar Links para trechos afins do DiveIntoPython, Thinking Like a Computer Scientist, etc

  • Adicionar mais exemplos! (de preferência tirados do UselessPython !!)

  • Mais exemplos, mais exemplos!!! De preferência, alguns úteis ou interessantes :)

  • Mais sobre HighOrder functions, quem sabe uma seção própria?

  • Concluir a seção sobre Closures, com exemplos.

  • Citar e exemplificar o lambda

  • Uma seção somente com Exemplos Práticos.

  • Uma Conclusão


EduardoDeOliveiraPadoan


Sugestão: Esta página fala na maior parte de particularidades das funções em python. Poderíamos mover este conteúdo para outra página (que poderia ser usada no NossoLivro) e deixar aqui apenas as coisas relativas à programação funcional. LuizCarlosGeron