Archive for the ‘Extensões funcionais’ Category.

Lambda Calculus – Parte 2

Expressões Lambda

Para criarmos uma expressão que retorne um functor iremos usar templates. Para isso precisaremos redefinir todos os operadores – aritméticos, lógicos, relacionais etc. Por exemplo, para podermos escrever uma expressão “2*x + 1” precisamos redefinir “*” e “+”. No entanto, embora possamos redefinir “*” para Var e int, o operador “+” precisa ser redefinido para o functor retornado por “2*x” e int. Como a princípio a expressão do lado esquerdo pode ser qualquer coisa (a do lado direito também), não temos como saber a priori qual o seu tipo. Por outro lado, não podemos redefinir o operador “+” para tipos genéricos sob o risco de fazer parar de funcionar outras templates. Assim, precisamos distinguir o que é uma expressão lambda de outros tipos do programa. A melhor forma de fazer isso é criar uma template Lambda que apenas encapsula um functor qualquer, e redefine o operador “()” para um parâmetro qualquer. Pensando dessa forma, a própria variável x pode ser do tipo Lambda, sob a forma de especialização de templates. O tipo Lambda será então apenas um envelope para expressões/functores, repassando para a expressão/functor a tarefa de executar a operação desejada.

template <class Functor>
struct Lambda {
  const Functor f;

  Lambda( const Functor& f ): f(f) {}

  template <class T>
  T operator()( const T& x ) const { return f( x ); }
};

struct Var {
  template <class T>
  T operator()( const T& x ) const { return x; }
};

template <>
struct Lambda< Var > {
  Var f;

  template <class T>
  T operator()( const T& x ) const { return x; }
};

typedef Lambda< Var > ParamX;

ParamX x;

Assim podemos redefinir os operadores para atuarem apenas em tipos Lambda< T >.

Para podermos encadear os operadores devemos primeiro transformá-los em functores com uma interface única, de modo que a chamada a um operador possa ser capturada em uma template (a STL usa essa técnica). Para tanto, criaremos uma struct com uma template para uma função estática (que pode ser chamada sem um objeto) que recebe dois parâmetros e retorna a aplicação do operador a estes parâmetros.

struct Adicao {
  template <class A, class B>
  static A aplica( A a, B b ) { return a + b; }
};

struct Multiplicacao {
  template <class A, class B>
  static A aplica( A a, B b ) { return a * b; }
};

struct Subtracao {
  template <class A, class B>
  static A aplica( A a, B b ) { return a - b; }
};

struct Divisao {
  template <class A, class B>
  static A aplica( A a, B b ) { return a / b; }
};

Dessa forma, com uma única template, que chamaremos de Oper, podemos encadear os operadores:

template <class O, class A, class B>
class Oper {
    private:
      const A a;
      const B b;

    public:
      Oper( const A& a, const B& b ): a(a), b(b) {}

      template <class T>
      T operator()( const T& x ) const { return O::aplica( a(x), b(x) ); }
};

Essa template possui dois campos, a e b, que são eles mesmos functores – e é exatamente isso que irá permitir que uma expressão seja representada por functores encadeados. Por exemplo, para representar x + x podemos escrever o seguinte código:

int main(int argc, char *argv[]) {
  Oper< Adicao, ParamX, ParamX > expr( x, x );

  cout << expr( 10 ) << endl;

  return EXIT_SUCCESS;
}

Esse código produz como saída 20. Mas o que queremos é poder realmente escrever x + x. Para tanto basta escrever uma template para o operador + com parâmetros Lambda< T >.

template <class A, class B>
inline Lambda< Oper< Adicao, A, B > > operator + ( const Lambda<A>& a, const Lambda<B>& b ) {
  return Oper< Adicao, A, B  >( a.f, b.f );
}

template <class A, class B>
inline Lambda< Oper< Multiplicacao, A, B > > operator * ( const Lambda<A>& a, const Lambda<B>& b ) {
  return Oper< Multiplicacao, A, B  >( a.f, b.f );
}

template <class A, class B>
inline Lambda< Oper< Subtracao, A, B > > operator - ( const Lambda<A>& a, const Lambda<B>& b ) {
  return Oper< Subtracao, A, B  >( a.f, b.f );
}

template <class A, class B>
inline Lambda< Oper< Divisao, A, B > > operator / ( const Lambda<A>& a, const Lambda<B>& b ) {
  return Oper< Divisao, A, B  >( a.f, b.f );
}

Com isso podemos já escrever uma expressão mais complexa, como por exemplo x*x + x.

