csharp-language-ext

Programación funcional en C# con la biblioteca LanguageExt

LanguageExt es una biblioteca de extensiones de lenguaje funcional para C#. Fue creado por Paul Louth y tiene como objetivo añadir facilitar el trabajo con características funcionales en C#, como la programación funcional, la inmutabilidad.

LanguageExt se basa en las mejores prácticas de la programación funcional y proporciona una amplia gama de estructuras de datos y funciones para ayudar a los desarrolladores a escribir código más limpio, más conciso y más seguro.

LanguageExt proporciona una amplia gama de características funcionales para C#, incluyendo:

  • Estructuras de datos inmutables, como listas y mapas.
  • Funciones de orden superior, como map y fold.
  • Programación asincrónica con soporte para cancelación y excepciones.
  • Tipos de opciones y resultados para evitar excepciones en tiempo de ejecución.
  • Un sistema de concurrencia basado en agentes y canales.

Esta biblioteca no es pequeña. Más bien todo lo contrario, es una biblioteca enorme, que proporciona una gran cantidad de herramientas que suponen un cambio significativo en la forma de utilizar C#.

Sin embargo, lógicamente no es necesario utilizar todas las herramientas todo el tiempo. LanguageExt se presenta como un recopilatorio de utilidades que resulta beneficioso usar en algunas ocasiones.

Por otro lado, muchas de las características que ofrece LanguageExt se han ido incorporando progresivamente en las versiones de .NET., que sigue su propia evolución hacia paradigmas más funcionales.

Personalmente, yo veo esta biblioteca también como un “campo de pruebas” de nuevas posibilidades y tendencias en el desarrollo de software, además de proporcionar ciertas utilidades prácticas que sí tienen un uso inmediato.

Cómo usar Language-ext

Podemos añadir la biblioteca a un proyecto de .NET fácilmente, a través del paquete Nuget correspondiente.

Install-Package LanguageExt.Core

Aquí tenéis algunos de cómo utilizar Language-ext extraídos de la documentación de la librería

Null

Podemos gestionar los objetos Null de forma sencilla gracias a los Option<T>

var optional = Some(123);

int x = optional
        .Some( v  => v * 2 )
        .None( () => 0 );

int x = optional.IfNone(10);  

Matching

Uso de Option<T> junto con matching.

Option<int> two = Some(2);
Option<int> four = Some(4);
Option<int> six = Some(6);
Option<int> none = None;

// This expression succeeds because all items to the right of 'in' are Some of int
// and therefore it lands in the Some lambda.
int r = match( from x in two
                from y in four
                from z in six
                select x + y + z,
                Some: v => v * 2,
                None: () => 0 );     // r == 24

Parámetros out

Evitar emplear los parámetros Out mediante los Option<T>

// Attempts to parse the value, uses 0 if it can't
int res = parseInt("123").IfNone(0);

// Attempts to parse the value, doubles it if can, returns 0 otherwise
int res = parseInt("123").Match(
                Some: x => x * 2,
                None: () => 0
            );

Unit

Tipo de datos Unit para evitar usar void para indicar ausencia de tipo.

public static Func<string, Unit> GetConsoleWrite() => fun<string>(Console.Write);
public static Func<string, Task<Unit>> GetConsoleWriteAsync() => (string v) => Console.Out.WriteAsync(v).ToUnit();

public static Unit WriteToConsole(string v) => fun(() => Console.Write(v))();
public static Task<Unit> WriteToConsoleAsync(string v) => Console.Out.WriteAsync(v).ToUnit(); 

Colecciones

También ofrece colecciones inmutables, como listas y mapas

var test = List(1, 2, 3, 4, 5);
var list = Range(500,1000);

var res = List(1, 2, 3, 4, 5)
            .Map(x => x * 10)
            .Filter(x => x > 20)
            .Fold(0, (x, s) => s + x);

O, por ejemplo, diccionarios inmutables

var dict = Map<string,int>();

var people = Map((1, "Rod"),
                (2, "Jane"),
                (3, "Freddy"));

Option<string> result = find(people, 1);

// Find the item, do some processing on it and return.
var res = match( find(people, 100),
                    Some: v  => "Hello " + v,
                    None: () => "failed" );

Funciones

Definición de funciones bajo un paradigma más funcional

public int Sum(IEnumerable<int> list) => match( list,
            ()      => 0,
            (x, xs) => x + Sum(xs)
);

Estos son solo ejemplos de algunas de las funcionalidades. Pero la biblioteca ofrece muchos, muchos (¡muchos!) Echarle un ojo a la documentación porque es realmente interesante, aunque solo sea como fuente de inspiración e ideas.