/ csharp-programming

Functional Programming with C#

Yeah, let's talk about a important feature of modern programming! Sem mais coisinhas em inglês pra parecer cult ou fazer aquelas click-baits marotas pra suprir a necessidade de atenção, bora falar sobre programação realmente funcional com C#, vale lembrar que o conteúdo apresentado aqui é original, então se gostar, colabore dando aquele comentário desconstrutor ou construtor sobre o que você vai ler aqui, e compartilhe com seus outros amigos sem vida.

_you.Deps.ToList().ForEach(x => x.GetReady())

Muita coisa aqui, vai ser feita com REPL, aquela interface entre a linguagem e você, pra invocar ela é muito simples, mas isso varia de qual implementação da linguagem, e do próprio framework que a ambienta, por isso, para evitar esse tipo de problema, vamos focar somente em uma implementação genérica, e válida para quase todas as arquiteturas e sistemas operacionais: DotNet Core versão 2.

Como nem tudo na vida é fácil, a Microsoft decidiu colocar na comunidade, e a interface foi removida na nova major, mas comunidade é comunidade senhores, então vamos partir para a instalação do que vamos precisar aqui, antes de você prosseguir, certifique-se que de fato tem o dotnet em seu source, para evitar tretas com o que vamos fazer à partir de agora:

curl -s https://raw.githubusercontent.com/filipw/dotnet-script/master/install/install.sh | sudo bash

Se você estiver me vendo do Windows... Eu não sei o que te falar, apenas perdão por não saber se temos um binário prai diferente do já contido como "C# Interactive" do Visual Studio.

Aqui vamos instalar um pacote mágico, que nos permite a mesma interface (aquela que temos no Visual Studio 2013+), em nosso ambiente de interface de linha de comando, se tudo ocorrer como deve, seu terminal deve ter uma stdout informando que o pacote for instalado, agora, se teve problemas, deixa o log pra nóis te ajudar ai nos comentários, ou aqui no grupo do Facebook.

Uma informação importante antes de você chegar no grupo, leia as regras, e jamais fique feliz lá!

Vamos testar agora, se de fato temos uma REPL com os recursos da linguagem, outra informação importante pra você, tudo aqui é C# 7.1+, alguns códigos deveram rodar em versões mais arcaicas, mas de forma alguma deixo uma recomendação para você utiliza-los nestas, por tanto, não altere a versão padrão da linguagem no pacote dotnet-script ou no próprio core, voltando ao teste:

Agora temos o que precisamos pra começar a estudar de maneira um pouco mais interativa o que de fato é programação funcional (não é pura tio, C# não é Haskell).

Uma nota importante, o pacote que instalamos, não é de fato algo ideal para fazer isso, mas como disse logo à cima, não temos uma REPL oficial por parte das releases Microsoftanas, ao menos não nos *nix da vida, por esse motivo, pode existir algum tipo de problema ou resultado não esperado da execução disso.

O correto seria consumirmos o recurso que já está dentro do framework dotnet script, mas vem cá, a estrutura dele para rodar nossos scripts de C#, sim scripts, não é nada amigável, da uma olhada no repo disso tudo, você vai entender melhor o que estou falando aqui.

Como diabos posso definir uma linguagem funcional?

Olha, essa pergunta motiva muita treta em vários grupos de computação ao redor do mundo, mas já passou da hora de pensar que programação funcional é algo que só rola dentro dos laboratórios de estudo de arquitetura e design de linguagens, e já está na hora de começar a implementar código que presta nas suas soluções (não sou odiador imperativo, mas faz essa approach em coisas paralelas hehe), aqui no C#, e na vasta gama de outras linguagens, podemos definir assim:

  • Funções são tudo, literalmente (functions as first-class values);
  • (Definitivamente nosso idioma não tem a melhor gramática para tratarmos expressões da nossa área!)
  • Mutabilidade? Hein?
  • Consistência e segurança above all! (não significa que não se pode criar valores mutáveis, apenas que é uma [email protected]#% má prática do paradigma e filosofia da parada...)

Funções e mais funções...

Em uma linguagem onde temos funções (perceba que não disse "apenas funções"[...]), você pode utilizar qualquer entrada ou saída dessas, para interagir com outras, então ao invés de ter aquele nível de abstração arcaico, sempre retornando um novo valor de um objeto, ou tentando se livrar disso com out, você simplesmente passa uma função como parâmetro de outra.

Vamos entender isso melhor na prática, vamos partir agora para o REPL, criando um exemplo simples do que estamos fazendo aqui:

Se você sempre utilizou a linguagem de uma maneira bem imperativa, pode ter achado isso como o demônio jeová da programação, mas sinceramente, imagine implementar esse código simples e enchuto (além de conciso, claro) de uma maneira totalmente imperativa, criando uma função para triplicar, um laço de repetição explicito para iterar, e por ai vai...

Isso meus amigos, é o nível de abstração que quero ver aqui! Continua no mundo dos Quem? então relaxa, vou te explicar expressão a expressão o que tá rolando ali, bom, primeiro, definimos nossa função triplicar, que utilizando aquela expressão lambda, podemos deixar no ar, vulgo implícito, o tipo da estrutura de dados de 'x, tendo em vista que na assinatura da função temos a definição já bem declarada.

Depois, consumimos uma função da interface IEnumerable<'T> (tá vendo como alguns conceitos de OO casam bem com FP?), Range(), que nos gera uma coleção de valores à partir de um ponto de partida, e um ponto de chegada, se você curtir um Python da vida, deve ter sentido uma certa familiaridade com a criação de estruturas de repetição de forma por extensa.

Depois, continuamos a consumir os métodos da interface, porém dessa vez, passamos direto a função triplicar, fazendo uma nova referência, se curtir um JavaScript, lembra que lá você não precisa criar uma extrutura extensa para funções de mesma assinatura? Aqui vale quase o mesmo, nesse contexo, é exatamente isso, tudo implicito, volto a repetir abstração senhores! Alto nível!

Escapando da variação do sistema de tipos de variáveis

Agora vamos pro segundo item da listinha, não é bom termos essa mutábilidade, ninguém aqui programa com letras do Raúl (ao menos ainda não, e espero que assim continue), para ser uma metamorfose ambulante, por tanto, vamos compreender o motivo disso ser uma pessima ideia, pior mesmo que votar no Bolsonaro (ou no Lula, ou no diabo que o carregue!).

O conceito é simples, quando temos a definição de escopo completa de um objeto, seu tipo é imutável, ou seja, não muda! E também, as variáveis nunca devem ser reatribuídas, vamos entender isso de maneira prática:

int[] numeros = {1, 2, 3, 4, 5};
numeros[0] = 666;

Parece um código normal né? But not! Isso é um erro, assim como a computação, isso não deve ocorrer, é feio, é inseguro, gera problema... Especialmente se tiver mais de uma thread consumido essa parada (não algo old school like arrays, mas você entendeu!)

Vamo fazer uma parada que muito professor de faculdade pede pra você fazer em (perda de tempo) pseudocódigo, aquele tal do pt-br mais imperativo que o normal, vamos checar se o número X é ímpar, de forma funcional, e respeitando a imutabilidade:

Novamente algo extremamente simples e imutável, preservamos tudo, nada de alteração, e novamente com um código muito conciso. Nesse exemplo realizamos duas ordenações na estrutura, uma clássica, de ordenação em ordem crescente dos inteiros, e a segunda identificando os números ímpares de uma coleção de valores inteiros.

Aqui temos aquele jogo entre OO e FP novamente, quem entra em ação nesse caso, é a interface IEnumerables com sua poderosa recursão com yield, que por mais curioso que possa ser, temos uma limitação do seu emprego na atual sintaxe do C#, que também é uma feature que pesa bastante em outras linguagens, que justamente a capacidade de consumir essa palavra chave em expressões anônimas, isso requer um alto custo de tempo de compilação...

Então, C# é pesadão por ter tantas features

Claro! Mas a parte boa de rodar em um ambiente similar ao Java, é que você tem uma caminha te esperando, e também essas paradas ficam mais pro tempo de compilação que execução, isso não significa que temos uma linguagem consistente e precisa, olha só, quando implementamos um objeto do tipo List<'T>, esse por si só já é uma tremenda falha de design, não sabe o que seguir tadinha, suporte pra OO e FP, tudo ao mesmo tempo, o resultado, bom, aquele tempo de compilação assombroso junto com coisas nonsense como a função Sort() que faz a ordenação dos numéricos em ordem crescente.

Não diretamente claro, tem um behavior por trás disso, nativamente não temos essa função dentro da coleção de métodos do objeto, mas temos as extensões em LINQ, as mesmas que usamos para fazer as paradas funcionais, entram em contradição com muitos objetos das APIs do DotNet, mas isso não é algo de se apavorar, apenas algo para se monitorar (e evitar)!

Conclusão

Não sou uma IA que consegue ter um fluxo de pensamento conciso, infelizmente um TDA me assola, então minhas capacidades dissertativas vão em bora com outro interesse, que nesse caso, eu me orgulhando ou não, é jogar meu bom e velho Minecraft, fiquem tranquilos, essa série vai se estender por uns bons meses aqui no blog, e volto a repetir: Curtiu? Compartilha essa coisa, odiou? Expressa seu ódio para eu (ignorar) tentar melhorar, e no caso de dúvidas, os comentários estão a sua disposição!