Para um mínimo de funcionalidade, precisamos lidar com constantes nas expressões lambda. Para tanto precisamos de uma classe que guarde o valor de uma constante de qualquer tipo e funcione como um functor para um parâmetro, retornando o valor dessa constante qualquer que seja o valor desse parâmetro. Além disso, precisamos de templates para os operadores lidarem com constantes e variáveis, para todos os operadores (abaixo fizemos apenas para “+”).

template <class C>
class Constante {
  private:
    const C n;

  public:
    Constante( const C& n ): n(n) {}

    template <class T>
    C operator()( const T& ) const { return n; }
};

template <class A, class B>
inline Lambda< Oper< Adicao, A, Constante<B> > > operator + ( const Lambda<A>& a, const B& b ) {
  return Oper< Adicao, A, Constante<B> >( a.f, b );
}

template <class A, class B>
inline Lambda< Oper< Adicao, Constante<B>, A > > operator + ( const A& a, const Lambda<B>& b ) {
  return Oper< Adicao, Constante<B>, A >( a, b.f );
}

Assim, já podemos escrever o seguinte código passando uma expressão lambda para uma função:

template <class Functor>
void imprime( int a, int b, Functor f ) {
  for( int i = a; i <= b; i++ )
    cout << f(i) << " ";

  cout << endl;
}

int main(int argc, char *argv[]) {
  imprime( 0, 10, x*x + x + 1 );

  return EXIT_SUCCESS;
}

Esse programa produz como saída o seguinte: 1 3 7 13 21 31 43 57 73 91 111

Na Parte 3 dessa série veremos como lidar com atribuições. Nesse link aqui você pode baixar o código completo desse post.

“I left alone my mind was blank/I needed time to think to get the memories from my mind”

Lambda Calculus – Parte 1

Criando funções on-the-fly

Expressões lambda e cálculo lambda são abstrações muito utilizadas em teoria da computação. E são uma ótima forma de se definir funções on-the-fly, ou seja, no meio do código. Aqui vou mostrar uma pequena introdução sobre como fazer isso em C++, e em outros posts irei aperfeiçoar a idéia. Na biblioteca Boost existe uma boa implementação de expressões lambda, mas ela é pouco didática para se estudar. Melhor a gente ter uma idéia de como implementar primeiro e depois usar a biblioteca.

Um exemplo de uma expressão lambda é dado a seguir:

λxy.x+y
(λxy.x+y)( 2, 4 )

Na linha 1 está uma expressão que define uma função (sem nome e sem tipos) que recebe dois parâmetros x e y e retorna a soma deles. A idéia central aqui é função sem nome e sem tipos. Na linha 2 essa expressão é avaliada para os argumentos 2 e 4, do tipo int, retornando 6.

A principal utilidade de expressões lambda é para substituir functores nas funções da STL, por exemplo for_each, permitindo que a função a ser passada como parâmetro seja definida on-the-fly. As vantagens desse tipo de definição são duas: a definição da função fica perto do seu uso, e desempenho, pois tudo é expandido inline.

Para se criar expressões lambda em C++ podemos fazer algumas simplificações. A primeira delas é abolir a letra λ e a segunda é fixar os nomes dos parâmetros. Nessa primeira parte do post, irei criar apenas expressões lambda de um parâmetro, que se chamará x. Além disso, as expressões lambda não terão tipo – o que nos indica que serão templates. Uma expressão lambda “x + 1″ deve retornar um functor que soma 1 ao argumento passado na chamada dele. Assim, (x + 1)( 10 ) deve retornar 11. O código a seguir ilustra onde queremos chegar.

#include <iostream>
#include <vector>
#include <lambda>

using namespace std;

struct Imprime {
  ostream& o;

  Imprime( ostream& o ): o(o) {}

  template <class T>
  void operator () ( T n ) {
    o << n << " ";
  }
};

int main(int argc, char *argv[]) {
  vector<int> a;

  a.push_back( 1 );
  a.push_back( 2 );
  a.push_back( 3 );
  a.push_back( 4 );
  a.push_back( 5 );

  for_each( a.begin(), a.end(), x = x*x+1 );

  for_each( a.begin(), a.end(), Imprime( cout ) );
}

Saída:

2 5 10 17 26

A expressão “x = x*x+1” retornou um functor que calcula esse valor, e ele foi passado como parâmetro para a função for_each, produzindo o efeito desejado nos elementos do vetor. O functor Imprime na verdade não está ali por acaso: ele servirá de idéia base para a construção de expressões lambda. Seu operador de chamada de função é uma template, e pode ser utilizado para qualquer tipo que possa ser passado para um ostream através do operador “<<”. Ou seja, ele é um functor que encapsula a expressão cout << x << ” “.

