Combinadores: and_then

map() se describió como una forma encadenable de simplificar las declaraciones de match. Sin embargo, el uso de map() en una función que devuelve una Option<T> da como resultado la Option<Option<T>> anidada. Entonces, encadenar varias llamadas juntas puede resultar confuso. Ahí es donde entra otro combinador llamado and_then()`, conocido en algunos lenguajes como mapa plano.

and_then() llama a su entrada de función con el valor envuelto y devuelve el resultado. Si la Opción es None, entonces devuelve None en su lugar.

En el siguiente ejemplo, cocinablev2() da como resultado una Option<Comida>. Usar map() en lugar de and_then() habría dado una Option<Option<Comida>>, que es un tipo inválido para come().

#![allow(dead_code)]

#[derive(Debug)] enum Comida { CordonBleu, Filete, Sushi }
#[derive(Debug)] enum Dia { Lunes, Martes, Miercoles }

// No tenemos los ingredientes para hacer sushi.
fn tengo_ingredientes(comida: Comida) -> Option<Comida> {
    match comida {
        Comida::Sushi => None,
        _             => Some(comida),
    }
}

// Tenemos la receta para todo menos Cordon Bleu.
fn tengo_receta(comida: Comida) -> Option<Comida> {
    match comida {
        Comida::CordonBleu => None,
        _                  => Some(comida),
    }
}

// Para preparar un plato, necesitamos tanto la receta como los ingredientes.
// Podemos representar la lógica con una cadena de `match`es:
fn cocinablev1(comida: Comida) -> Option<Comida> {
    match tengo_receta(comida) {
        None         => None,
        Some(comida) => match tengo_ingredientes(comida) {
            None         => None,
            Some(comida) => Some(comida),
        },
    }
}

// Esto se puede reescribir convenientemente de manera más compacta con `and_then()`:
fn cocinablev2(comida: Comida) -> Option<Comida> {
    tengo_receta(comida).and_then(tengo_ingredientes)
}

fn come(comida: Comida, day: Dia) {
    match cocinablev2(comida) {
        Some(comida) => println!("¡Hurra! Los {:?} comemos {:?}.", day, comida),
        None         => println!("Oh no. No podemos comer los {:?}?", day),
    }
}

fn main() {
    let (cordon_bleu, filete, sushi) = (Comida::CordonBleu, Comida::Filete, Comida::Sushi);

    come(cordon_bleu, Dia::Lunes);
    come(filete, Dia::Martes);
    come(sushi, Dia::Miercoles);
}

Ve también

clausuras, Option, y Option::and_then()