Introdução
A programação funcional é um paradigrama que o grande foco como o nome diz, são as funções. O aspecto mais importante da PF (programação funcional, não polícia federal 😅) é a imutabilidade. Grande parte dos nossos bugs, vem de estado mutável, ou seja, executar a mesma função várias vezes com os mesmos parâmetros pode gerar resultados diferentes, tornando o comportamento do programa imprevisível.
Aprendi estes conceitos por meio do livro "Learning Functional Programming: Managing Code Complexity by Thinking Functionally", escrito por Jack Widman e publicado pela editora O'Reilly.

Capa do livro Learning Functional Programming: Managing Code Complexity by Thinking Functionally.
Exemplo de problema com estado mutável
Um grande exemplo disso, é como manipulamos erros normalmente, veja o exemplo abaixo em C#:
public void EnviarPedido(Pedido pedido)
{
if(string.IsNullOrEmpty(pedido.Endereco))
AddError("Endereço não pode ser vazio.");
/// Continuação do método...
}
Se executarmos este método duas vezes, o método EnviarPedido
pode gerar efeitos colaterais inesperados. Por exemplo, se o método AddError
modifica algum estado interno compartilhado do serviço, ou se ele mantém uma lista global de erros, executar EnviarPedido
com diferentes pedidos pode “vazar” informações de um pedido para outro. Isso é um exemplo clássico de problema causado por estado mutável.
Pedido pedido1 = new Pedido { Endereco = "" };
Pedido pedido2 = new Pedido { Endereco = "Lo Barnechea, 42" };
PedidoService service = new PedidoService();
service.EnviarPedido(pedido1);
service.EnviarPedido(pedido2);
Outro ponto importante, é que nada promete que o desenvolvedor vai tratar erros de validação, pois o método por si só, não retornou nada.
Result Pattern
O Result Pattern é uma forma de tornar explícito o resultado de uma operação, indicando se ela foi bem-sucedida ou não e carregando informações adicionais, como mensagens de erro. Isso elimina efeitos colaterais e força o consumidor a lidar com erros de forma clara.
Existem diversas bibliotecas que oferecem este pattern, mas para evitar dependências externas, prefiro implementar na mão.
public class Result
{
public bool IsSuccess { get; }
public string Error { get; }
public T Value { get; }
private Result(bool isSuccess, T value, string error)
{
IsSuccess = isSuccess;
Value = value;
Error = error;
}
public static Result Ok(T value) => new Result(true, value, null);
public static Result Fail(string error) => new Result(false, default, error);
}
[MustUseReturnValue]
public Result ValidarPedido(Pedido pedido)
{
if (string.IsNullOrEmpty(pedido.Endereco))
return Result.Fail("Endereço não pode ser vazio.");
return Result.Ok(pedido);
}
O Result Pattern força a verificação de erros e evita mutabilidade nos métodos. O atributo [MustUseReturnValue]
do package JetBrains.Annotations
ajuda a garantir que o resultado não seja ignorado pelo desenvolvedor que está usando o método.
Para integrar suas validações com o ASP.NET Core, na sua camada da Web, você pode criar um método de extensão e utilizar em suas Controllers:
public static class ModelStateExtensions
{
public static void AddResultErrors(this ModelStateDictionary modelState, Result result, string key = null)
{
if (!result.IsSuccess)
{
modelState.AddModelError(key ?? string.Empty, result.Error);
}
}
}
[HttpPost]
public IActionResult CriarPedido(Pedido pedido)
{
var resultado = ValidarPedido(pedido);
ModelState.AddResultErrors(resultado, nameof(pedido));
if (!ModelState.IsValid)
return BadRequest(ModelState);
EnviarPedido(pedido);
return Ok();
}
Imutabilidade com records
Em C#, records são tipos de referência imutáveis por padrão, perfeitos para programação funcional. Eles permitem criar objetos imutáveis após a criação, evitando efeitos colaterais. Outra grande vantagem é que eles já vêm de fábrica com um operador with
para criar cópias do objeto. Também gosto de enxergar eles como o híbrido entre uma classe e um struct, pois funcionam por referência, mas ao serem comparados com o operador ==
o que vale é o valor das props e não a referência dele.
public record Pedido(string Endereco, int Quantidade);
Um bom exemplo é gerar a cópia de um pedido, uma cópia com o operador with
:
var pedido = new Pedido("Rua A, 10", 2);
var copiaDoPedido = pedido with { Endereco = "Rua B, 20" };
Com isso, cada instância permanece consistente, evitando os famigerados erros de alterar o valor de algo por referência.
F#
Se você gostou do que leu até aqui, uma maneira de colocar a PF em prática é com o F#, que é uma linguagem funcional do ecossistema .NET, totalmente compatível com códigos existentes em C#, mas com foco em imutabilidade, expressões e funções puras. Ela encoraja escrever código declarativo, onde o estado mutável é evitado sempre que possível, tornando programas mais previsíveis e seguros.
let endereco = "Rua A, 10"
// endereco <- "Rua B, 69" // Isso gera erro de compilação
// Pra termos mutabilidade, precisamos utilizar a palavra mutable:
let mutable contador = 0
contador <- contador + 1
O que mais gosto do F# são as type unions, até o momento que este artigo foi escrito, o C# ainda não suporta type unions, que são uma maneira de implementar o result pattern, como no exemplo abaixo em F#:
type PedidoResult =
| Success of Pedido
| Error of string
let validarPedido pedido =
if pedido.Endereco = "" then
Error "Endereço não pode ser vazio"
else
Success pedido
Conclusão
Lembre-se que a programação funcional é apenas um dos paradigmas existentes por aí. Ela não é a solução de todos os problemas do mundo e você pode acabar confundindo seus colegas colocando este paradigma em algum lugar que claramente a orientação a objetos seria muito melhor, como sempre defendo, temos que saber um pouco de tudo e escolher a ferramenta certa para cada contexto. Os principais cenários pra PF são trabalhar com multithread, transformação de dados ou escrever código testável. Obviamente, o gosto do desenvolvedor também afeta muito a escolha do paradigma, eu particularmente prefiro muito mais de trabalhar com retornos em métodos do que com estado, a grande vantagem do C# e do F# é que são linguagens multiparadigma.