Su C # ya es "funcional", solo déjelo.

Hola Habr! Te presento la traducción del artículo original "Tu C # ya es funcional, pero solo si lo dejas" por Igal Tabachnik.

Hace unos días, tuiteé un fragmento de código C # que implementa FizzBuzz , usando algunas de las nuevas características en C # 8.0 . El tweet "se volvió viral", varias personas admiraron su concisión y funcionalidad, mientras que otros me preguntaron por qué no lo escribí en F #.

Han pasado más de 4 años desde la última vez que escribí en C #, y el hecho de que generalmente uso programación funcional ha influido claramente en la forma en que escribo código hoy. El fragmento que escribí parece muy ordenado y natural, pero algunas personas han expresado su preocupación de que no se parece al código C #.
"Se ve demasiado funcional". Me escribieron
Dependiendo de a quién le pregunte, "programación funcional" significa cosas diferentes para diferentes personas. Pero en lugar de discutir la semántica, me gustaría ofrecer una explicación de por qué esta implementación de FizzBuzz parece funcional.

Primero, veamos qué hace este código:

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); 
}

Aquí creamos un método local, representado por una expresión lambda, cuyo resultado se calcula utilizando una tupla.

La novedad aquí es el uso de una tupla (par) para trabajar con el resultado de calcular dos expresiones juntas (x% 3 = = 0 yx% 5 = = 0). Esto le permite utilizar la coincidencia de patrones para determinar el resultado final. Si ninguna de las opciones coincide, por defecto se devolverá una representación de cadena del número.

Sin embargo, ninguna de las muchas "características" "funcionales" utilizadas en este código (incluidos los bucles foreach estilo LINQ) hacen que este código sea funcional en sí mismo. Lo que lo hace funcional es el hecho de que, con la excepción de enviar el resultado a la consola, todos los métodos utilizados en este programa son expresiones.

En pocas palabras, la expresión es una pregunta que siempre tiene una respuesta. En términos de programación, una expresión es una combinación de constantes, variables, operadores y funciones calculadas por el tiempo de ejecución para calcular ("devolver") un valor. Para ilustrar la diferencia con las declaraciones, escribamos una versión más familiar de FizzBuzz para programadores de 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, esto está lejos de ser un código "limpio", y se puede mejorar, pero uno no puede dejar de estar de acuerdo en que es un código C # ordinario. Tras una inspección más cercana del método FizzBuzz, incluso teniendo en cuenta su simplicidad, muestra claramente varios problemas de diseño.

En primer lugar, este programa viola el primero de los principios de SOLID: el principio de responsabilidad única. Mezcla la lógica de calcular el valor de salida en función del número y enviar este valor a la consola. Como resultado, viola el principio de inversión de dependencia (el último de SOLID), conectándose estrechamente con la salida del resultado a la consola. Finalmente, dicha implementación del programa dificulta la reutilización y el código sandbox. Por supuesto, para un programa tan simple como este, no tiene sentido entrar en las complejidades del diseño, de lo contrario, algo como esto puede resultar.

Todos los problemas anteriores se pueden resolver separando la recepción de valores y la salida a la consola. Incluso sin usar construcciones de lenguaje sofisticadas, simplemente devolver el valor resultante al código de llamada nos libera de la responsabilidad de usar este 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();
}

Por supuesto, estos no son cambios radicales, pero esto ya es suficiente:

  1. El método FizzBuzz es ahora una expresión que toma un valor numérico y devuelve una cadena.
  2. No tiene otras responsabilidades y efectos ocultos, lo que lo convierte en una función pura.
  3. Se puede usar y probar de forma independiente, sin dependencias y configuraciones adicionales.
  4. El código que llama a esta función es libre de hacer cualquier cosa con el resultado; ahora no es nuestra responsabilidad.

Y esta es la esencia de la programación funcional: el programa consta de expresiones que dan como resultado valores, y estos valores se devuelven al código de llamada. Estas expresiones, por regla general, son completamente independientes y el resultado de su llamada depende solo de los datos de entrada. En la parte superior, en el punto de entrada (o a veces llamado el "fin del mundo"), los valores devueltos por las funciones recopilan e interactúan con el resto del mundo. En la jerga orientada a objetos, esto a veces se llama "arquitectura de cebolla" (o "puertos y adaptadores"), un núcleo limpio que consiste en lógica empresarial y una capa externa imperativa responsable de interactuar con el mundo exterior.

El uso de expresiones en lugar de declaraciones se usa en cierta medida en todos los lenguajes de programación. C # evolucionó con el tiempo para introducir funciones que facilitan el trabajo con expresiones: LINQ, métodos basados ​​en expresiones, coincidencia de patrones y más. Estas "características" a menudo se denominan "funcionales" porque existen, en lenguajes como F # o Haskell, en los que la existencia de cualquier otra cosa que no sean expresiones es casi imposible.

De hecho, este estilo de programación ahora es alentado por el equipo de C #. En un discurso reciente en NDC London, Bill Wagner insta a los desarrolladores a cambiar sus hábitos (imperativos) y adoptar métodos modernos:


C # (y otros lenguajes imperativos como Java) pueden usarse funcionalmente, pero requiere mucho esfuerzo. Estos lenguajes hacen que el estilo funcional sea una excepción, no la norma. Te animo a aprender lenguajes de programación funcionales para convertirte en un especialista de primera clase.

All Articles