Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

コンビネータ: and_then

map() は、match 文を簡潔にするための連鎖可能な方法として説明しました。 しかし、Option<T> を返す関数に map() を使うと、ネストした Option<Option<T>> になります。複数の呼び出しを連鎖させると、 混乱しやすくなります。そこで、いくつかの言語では flatmap として知られる and_then() という別のコンビネータの出番です。

and_then() は、ラップされた値を使って入力された関数を呼び出し、その結果を返します。OptionNone の場合は、代わりに None を返します。

次の例では、cookable_v3()Option<Food> を返します。 and_then() の代わりに map() を使うと Option<Option<Food>> になってしまい、これは eat() に対して無効な型です。

#![allow(dead_code)]

#[derive(Debug)] enum Food { CordonBleu, Steak, Sushi }
#[derive(Debug)] enum Day { Monday, Tuesday, Wednesday }

// Sushi を作るための材料はありません。
fn have_ingredients(food: Food) -> Option<Food> {
    match food {
        Food::Sushi => None,
        _           => Some(food),
    }
}

// Cordon Bleu 以外のすべてのレシピがあります。
fn have_recipe(food: Food) -> Option<Food> {
    match food {
        Food::CordonBleu => None,
        _                => Some(food),
    }
}

// 料理を作るには、レシピと材料の両方が必要です。
// このロジックは `match` の連鎖で表現できます:
fn cookable_v1(food: Food) -> Option<Food> {
    match have_recipe(food) {
        None       => None,
        Some(food) => have_ingredients(food),
    }
}

// これは `and_then()` を使うと、より簡潔に書き換えられます:
fn cookable_v3(food: Food) -> Option<Food> {
    have_recipe(food).and_then(have_ingredients)
}

// そうしない場合、`Option<Option<Food>>` を `flatten()` して
// `Option<Food>` を得る必要があります:
fn cookable_v2(food: Food) -> Option<Food> {
    have_recipe(food).map(have_ingredients).flatten()
}

fn eat(food: Food, day: Day) {
    match cookable_v3(food) {
        Some(food) => println!("やった! {:?} には {:?} が食べられます。", day, food),
        None       => println!("なんてこと。{:?} には食べられないのですか?", day),
    }
}

fn main() {
    let (cordon_bleu, steak, sushi) = (Food::CordonBleu, Food::Steak, Food::Sushi);

    eat(cordon_bleu, Day::Monday);
    eat(steak, Day::Tuesday);
    eat(sushi, Day::Wednesday);
}

関連項目:

クロージャOptionOption::and_then()、および Option::flatten()