Есть ли какой-либо встроенный способ объединить два варианта?

Могу ли я как-нибудь избежать определения 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

Я не знаю, можете ли вы перейти к одной строке (Edit: о, принятый ответ красиво сводит его к одной строке), но вы можете избежать вложенного 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
Я не уверен, что так должно быть. Обратите внимание, что как пример в исходном вопросе, так и пример _1 _ + _ 2_ выше, возвращают 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), но тогда вам нужно будет указать вид закрытия _6 _.

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) в сочетании с оператором ? (попытка):

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

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

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

В будущем вы можете использовать блоки try:

#![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