CodeGym /Cursos /C# SELF /Funciones puras e inmutabilidad

Funciones puras e inmutabilidad

C# SELF
Nivel 51 , Lección 1
Disponible

1. Introducción

Función pura — es una función que con los mismos datos de entrada siempre devuelve el mismo resultado y no provoca ningún efecto secundario. Más fácil: una función pura no "estropea" nada a su alrededor y no se ve afectada desde fuera.

Una función se considera pura si:

  • Solo calcula un valor en base a los argumentos de entrada.
  • No modifica variables externas ni el estado del programa.
  • No depende de variables externas o estados que puedan cambiar.

Dos reglas de oro de la pureza

  1. Determinismo: la misma entrada — la misma salida.
  2. Sin efectos secundarios: la función no cambia nada fuera de sí misma: ni archivos, ni variables globales, ni la interfaz de usuario (hola, Console.WriteLine).

No es religión, es sentido común

Parecerá un truco académico, pero la práctica demuestra lo contrario:

  • Una función pura es predecible. Es fácil de testear: la llamas con argumentos concretos y obtendrás un resultado concreto.
  • Una función pura se puede reordenar en el código sin miedo a que algo se rompa en otro sitio.
  • En un mundo multinúcleo, una función pura es garantía de seguridad: se puede ejecutar en paralelo sin miedo a race conditions.

2. Ejemplos de funciones puras e impuras

Función pura: todo predecible

// Función pura: no toca nada fuera de sí misma
int Add(int a, int b)
{
    return a + b;
}

int Square(int x)
{
    return x * x;
}

Llama a Add(2, 3) cien veces — siempre devolverá 5. Aburrido, pero fiable.

Función impura: rompe las reglas

// Rompe la pureza: depende de estado externo (variable estática)
int counter = 0;

int Increase()
{
    counter++;
    return counter;
}

Aquí la llamada Increase() devolverá un valor distinto cada vez — o sea, ya no hay determinismo.

// Rompe la pureza: provoca un efecto externo (imprime en pantalla)
int AddAndPrint(int a, int b)
{
    int sum = a + b;
    Console.WriteLine(sum); // ¡Efecto secundario!
    return sum;
}

¿Y la aleatoriedad y el tiempo?

Cualquier función que use DateTime.Now o Random ya no es pura:

// ¡No pura!
int GetRandomNumber()
{
    return new Random().Next();
}

Tabla de diferencias

Característica Función pura Función impura
Siempre el mismo resultado para los mismos argumentos No
Efectos secundarios No
Dependencia del estado externo No

3. Inmutabilidad de datos: teoría y práctica

Inmutabilidad (immutability) — es un enfoque donde un objeto no se puede cambiar después de crearse. Si necesitas un nuevo valor — creas un objeto nuevo.

¿Por qué es importante?

  • La aplicación se vuelve resistente a cambios accidentales de datos.
  • No hay "fugas" secretas de cambios: si tienes un objeto, nadie lo modificará a escondidas.
  • La inmutabilidad es la base para muchas optimizaciones automáticas y cálculos en paralelo.

Ejemplos sencillos en C#

Tipos inmutables en .NET

Las cadenas (string) en C# son inmutables. Cada vez que haces string.Concat(s, "world"), se crea una cadena nueva.

string s = "Hello";
string t = s;
s = s + " World";
Console.WriteLine(t); // t == "Hello"

Arrays y colecciones: mutable por defecto

int[] numbers = { 1, 2, 3 };
numbers[0] = 42; // ¡El array cambió!

Inmutabilidad "a grosso modo": compilar código

En lugar de modificar un objeto/valor existente, devuelve uno nuevo:

// En lugar de esto:
void AddToList(List<int> list, int value)
{
    list.Add(value); // ¡Mutacion!
}

// Mejor así:
List<int> AddToList(List<int> list, int value)
{
    var newList = new List<int>(list) { value }; // Nueva lista
    return newList;
}

Ilustración: cambiar vs no cambiar

flowchart LR
    A[Objeto original] --"mutación"--> B[Mismo objeto, pero diferente por dentro]
    A --"inmutabilidad"--> C[Objeto nuevo]

4. ¿Para qué sirve esto en código C# real?

  • En el C# moderno, librerías como LINQ, Entity Framework y ASP.NET Core apuestan por funciones puras e inmutabilidad.
  • La inmutabilidad reduce la cantidad de bugs "mágicos" cuando alguien sobrescribe un valor importante en algún lado.
  • Las funciones puras facilitan el testing automático (unit tests), porque para probar solo necesitas comprobar entrada y salida, sin preocuparte por el mundo exterior.

Ejemplo: trabajar con cadenas

string s = "Hello";
string newS = s.Replace("H", "J"); // s sigue siendo "Hello"; newS — "Jello"

Ejemplo: LINQ y colecciones

Where, Select y otros métodos devuelven colecciones nuevas, sin tocar las antiguas.

var numbers = new List<int> { 1, 2, 3, 4 };
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();

// numbers permanece igual!

Ejemplo real: configuración con objetos inmutables

Muchas APIs .NET modernas usan objetos inmutables para configuración, por ejemplo JsonSerializerOptions:

var options = new JsonSerializerOptions
{
    WriteIndented = true
};
// Este objeto no cambia "en caliente", lo que aumenta la fiabilidad.

5. Errores y trampas típicas

El terreno resbaladizo aparece cuando "accidentalmente" mutas datos en código que deberías considerar "puro".

Suele pasar con colecciones: quieres filtrar una lista y de paso modificas la original.

O te olvidas de que un método como List<T>.Add modifica el objeto en sitio.

Ejemplo traicionero:

List<int> DoubleTheNumbers(List<int> xs)
{
    // ¡Error! Mutamos la lista original, devolvemos el mismo objeto.
    foreach (var i in xs)
        xs.Add(i * 2);
    return xs;
}

Este código incluso lanzará una excepción en tiempo de ejecución (InvalidOperationException), porque estamos cambiando la colección mientras la iteramos — problema clásico de la mutación.

Correcto:

List<int> DoubleTheNumbers(List<int> xs)
{
    var newList = new List<int>(xs.Select(x => x * 2));
    return newList;
}
2
Tarea
C# SELF, nivel 51, lección 1
Bloqueada
Lista inmutable de números
Lista inmutable de números
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION