Não tenho dúvidas de que o C# é a linguagem que melhor evoluiu nos últimos anos. No entanto, são tantas mudanças e novidades que às vezes fica difícil acompanhar e aplicá-las no dia-a-dia.
Por exemplo, desde o C# 2.0, a linguagem vem sendo influenciada por conceitos de linguagem funcional. Estas novidades têm impacto na forma como escrevemos nosso código, nos constructors que utilizamos. E isto não muda de um dia para outro.
Por isto resolvi fazer algumas brincadeiras com C# para mostrar novas possibilidades na forma de programar já oferecidas pela linguagem. Vou começar com C# 2.0, sem as novidades do 3.5 e depois evoluir os exemplos.
High order function é um conceito típico da programação funcional: São funções que recebem uma ou mais funções como parâmetro ou retornam uma função. Para explicar melhor vou implementar alguns exemplos clássicos de high order functions: Filter, Map e Fold.
Filter
Um filtro processa uma coleção de objetos, filtrando-a de acordo com uma regra de decisão. Vamos considerar um exemplo simples, mas funcional, preciso filtrar de uma coleção de clientes todos que residem no estado do Pará. A implementação mais simples para resolver este problema seria:
private List<Cliente> GetClientesDoPara(List<Cliente> clientes)
{
List<Cliente> resultado = new List<Cliente>();
foreach (Cliente cliente in clientes)
if (cliente.Estado.Equals("Para"))
resultado.Add(cliente);
return resultado;
}
No dia seguinte preciso filtrar todos os clientes cujo nome começa com “P”. Posso utilizar a velha técnica de reuso de código conhecida como Ctrl+C, Ctrl+V. Todavia, estamos aqui para escrever código de qualidade, então esta opção não é aceitável. Para tornar este método reutilizável, preciso identificar o que pode ser reaproveitado e o que deve ser mutável.
Tudo que está rachurado é código necessário e repetido para qualquer filtro sobre um cliente. É preciso criar uma nova lista de clientes, iterar sobre a lista atual, adicionar os escolhidos na nova lista e retorná-la ao final. O único trecho que muda é o algoritmo de decisão se o cliente entra ou não na nova lista.
O problema é que este algoritmo é uma função, que retorna um boleano, ou seja, preciso de uma função high-order, que recebe como parâmetro uma função. A forma de fazer isto em C# é utilizando delegate. Delegates permitem a passagem de uma função, com uma determinada assinatura, para outra função. Então vou: Abstrair a lógica de filtro para uma nova função, chamada FiltraClientes, que recebe a lista de clientes e o delegate da função de decide se o cliente é filtrado ou não; Criar este delegate, que recebe um cliente e retorna um boleano; e abstrair a lógica de filtro em um método que segue a assinatura deste delegate. Veja como ficou:
delegate bool Predicate(Cliente cliente);
private bool ClienteResideNoPara(Cliente cliente)
{
return cliente.Estado.Equals("Pará");
}
private List<Cliente> GetClientesDoPara(List<Cliente> clientes)
{
return FilterClientes(clientes, ClienteResideNoPara);
}
private List<Cliente> FilterClientes(List<Cliente> clientes, Predicate filter)
{
List<Cliente> resultado = new List<Cliente>();
foreach (Cliente cliente in clientes)
if (filter(cliente))
resultado.Add(cliente);
return resultado;
}
Para finalizar utilizo o generics e a interface IEnumerable para tornar tudo independente do tipo da coleção e do objeto, ou seja filtro qualquer coleção de objetos, não interessa o seu tipo.
delegate bool Predicate<T>(T item);
private bool ClienteResideNoPara(Cliente cliente)
{
return cliente.Estado.Equals("Pará");
}
private List<Cliente> GetClientesDoPara(List<Cliente> clientes)
{
return (List<Cliente>) Filter(clientes, ClienteResideNoPara);
}
private IEnumerable<T> Filter<T>(IEnumerable<T> collection, Predicate<T> filter)
{
List<T> resultado = new List<T>();
foreach (T item in collection)
if (filter(item))
resultado.Add(item);
return resultado;
}
Na próxima vou fazer o mesmo exercício para o Map. Aguardem