2006-07-17

Metaprogramação

Nautilus Certa vez conversando com o Fernando, um colega meu da faculdade, ele me disse que deveria haver um paradigma de programação mais avançado do que orientação a objetos. Então comentei com ele sobre orientação a eventos e metaprogramação – esqueci do paradigma funcional, ó que vergonha!

Resolvi então falar sobre metaprogramação.

Vamos compreender o que significa este meta: o que é metalinguística? É o estudo liguístico da própria linguística! Por exemplo, um dicionário é um livro metalinguístico – a linguagem usada para falar da linguagem. =)

Entendendo o uso do prefixo (na verdade um radical), vamos agora entender o que é metaprogramação.

Há dois conceitos distintos mas relacionados.

O primeiro conceito é o de metacódigo: é o código que gera como resultado outro código. Neste caso, metaprogramação é a criação de metacódigo.

Vou dar um exemplo em Shell script:

#!/bin/bash
# criaget.sh

ARQ="get.sh"

echo -n "Informe o mirror: "
read MIRROR

echo -n "Informe o caminho para o arquivo: "
read CAMINHO

if [[ "${MIRROR:0:4}" != "http" && "${MIRROR:0:3}" != "ftp" ]]
then
MIRROR="http://$MIRROR"
fi

if [[ "${MIRROR%/}" = "${$MIRROR}" ]]
then
$MIRROR="${MIRROR}/"
fi

cat > $ARQ <<-EOF
#!/bin/bash

wget -c ${MIRROR}${CAMINHO}.md5
wget -c ${MIRROR}${CAMINHO}

cat *.md5 | md5sum --check
EOF

chmod +x $ARQ

exit


Este programa irá pedir um mirror e um arquivo neste mirror, e irá gerar um código (get.sh) que, quando executado, tentará baixar o arquivo informado, fazendo uma verificação de MD5.

Não é um conceito difícil, mas não é desse tipo de metaprogramação que quero falar. =P


Outro conceito de metaprogramação é quando elementos que geralmente são definidos na codificação, como funções e classes, são definidos em tempo de execução por outro elemento semelhante.

Ou seja: funções que retornam funções e classes cujas instâncias são classes.

Vamos a um exemplo em Python:

def inteiro(f):
def func(*args, **kw):
resp = f(*args, **kw)
return int(resp + .5)
return func


Olha só: esta função recebe uma função como argumento e retorna outra função. Por exemplo:

>>> def pre_fib(n):
n += 1
return (
pow(
(1. + pow(5., .5)) / 2.,
n
) - pow(
(1. - pow(5., .5)) / 2.,
n
)
) / pow(5., .5)

>>> fib = inteiro(pre_fib)
>>> [fib(x) for xrange(10)]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]


Esta é a fórmula fechada de Fibonacci, no entanto, como trabalha com números de ponto flutuante, os valores trazem erros de aproximação que precisam ser solucionados por conversão para inteiros.

A função inteiro() pega a função pre_fib() como parâmetro e retorna uma função que faz a mesma coisa que a original, mas traduzindo a resposta para inteiro. Portanto inteiro() é uma metafunção.

Python traz ainda um açúcar sintático chamado «decorador», que reduz a quantidade de código ao programar com metafunções. Por exemplo, a função acima poderia ser reescrita usando inteiro() como decorador:

@inteiro
def fib(n):
n += 1
return (
pow(
(1. + pow(5., .5)) / 2.,
n
) - pow(
(1. - pow(5., .5)) / 2.,
n
)
) / pow(5., .5)


Observação: classes podem ser usadas também como decoradores, desde que suas instâncias sejam «executáveis» (callable) – futuramente escreverei um artigo sobre memoization, então vou falar um pouco mais sobre isso.

Vamos agora falar de «metaclasses».

Metaclasse é uma classe que retorna como instância outra classe – é um recurso para criação de classes em tempo de execução (e/ou para salvar horas de codificação). =)

Em Python, metaclasses devem ser filhas (herdeiras) de type e o construtor deve receber como argumentos cls (referência à própria instância), name, bases e dict, assim como a função/classe type(), que cria tipos/classes dinamicamente.

Vamos ao exemplo proposto pelo próprio BDFL:

class autoprop(type):
def __init__(cls, name, bases, dict):
super(autoprop, cls).__init__(name, bases, dict)
props = {}
for name in dict.keys():
if len(name) > 5:
if name.startswith("_get_") \
or name.startswith("_set_") \
or name.startswith("_del_"):
props[name[5:]] = 1
for name in props.keys():
fget = getattr(cls, "_get_" + name, None)
fset = getattr(cls, "_set_" + name, None)
fdel = getattr(cls, "_del_" + name, None)
setattr(cls, name, property(fget, fset, fdel))


Esta metaclasse cria propriedades (atributos que chamam métodos) automaticamente em suas instâncias. Um exemplo:

class X:
__metaclass__ = autoprop

def __init__(self, x=None):
self.__x = 0
if x is not None:
self._set_x(x)

def _get_x(self):
return self.__x

def _set_x(self, v):
v = int(v + .5)
if v >= 0:
self.__x = v

def _del_x(self):
raise TypeError, "Nao eh possivel remover x"


Esta classe é uma instância de autoprop (não filha). Veja o que acontece com suas próprias instâncias:

>>> a = X()
>>> a.x
0
>>> a.x = 2.1
>>> a.x
2
>>> a.x = -4
>>> a.x
2
>>> del a.x
> del a.x

Traceback (most recent call last):
File "<pyshell#37>", line 1, in -toplevel-
del a.x
File "<pyshell#30>", line 14, in _del_x
raise TypeError, "Nao eh possivel remover x"
TypeError: Nao eh possivel remover x
>>> a.x
2


A propriedade x interceptou todas as chamadas a a.x e chamou os métodos convenientes (_get_x(), _set_x() e _del_x()). Para isso, a propriedade x precisou ser definida antes pela função property(). Onde esta função foi executada? Ela não existem na definição da classe X...

Exatamente: ela foi executada no construtor de autoprop, atribuindo assim a propriedade x – criada dinamicamente – a sua instância X.

Se alguém ficou em dúvida, comente este artigo e responderei!

PS: Walter, que tal escrever um artigo sobre metatabelas em Lua?