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”.