【问题标题】:Is there any built in way to "combine" two Options?是否有任何内置方式可以“组合”两个选项?
【发布时间】:2015-11-18 12:02:49
【问题描述】:

在下面的示例程序中,有什么方法可以避免定义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 的人,我想这个问题也可以表述为“我们可以使用任何工具来代替 Rust 中的 liftM2 吗?”

【问题讨论】:

    标签: rust optional


    【解决方案1】:

    我不相信有一个直接函数相当于liftM2,但你可以像这样组合Option::and_thenOption::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)
    

    【讨论】:

    • 谢谢,当您只需要执行一次或两次时,这实际上是一个非常好的解决方案。不过,在某些情况下定义函数可能仍然值得。
    • 另一个更长一点但可能更容易理解的选项:a.and_then(|a| b.and_then(|b| Some(f(a, b))))
    【解决方案2】:

    从 Rust 1.46.0 开始,您可以使用 Option::zip:

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

    这可以和Option::map结合,如其他答案所示:

    fn map2<T, U, V, F: Fn(T, U) -> V>(f: F, a: Option<T>, b: Option<U>) -> Option<V> {
        a.zip(b).map(|(x, y)| f(x, y))
    }
    

    【讨论】:

      【解决方案3】:

      我不知道你是否可以降到一行(编辑:哦,接受的答案很好地降到了一行),但你可以通过匹配一个元组来避免嵌套的 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)
      

      【讨论】:

      • 我觉得这不完整。如果 a 或 b 是 None,则返回 None,它应该分别返回 Some(5)Some(10)
      • 我不确定是否应该这样做。请注意,如果 either 参数是 None,则原始问题中的示例和上面的 and_then+map 示例都会返回 None。通常,如果f 的返回类型与其参数的类型不同,则可能无法返回除None 之外的任何内容。也就是说,如果您想要在这种情况下进行回退,您可以将 _ =&gt; None 子句替换为 _ =&gt; a.or(b)
      【解决方案4】:

      您可以使用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),但是你必须specify the closure kind of f

      【讨论】:

      • 替代:map(|(a,b)| f(a,b))
      • 是的,我试图避免重复函数调用。如果您要重复它,那么@Dogbert 的解决方案是更好的解决方案。
      【解决方案5】:
      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)
      

      【讨论】:

      【解决方案6】:

      您可以将immediately invoked function expression (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);
      }
      

      以后你可以使用try blocks

      #![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);
      }
      

      另见:

      【讨论】:

      • 嗯,这很整洁。即使定义 map2 是值得的,Some(f(a?, b?)) 也是比我在 2015 年写的更好的实现。
      猜你喜欢
      • 1970-01-01
      • 2021-02-08
      • 2021-03-04
      • 2020-12-19
      • 1970-01-01
      • 1970-01-01
      • 2015-07-18
      • 2017-08-01
      • 1970-01-01
      相关资源
      最近更新 更多