Se fizermos um operador “<<” para ostream e um tipo novo para representar x (Var por exemplo) e se esse operador ao invés de imprimir retornar um objeto do tipo Imprime, poderemos criar uma expressão lambda.

Primeira Expressão Lambda:

#include <iostream>
#include <vector>
#include <lambda>

using namespace std;

struct Imprime {
  ostream& o;

  Imprime( ostream& o ): o(o) {}

  template <class T>
  void operator () ( T n ) {
    o << n << " ";
  }
};

struct Var {};
Var x;

Imprime operator << ( ostream& o, Var ) {
  return Imprime( o );
}

int main(int argc, char *argv[]) {
  vector<int> a;

  a.push_back( 1 );
  a.push_back( 2 );
  a.push_back( 3 );
  a.push_back( 4 );
  a.push_back( 5 );

  for_each( a.begin(), a.end(), x = x*x+1 );

  for_each( a.begin(), a.end(), cout << x );
}

É essa a idéia central: representar através de functores sem tipo as operações que compõem uma expressão lambda. A expressão é montada através de operadores
redefinidos, conforme podemos ver nas linhas 21 e 36.

No próximo post, mostro como encadear as expressões.

“Se a experiência funcionou na primeira tentativa, tem algo errado.”

Funções x Functores… qual a melhor opção?

Na dúvida entre usar um ponteiro para uma função ou criar um functor?

Antes de mais nada, é preciso deixar claro o que é um functor: um objeto função, também chamado de um functor, é uma construção de programação que permite a um objeto ser invocado ou chamado como se fosse uma função comum, geralmente com a mesma sintaxe. Ou seja, qualquer objeto cuja classe redefina o operador “()” pode ser considerado um functor.

Na STL existem diversas funções (a maioria definidas como templates) que recebem como parâmetro um ponteiro para uma função, que pode ser também um functor. Um exemplo típico é a função for_each, que percorre elementos de uma coleção qualquer aplicando uma determinada função. for_each recebe três parâmetros: dois iteradores, início e fim, e uma função (ou functor) que será chamado para cada elemento da coleção. Segue um exemplo onde é mostrado também o código de uma possível versão da função forEach definida como template.

Código Fonte usando ponteiro para função:

#include <cstdio>

using namespace std;

template <class Itr, class F>
inline void forEach( Itr i, Itr fim, F f ) {
  while( i != fim )
    f( *i++ );
}

inline void print( int n ) {
  printf("%d ", n );
}

