Lazy Evaluation
19/02/2008
Agora, voltando a falar um pouco sobre funções, um conceito que pode ser muito útil é chamado Lazy Evaluation (Avaliação preguiçosa, ou algo do tipo).
Veja o exemplo dessa função:
string call(bool b, string s)
{
if(b)
return s;
else
return "";
}
Como podem ver, essa função (inútil, mas é apenas para demonstrar) retorna seu segundo argumento (s) se seu primeiro argumento (b) for verdadeiro.
Um problema com isso é que seu segundo argumento é sempre avaliado. Imaginando que ele seja uma função relativamente complexa, como a função “convert”, da classe Layout (função parecida com string.Format do C# ou String.format do Java), isso seria um desperdício de processamento) em casos como:
call(false, Format("{}", i));
(Format é uma “instância global” de Layout para char’s, definida em tango.text.convert.Format
Uma solução para isso seria passar um ponteiro para uma função ou um delegate (ponteiro para um membro, ou uma função interna) que faz a formatação:
call(false, { return Format("{}", i); });
Com isso, seria necessário trocar return s; em call por return s();.
Claro que é meio incomode ter que usar a sintaxe para delegates literais sempre que se quiser esse tipo de otimização, por isso foi criado o conceito de argumento preguiçoso (lazy), que basicamente é convertido para um delegate. Veja a função:
string lazyCall(bool b, lazy string s)
{
if(b)
return s();
else
return "";
}
Obs.: string é um alias que eu criei para um array de char’s ou invariant(char)’s, dependendo da versão da linguagem ou biblioteca sendo usada. Ele é definido como:
static if(!is(string))
{
static if(is(typeof(object.Object.toString) R == return))
alias R string;
}
Esse trecho verifica se já não existe um tipo string já definido (lembrando que ele foi adicionado ao módulo “object” para Phobos v2). Se não existir, ele pega o tipo que o método toString (método existente em Phobos e adicionado à Tango na versão 0.99.4 Frank – antes disso era toUtf8 da classe Object (classe esta existente em Phobos e em Tango no módulo object) em R e cria um alias para esse tipo como string. Uma observação: não vá confundir o typeof em D e em C#. Em C#, você usaria typeof(tipo) para retornar uma instância de System.Type para o tipo (ou algo do tipo) passado. Em D, você usa typeof(variável) para retornar o tipo da variável.
static if é uma forma de compilação condicional da linguagem. A condição dessa instrução é avaliada em tempo de compilação, e não é criado um novo escopo para seu bloco de instruções.
Esta forma de is é para verificar, em tempo de compilação, a validade ou não de algum tipo. Mais informações sobre isso futuramente (assim espero!).
Para demonstrar a eficiência disso, criei este simples benchmark:
module Lazy; import tango.io.Stdout;
import tango.text.convert.Format;
import tango.time.StopWatch;const j = 10000000u; static if(!is(string))
{
static if(is(typeof(object.Object.toString) R == return))
alias R string;
}
string lazyCall(bool b, lazy string s)
{
if(b)
return s();
else
return "";
}
string call(bool b, string s)
{
if(b)
return s;
else
return "";
}
private void benchmarkImpl(alias fn)(bool b)
{
StopWatch e;
Stdout.formatln("Executando {} vezes com b = {}", j, b);
e.start();
for(uint i = 0; i < j; i++)
fn(b, Format("{}", i));
Stdout.formatln("Tempo de execução: {}", e.stop()).newline;
}
void benchmark(alias fn)()
{
benchmarkImpl!(fn)(true);
benchmarkImpl!(fn)(false);
}
void main()
{
Stdout("lazyCall").newline;
benchmark!(lazyCall)();
Stdout("-----------------------").newline; Stdout("call").newline;
benchmark!(call)();
}
Coloque isso em um arquivo chamado Lazy.d e compile (tomara que compile).
Acho que a única novidade nesse código é o private void benchmarkImpl(alias fn)(bool b).
Então, vejamos o que é isso:
private, assim como para classes, torna a função benchmarkImpl visível apenas para o módulo Lazy (lembrando que em OOP em D, campos/métodos privados são visíveis para todo o módulo, não apenas para a classe, semelhantemente ao Delphi – mas não tente colocar strict private para mudar isso).
Fora isso, sobrou apenas a parte legal da declaração:
void benchmarkImpl(alias fn)(bool b).
Isso é equivalente a declarar um template da seguinte forma:
template benchmarkImpl(alias fn)
{
void benchmarkImpl(bool b) { ... }
}
O alias na declaração do template, neste caso, aceita símbolos globais no geral (no caso, uma função).
Usando Phobos (a biblioteca padrão), o exemplo ficaria mais ou menos assim:
module Lazy;import std.perf;
import std.stdio;
import std.string;
const j = 10000000u;
static if(!is(string))
{
static if(is(typeof(object.Object.toString) R == return))
alias R string;
}
string lazyCall(bool b, lazy string s)
{
if(b)
return s();
else
return "";
}
string call(bool b, string s)
{
if(b)
return s;
else
return "";
}
private void benchmarkImpl(alias fn)(bool b)
{
auto e = new PerformanceCounter();
writefln("Executando %s vezes com b = %s", j, b);
e.start();
for(uint i = 0; i < j; i++)
fn(b, format("%s", i));
e.stop;
writefln("Tempo de execução: %s", e.milliseconds / 1000.0);
}
void benchmark(alias fn)(){
benchmarkImpl!(fn)(true);
benchmarkImpl!(fn)(false);
}
void main(){
writefln("lazyCall");
benchmark!(lazyCall)();
writefln("-----------------------"); writefln("call");
benchmark!(call)();
}
blogblogs tags:linguagem, programacao, d
Entry Filed under: D, Dev. Tags: D, linguagem, programaçao.
Trackback this post | Subscribe to the comments via RSS Feed