Има ли някакъв вграден начин за комбиниране на две опции?

В следната примерна програма има ли някакъв начин да избегна необходимостта да дефинирам map2?

fn map2<T, U, V, F: Fn(T, U) -> V>(f: F, a: Option<T>, b: Option<U>) -> Option<V> {
    match a {
        Some(x) => match b {
            Some(y) => Some(f(x, y)),
            None => None,
        },
        None => None,
    }
}

fn main() {
    let a = Some(5);
    let b = Some(10);
    let f = |a, b| {
        a + b
    };
    let res = map2(f, a, b);
    println!("{:?}", res);
    // prints Some(15)
}

За хора, които също говорят Haskell, предполагам, че този въпрос може да бъде формулиран и като „Има ли някакъв инструмент, който можем да използваме вместо liftM2 в Rust?“


person Erik Vesteraas    schedule 18.11.2015    source източник


Отговори (6)


Не вярвам, че има директна функция, еквивалентна на liftM2, но можете да комбинирате Option::and_then и Option::map като това:

fn main() {
    let a = Some(5);
    let b = Some(10);
    let f = |a, b| {
        a + b
    };

    println!("{:?}", a.and_then(|a| b.map(|b| f(a, b))));
}

Изход:

Some(15)
person Dogbert    schedule 18.11.2015
comment
Благодаря, това всъщност е доста добро решение, когато трябва да направите това само веднъж или два пъти. Вероятно все пак си струва да дефинирате функцията в някои случаи. - person Erik Vesteraas; 18.11.2015
comment
Друг вариант, който е малко по-дълъг, но може би по-лесен за следване: a.and_then(|a| b.and_then(|b| Some(f(a, b)))) - person Jack O'Connor; 02.07.2018

Не знам дали можете да стигнете до един ред (Редактиране: о, приетият отговор го свежда добре до един ред), но можете да избегнете вложеното match чрез съпоставяне на кортеж:

let a = Some(5);
let b = Some(10);
let f = |a, b| {
    a + b
};
let res = match (a, b) {
    (Some(a), Some(b)) => Some(f(a, b)),
    _ => None,
};
println!("{:?}", res);
// prints Some(15)
person Jack O'Connor    schedule 03.10.2017
comment
Намирам това за непълно. Ако a или b е None, това връща None, където трябва да върне съответно Some(5) или Some(10). - person RubberDuck; 01.07.2018
comment
Не съм сигурен, че трябва. Обърнете внимание, че и примерът в оригиналния въпрос, и примерът and_then+map по-горе връщат None, ако аргументът или е None. По принцип, ако типът на връщане на f е различен от типа на неговите аргументи, може да не е възможно да се върне нещо различно от None. Въпреки това, ако искате резервния вариант в този случай, можете да замените клаузата _ => None с _ => a.or(b). - person Jack O'Connor; 02.07.2018


Можете да използвате факта, че Options могат да бъдат итерирани. Итерирайте и двете опции, съединете ги заедно и картографирайте получения итератор върху вашата функция.

fn main() {
    let a = Some(5);
    let b = Some(10);
    let f = |(a, b)| {
        a + b
    };
    let res = a.iter().zip(b.iter()).map(f).next();
    println!("{:?}", res);
    // prints Some(15)
}

Това изисква модификация на f, така че аргументите се обединяват в един аргумент-кортеж. Би било възможно без промяна на f, чрез директно картографиране върху |args| f.call(args), но тогава ще трябва да укажете вида на затваряне на f.

person oli_obk    schedule 18.11.2015
comment
алтернатива: map(|(a,b)| f(a,b)) - person sellibitze; 18.11.2015
comment
да, опитвах се да избегна повторението на извикването на функцията. Ако го повтаряте, тогава решението на @Dogbert е по-доброто. - person oli_obk; 18.11.2015

Можете да използвате незабавно извикан функционален израз (IIFE), комбиниран с оператора ? (try):

fn main() {
    let a = Some(5);
    let b = Some(10);
    let f = |a, b| a + b;

    let res = (|| Some(f(a?, b?)))();

    println!("{:?}", res);
}

В бъдеще можете да използвате пробни блокове:

#![feature(try_blocks)]

fn main() {
    let a = Some(5);
    let b = Some(10);
    let f = |a, b| a + b;

    let res: Option<_> = try { f(a?, b?) };

    println!("{:?}", res);
}

Вижте също:

person Shepmaster    schedule 19.05.2020
comment
Е, това е доста добре. Дори когато си струва да се дефинира map2, Some(f(a?, b?)) е много по-хубава реализация от тази, която написах през 2015 г. - person Erik Vesteraas; 20.05.2020

let num_maybe = Some(5);
let num_maybe2 = Some(10);
let f = |a, b| {
    a + b
};

Опция 1

if let (Some(a), Some(b)) = (num_maybe, num_maybe2) {
    f(a, b)
}

Вариант 2

num_maybe.and_then(|a| num_maybe2.map(|b| f(a, b))

Вариант 3

[num_maybe, num_maybe2].into_iter().flatten().fold(0, f)
person Ben    schedule 21.06.2019
comment
Вашият вариант 2 вече присъства в отговора с най-много гласове - person Shepmaster; 21.06.2019