Seu C # já está "funcional", apenas deixe.

Olá Habr! Apresento a você a tradução do artigo original "Seu C # já está funcional, mas somente se você permitir", de Igal Tabachnik.

Alguns dias atrás, eu twittou um C # trecho de código que implementa FizzBuzz , usando alguns dos novos recursos do C # 8.0 . O tweet “se tornou viral”, várias pessoas admiraram sua concisão e funcionalidade, enquanto outras me perguntaram por que eu não escrevi em F #?

Mais de quatro anos se passaram desde a última vez que escrevi em C #, e o fato de eu geralmente usar programação funcional influenciou claramente a maneira como escrevo código hoje. O trecho que escrevi parece muito elegante e natural, mas algumas pessoas expressaram preocupações de que ele não se parece com o código C #.
"Parece muito funcional." Eles escreveram para mim.
Dependendo de quem você pergunta, "programação funcional" significa coisas diferentes para pessoas diferentes. Mas, em vez de discutir a semântica, gostaria de oferecer uma explicação de por que essa implementação do FizzBuzz parece funcional.

Primeiro, vamos ver o que esse código faz:

public static void Main(string[] args)
{
      string FizzBuzz(int x) => 
            (x % 3 == 0, x % 5 == 0) switch
            {  
                  (true, true) => "FizzBuzz", 
                  (true, _) => "Fizz", 
                  (_, true) => "Buzz", 
                  _ => x.ToString()
            };
    
      Enumerable.Range(1, 100 ) 
            .Select(FizzBuzz).ToList() 
            .ForEach(Console.WriteLine); 
}

Aqui, criamos um método local, representado por uma expressão lambda, cujo resultado é calculado usando uma tupla.

A novidade aqui é o uso de uma tupla (par) para trabalhar com o resultado do cálculo de duas expressões juntas (x% 3 = = 0 ex x% 5 = = 0). Isso permite que você use a correspondência de padrões para determinar o resultado final. Se nenhuma das opções corresponder, então, por padrão, uma representação de string do número será retornada.

No entanto, nenhum dos muitos "recursos" funcionais "usados ​​neste pedaço de código (incluindo loops foreach no estilo LINQ) tornam esse código funcional por si só. O que o torna funcional é o fato de que, com exceção da saída do resultado no console, todos os métodos usados ​​neste programa são expressões.

Simplificando, expressão é uma pergunta que sempre tem uma resposta. Em termos de programação, uma expressão é uma combinação de constantes, variáveis, operadores e funções calculadas pelo tempo de execução para calcular ("retornar") um valor. Para ilustrar a diferença com as instruções, vamos escrever uma versão mais familiar do FizzBuzz para programadores em C #:

public static void Main(string[] args) 
{
      foreach( int x in Enumerable.Range(1, 100)) 
      {
             FizzBuzz(x);
      }
} 

public static void FizzBuzz( int x) 
{
      if (x % 3 == 0  && x % 5 == 0) 
             Console.WriteLine("FizzBuzz"); 
      else if (x % 3 == 0 ) 
             Console.WriteLine("Fizz"); 
      else if (x % 5 == 0 ) 
             Console.WriteLine("Buzz"); 
      else
             Console.WriteLine(x);
}

Naturalmente, isso está longe de ser um código "limpo" e pode ser aprimorado, mas não se pode deixar de concordar que esse é um código C # comum. Após uma inspeção mais detalhada do método FizzBuzz, mesmo considerando sua simplicidade, ele mostra claramente vários problemas de design.

Antes de tudo, este programa viola o primeiro dos princípios do SOLID - o princípio da responsabilidade única. Ele mescla a lógica do cálculo do valor de saída com base no número e na saída desse valor para o console. Como resultado, viola o princípio da inversão de dependência (o último do SOLID), conectando-se firmemente à saída do resultado no console. Por fim, essa implementação do programa dificulta a reutilização e o código da área restrita. Obviamente, para um programa tão simples como esse, não faz sentido entrar nos meandros do design, caso contrário, algo como isso pode resultar.

Todos os problemas acima podem ser resolvidos separando o recebimento do valor e a saída no console. Mesmo sem usar construções de linguagem sofisticada, simplesmente retornar o valor resultante ao código de chamada nos isenta da responsabilidade de usar esse valor.

public static string FizzBuzz(int x)
{
      if (x % 3 == 0 && x % 5 == 0)
            return "FizzBuzz";
      else if (x % 3 == 0)
            return "Fizz";
      else if (x % 5 == 0)
            return "Buzz";
      else
            return x.ToString();
}

Obviamente, essas não são mudanças radicais, mas isso já é suficiente:

  1. O método FizzBuzz agora é uma expressão que pega um valor numérico e retorna uma string.
  2. Não possui outras responsabilidades e efeitos ocultos, o que a transforma em pura função.
  3. Pode ser usado e testado independentemente, sem dependências e configurações adicionais.
  4. O código que chama essa função é livre para fazer qualquer coisa com o resultado - agora isso não é de nossa responsabilidade.

E essa é a essência da programação funcional - o programa consiste em expressões que resultam em quaisquer valores e esses valores são retornados ao código de chamada. Essas expressões, como regra, são completamente independentes e o resultado de sua chamada depende apenas dos dados de entrada. No topo, no ponto de entrada (ou às vezes chamado de "fim do mundo"), os valores retornados pelas funções coletam e interagem com o resto do mundo. No jargão orientado a objetos, isso às vezes é chamado de "arquitetura de cebola" (ou "portas e adaptadores") - um núcleo limpo que consiste em lógica de negócios e uma camada externa imperativa responsável por interagir com o mundo externo.

O uso de expressões em vez de instruções é usado até certo ponto por todas as linguagens de programação. O C # evoluiu ao longo do tempo para introduzir funções que facilitam o trabalho com expressões: LINQ, métodos baseados em expressão, correspondência de padrões e muito mais. Esses "recursos" costumam ser chamados de "funcionais" porque existem - em linguagens como F # ou Haskell, nas quais a existência de algo que não seja expressões é quase impossível.

De fato, esse estilo de programação agora é incentivado pela equipe de C #. Em um discurso recente na NDC London, Bill Wagner pede aos desenvolvedores que mudem seus hábitos (imperativos) e adotem métodos modernos:


C # (e outras linguagens imperativas, como Java) pode ser usado funcionalmente, mas requer muito esforço. Essas linguagens tornam o estilo funcional uma exceção, não a norma. Convido você a aprender linguagens de programação funcionais para se tornar um especialista de primeira classe.

All Articles