Translate

jueves, 8 de enero de 2026

Métodos Monádicos en Rust — Parte 2


Rust nos da Option y Result, pero podemos definir nuestra propia estructura monádica para aprender cómo se implementan los métodos map, and_then, y or_else.

Lo haremos con un tipo Maybe<T>, que representa un valor que puede o no existir (una versión casera de Option<T>).


#[derive(Debug, Clone, PartialEq)]

enum Maybe<T> {

    Just(T),

    Nothing,

}

  • Just(T) representa un valor presente.
  • Nothing representa ausencia de valor.


Implementemos los métodos monádicos


impl<T> Maybe<T> {

    /// Aplica una función al valor si existe

    fn map<U, F>(self, f: F) -> Maybe<U>

    where

        F: FnOnce(T) -> U,

    {

        match self {

            Maybe::Just(v) => Maybe::Just(f(v)),

            Maybe::Nothing => Maybe::Nothing,

        }

    }


    /// Encadena funciones que devuelven Maybe

    fn and_then<U, F>(self, f: F) -> Maybe<U>

    where

        F: FnOnce(T) -> Maybe<U>,

    {

        match self {

            Maybe::Just(v) => f(v),

            Maybe::Nothing => Maybe::Nothing,

        }

    }


    /// Valor alternativo si es Nothing

    fn or_else<F>(self, f: F) -> Maybe<T>

    where

        F: FnOnce() -> Maybe<T>,

    {

        match self {

            Maybe::Just(_) => self,

            Maybe::Nothing => f(),

        }

    }

}


Veamos como podemos usarlo: 


fn half(n: i32) -> Maybe<i32> {

    if n % 2 == 0 {

        Maybe::Just(n / 2)

    } else {

        Maybe::Nothing

    }

}


fn add_ten(n: i32) -> Maybe<i32> {

    Maybe::Just(n + 10)

}


fn main() {

    let result = Maybe::Just(8)

        .and_then(half)

        .and_then(add_ten)

        .or_else(|| Maybe::Just(0));


    println!("{:?}", result); // Just(14)

}


  • Si algún paso devuelve Nothing, el resto se saltea.
  • No hay if, ni unwrap, ni panic!.


También podés transformar el tipo interno con map:


let maybe_str = Maybe::Just(21)

    .map(|x| format!("Value: {}", x))

    .or_else(|| Maybe::Just("Fallback".to_string()));


println!("{:?}", maybe_str); // Just("Value: 21")


Podés generalizar este patrón para representar éxito o error, al estilo Result:


#[derive(Debug)]

enum Either<L, R> {

    Left(L),

    Right(R),

}


impl<L, R> Either<L, R> {

    fn map<U, F>(self, f: F) -> Either<L, U>

    where

        F: FnOnce(R) -> U,

    {

        match self {

            Either::Right(v) => Either::Right(f(v)),

            Either::Left(e) => Either::Left(e),

        }

    }


    fn and_then<U, F>(self, f: F) -> Either<L, U>

    where

        F: FnOnce(R) -> Either<L, U>,

    {

        match self {

            Either::Right(v) => f(v),

            Either::Left(e) => Either::Left(e),

        }

    }

}


Esto es básicamente lo que Result<T, E> hace internamente.

Las mónadas en Rust no son magia: son simplemente enums con funciones que saben cuándo continuar y cuándo parar.