int main(int argc, char *argv[])
{
  int tab[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

  forEach( tab, tab+10, print );
}

O programa acima imprime os números que estão no array tab. Note que a função forEach é genérica o suficiente par trabalhar tanto com arrays (ponteiros) como com containers da STL. E embora todas as funções estejam definidas como inline, a função print não será expandida inline dentro de forEach – isso ocorre porque está sendo usado um ponteiro para esta função. Por outro lado a função forEach será expandida inline dentro da função main. Observe o código assembly gerado, com as opções de otimização ligadas (-O3).

Código assembly gerado:

	.file	"main.cpp"
	.section .rdata,"dr"
LC0:
	.ascii "%d \0"
	.text
	.align 2
	.p2align 4,,15
.globl __Z5printi
	.def	__Z5printi;	.scl	2;	.type	32;	.endef
__Z5printi:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$8, %esp
	movl	8(%ebp), %eax
	movl	$LC0, (%esp)
	movl	%eax, 4(%esp)
	call	_printf
	leave
	ret
	.def	___main;	.scl	2;	.type	32;	.endef
	.align 2
	.p2align 4,,15
.globl _main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
	pushl	%ebp
	movl	$16, %eax
	movl	%esp, %ebp
	pushl	%edi
	pushl	%esi
	pushl	%ebx
	subl	$76, %esp
	andl	$-16, %esp
	call	__alloca
	movl	$__Z5printi, %edi
	leal	-72(%ebp), %ebx
	leal	-32(%ebp), %esi
	call	___main
	movl	$0, -72(%ebp)
	movl	$1, -68(%ebp)
	movl	$2, -64(%ebp)
	movl	$3, -60(%ebp)
	movl	$4, -56(%ebp)
	movl	$5, -52(%ebp)
	movl	$6, -48(%ebp)
	movl	$7, -44(%ebp)
	movl	$8, -40(%ebp)
	movl	$9, -36(%ebp)
	jmp	L11
	.p2align 4,,7
L13:
	movl	(%ebx), %edx
	addl	$4, %ebx
	movl	%edx, (%esp)
	call	*%edi
L11:
	cmpl	%esi, %ebx
	jne	L13
	leal	-12(%ebp), %esp
	xorl	%eax, %eax
	popl	%ebx
	popl	%esi
	popl	%edi
	leave
	ret
	.def	_printf;	.scl	2;	.type	32;	.endef

Detalhes:

Assembly é um porre de se olhar, mas vamos lá… Coloquei em vermelho as partes importantes. A função __Z5printi é como o compilador chamou a função print do programa fonte em C++. Ela é criada, apesar de ser declarada como inline, porque na função main o seu endereço foi passado para a função forEach, como dito acima. Sempre que for necessário, o compilador irá gerar código para uma função declarada inline, por exemplo quando um ponteiro para a função é usado. Mas não se preocupe, a função não deixou de ser inline, nas outras chamadas ela será expandida normalmente. No código dessa função vemos a chamada a printf. __Z5printi é movido para o registrador edi, e logo em seguida no bloco L13 há um call para o endereço nesse registrador.

Agora compare o que ocorre quando usamos um functor no lugar de uma função. Esse functor é bem simples, apenas uma struct com o operador “()” redefinido, e recebendo um int.

Código Fonte usando functor:

#include <cstdio>

using namespace std;

template <class Itr, class F>
inline void forEach( Itr i, Itr fim, F f ) {
  while( i != fim )
    f( *i++ );
}

struct Imprime {
  void operator () ( int n ) {
    printf("%d ", n );
  }
};

int main(int argc, char *argv[])
{
  int tab[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

  forEach( tab, tab+10, Imprime() );
}

Note que na chamada a forEach é passado um objeto temporário do tipo Imprime. Pode-se também declarar uma variável local e passá-la, mas não há necessidade. Por outro lado, o C++ exige que ao se criar um objeto temporário se chame o construtor, nesse caso, o default, sem nenhum parâmetro.

Código assembly gerado:

	.file	"main.cpp"
	.def	___main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
LC0:
	.ascii "%d \0"
	.text
	.align 2
	.p2align 4,,15
.globl _main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
	pushl	%ebp
	movl	$16, %eax
	movl	%esp, %ebp
	pushl	%esi
	pushl	%ebx
	subl	$64, %esp
	andl	$-16, %esp
	call	__alloca
	leal	-56(%ebp), %ebx
	leal	-16(%ebp), %esi
	call	___main
	movl	$0, -56(%ebp)
	movl	$1, -52(%ebp)
	movl	$2, -48(%ebp)
	movl	$3, -44(%ebp)
	movl	$4, -40(%ebp)
	movl	$5, -36(%ebp)
	movl	$6, -32(%ebp)
	movl	$7, -28(%ebp)
	movl	$8, -24(%ebp)
	movl	$9, -20(%ebp)
	jmp	L11
	.p2align 4,,7
L13:
	movl	(%ebx), %edx
	addl	$4, %ebx
	movl	$LC0, (%esp)
	movl	%edx, 4(%esp)
	call	_printf
L11:
	cmpl	%esi, %ebx
	jne	L13
	leal	-8(%ebp), %esp
	xorl	%eax, %eax
	popl	%ebx
	popl	%esi
	leave
	ret
	.def	_printf;	.scl	2;	.type	32;	.endef

Resumo da ópera:

Agora sim… o compilador não declarou nenhuma função para o operador “()” da struct Imprime, expandido a chamada a printf diretamente no código da função main no bloco L13. Portanto, é mais vantajoso usar um functor do que um ponteiro para uma função, especialmente no caso de templates expandidas inline, que são a grande maioria na prática. O código gerado é menor e se evita uma chamada de função. Claro, em um programa pequeno esse custo é desprezível, mas o objetivo aqui é mostrar que o uso de functores geram um código mais eficiente do que o uso de ponteiros para funções, ao contrário do que o senso comum diz. Novamente, é necessário ajudar o otimizador.

Haveria alguma vantagem em usar um functor se o código da template forEach não fosse inline? Sim, pois mesmo dessa forma uma chamada de função é evitada, pois o operador “()” seria expandido inline dentro de forEach.

E se a função forEach não fosse template? Aí, provavelmente ela receberia um functor por referência e polimórfico, ou seja, com o operador “()” declarado como virtual e toda uma hierarquia de functores… e então talvez empatasse com a opção usando ponteiro para função! Mas eficiência não é a única vantagem dos functores: por serem objetos, podem ter outros campos etc.

Though his mind is not for rent/Don’t put him down as arrogant