Programação funcional em C#

Programação funcional com C#, na prática, para o dia a dia

Tem algo mais frustrante do que abrir o stackoverflow e ver que a única resposta é uma que não serve para nada e foi claramente publicada ali, só para que o sujeito ganhasse pontos fáceis? Sim, tem!

Tente buscar no Google sobre programação funcional e terá a mesma sensação (teoria, teoria e mais teoria).

Na equipe onde trabalho, temos um evento periódico que carinhosamente atribui o nome de DevTalk. Esse é um espaço para que os times de desenvolvimento troquem informações sobre determinados assuntos. Eu sempre sugiro que eles apontem assuntos para pesquisarmos e discutirmos nesses encontros (não é nada aleatório, a ideia é pensarmos fora da caixa, trazer ideias e avaliar se cabe no nosso dia a dia; para que aos poucos coloquemos em prática).
Na semana passada, apontaram como assunto "programação funcional"... E foi ai que resolvi escrever esse post (depois de um bom tempo sem publicar nada) ;/

Mas que diabos é isso?

Eu poderia escrever um monte de baboseiras aqui, como de onde veio, quando veio, quem teve a ideia. Mas não, isso está aos montes na internet (aqui, aqui e também aqui) e o paradigma funcional não é nada novo, só está em evidência pois agora o processamento paralelo está sendo amplamente utilizado nos sistemas web.


Então pra que esse artigo?

Como o próprio sub titulo diz. A ideia aqui é mostrar na prática como trabalhar com programação funcional em um cenário real (não esses montes de blocos de códigos espalhados nos artigos e que só trazem dúvidas ao invés de esclarecimentos).


Let it go, let it go...

Vou colocar abaixo uma classe c# com operações bancárias, da forma como fazemos no dia a dia e depois vou aplicar nessa classe os conceitos de programação funcional e explicar as vantagens de um modelo para o outro.

public class Caixa {
   private double Saldo {get; set; }

   public Caixa(double saldoInicial){
      this.Saldo = saldoInicial;
   }

   public void Depositar(double valor) {
       this.Saldo += valor;
   }


   public void Sacar(double valor) {
       this.Saldo -= valor;
   }


   public string ImprimirExtrato() {
       return "Seu saldo atual é de: " + this.Saldo;
   }
}

Usando exemplo 1):
var caixa = new Caixa(10.5);
caixa.ImprimirExtrato();
caixa.Depositar(31.4);
caixa.Sacar(5.7);
caixa.ImprimirExtrato();




public class Caixa {

   public double Depositar(this double saldoAtual, double valor) {
       return saldoAtual += valor;
   }


   public double Sacar(this double saldoAtual, double valor) {
       return saldoAtual += valor;
   }


   public string ImprimirExtrato(this double saldoAtual) {
       return "Seu saldo atual é de: " + saldoAtual;
   }
}

Usando exemplo 2):
double saldo = 10.5;
saldo.ImprimirExtrato().Depositar(31.4).Sacar(5.7).ImprimirExtrato();


Enquanto isso, no dia a dia...

Observando os exemplos acima, a grande "vantagem", é a forma como utilizamos os métodos. Parece mais "legível" e para por aí. Porém, se pensarmos em utilizar esse formato em um ambiente multi-threading, as vantagens crescem infinitamente. É possível executar operações exatamente após a operação anterior e os problemas com mutabilidade que frequentemente encontramos na programação imperativa, são minimizados ou até mesmo eliminados por completo. Mas, Porque? 

Imagine que as mesmas operações acima, são assíncronas (um app web, SPA por exemplo) e utilizam multi-processamento:

Se "caixa.Depositar(31.4)" fosse uma operação mais demorada que "caixa.ImprimirExtrato()", em um cenário multi-threading, o valor do extrato seria impresso sem considerar o depósito de 31.4, pois as operações executam paralelamente. Então, no mundo imperativo, utilizamos algo como: "caixa.Depositar(double valor, Action callback)" para resolver o problema e então teríamos algo semelhante a isso:


var caixa = new Caixa(10.5);
caixa.ImprimirExtrato(() => {
   caixa.Depositar(31.4, () => {
      caixa.Sacar(5.7, () => {
         caixa.ImprimirExtrato();
      });
   });
});

Certo, e qual o problema com isso?

Além de feio, bizarro e complexo? Observe o método "ImprimirExtrato()". Para essa implementação, eu teria de ter 2 métodos "ImprimirExtrato()" e "ImprimirExtrato(Action callback)" na minha classe Caixa. Um que recebe um parâmetro do tipo Action (para executar um callback) e outro sem esse parametro. Estaríamos aumentando a quantidade de código, os pontos de manutenção e a complexidade pelo simples fato de não utilizarmos o paradigma funcional.


Então eu devo utilizar funcional sempre que trabalho com multi-threading?

Preferencialmente, sim. Mas sempre vai depender da sua necessidade. Se seu problema exige trabalhar com Imutabilidade de estado; trabalhar com o paradigma funcional pode ser de grande ajuda. No geral, sistemas que trabalham com computação matemática, inteligência artificial e processamento paralelo são sistemas onde o paradigma funcional funcionará melhor do que a programação imperativa. Mas podemos ter sistemas tradicionais, aplicações web e em determinados pontos usarmos o paradigma funcional para resolver o problema com maior elegância, eficácia e eficiência.

Ah, se você tem pelo menos mais de 5 anos em desenvolvimento de software, certamente você já teve problemas com inclusão e processamento de arquivos em banco de dados e teve que criar controles absurdos para contornar esses problemas, não foi? Pense como sua vida teria sido mais fácil usando o paradigma funcional (não fica triste não, eu todos já passamos por isso rsrs).

Espero ter ajudado de alguma forma, até a próxima =)