C# tem ganho, cada vez mais, “aromas funcionais” em suas últimas versões. O que, de forma geral, é muito bom por permitir a escrita de códigos menores e mais expressivos.
Começando pelas expressões lambda, seguindo com expression bodied members, depois pelo suporte avançado a tuplas e, agora, pela disseminação de pattern matching, práticas e padrões de programação funcional parecem estar “emergindo” em toda a linguagem.
using static System.Console; static class Program { static void Main() { var p1 = new Point3F(3, 4, 5); float x, y; (x, y, _) = p1; WriteLine($"Value of X: {x}"); WriteLine($"Value of Y: {y}"); } } public readonly struct Point3F { public Point3F(float x, float y, float z) => (X, Y, Z) = (x, y, z); public float X { get; } public float Y { get; } public float Z { get; } public void Deconstruct(out float x, out float y, out float z) => (x, y, z) = (X, Y, Z); }
No código acima, por exemplo, saltam aos olhos a possibilidade de declarar o uso de classes estáticas dando a elas a característica de “envólucros” para funções. Também chama a atenção o uso de tuplas, tanto na construção quanto na “desconstrução” de objetos, diminuindo a demanda por tipos específicos. Por fim, pattern matching, contando, inclusive, com o operador de descarte “_
” parece estar redefinindo a forma como fazemos atribuições mais complexas.
No C# 8, aliás, o suporte a pattern matching aumentou muito. A novidade são as expressões switch
que podem ser utilizadas para fazer mapping de dados com códigos extremamente econômicos.
using static System.Console; static class Program { static void Main() { var p1 = new Point2F(3, 4); var q = p1.GetQuadrant(); WriteLine($"Quadrant of {p1} is {q}. {q.GetQuadrantDescription()}"); } static Quadrant GetQuadrant(this Point2F point) => point switch { (0, 0) => Quadrant.Origin, var (x, y) when x > 0 && y > 0 => Quadrant.One, var (x, y) when x < 0 && y > 0 => Quadrant.Two, var (x, y) when x < 0 && y < 0 => Quadrant.Three, var (x, y) when x > 0 && y < 0 => Quadrant.Four, var (_, _) => Quadrant.OnBorder }; static string GetQuadrantDescription(this Quadrant quadrant) => quadrant switch { Quadrant.Origin => "Values of X and Y are 0.", Quadrant.One => "Values of X and Y are positives.", Quadrant.Two => "X is negative and Y is positive.", Quadrant.Three => "Values of X and Y are negatives.", Quadrant.Four => "X is positive and Y is negative", Quadrant.OnBorder => "X or Y is zero, but not both", Quadrant.Unknown => "Who knows?!" }; } public enum Quadrant { Unknown, Origin, One, Two, Three, Four, OnBorder } public readonly struct Point2F { public Point2F(float x, float y) => (X, Y) = (x, y); public float X { get; } public float Y { get; } public void Deconstruct(out float x, out float y) => (x, y) = (X, Y); public override string ToString() => $"({X}; {Y})"; }
Essas expressões, aliás, suportam inclusive a inspeção de valores de propriedades de objetos.
public static float GetTaxRateByCity(this Store store) => store switch { { City: "San Francisco, CA" } => 8.5F, { City: "Los Angeles, CA" } => 9.4F, { City: "Seattle, WA" } => 10.1F, _ => throw new Exception("Unrecognized city") };
A Microsoft disponibilizou um excelente tutorial com ideias e aplicações avançadas para alguns dos conceitos que exploramos aqui.
Se as linguagens funcionais ainda não estão ganhando a atenção que merecem, parece que fragmentos do paradigma estão, aos poucos, “invadindo” linguagens mainstream como C#. O desafio, acreditamos, é “acostumar os olhos” dos desenvolvedores mais experientes a essa forma nova de “pensar código”.