折叠后返回结果的闭包

时间:2017-11-15 21:39:23

标签: rust fold rust-result

我正在使用正则表达式包找到这个正则表达式的一些文本:

lazy_static! {
    static ref FIND_STEPS_RE: Regex =
        Regex::new(r"my regex").unwrap();
}

我想找到所有可能的捕获并迭代它们:

FIND_STEPS_RE.captures_iter(script_slice)

每个捕获的元素由2个值组成:操作和数字。例如,输出可以是:

[("+", "10"), ("-", "20"), ("*", "2")]

我想迭代它,解析数字并应用操作。

我试过了:

let e = FIND_STEPS_RE.captures_iter(script_slice)
    .fold(0, |sum, value| apply_decoding_step)?;

其中apply_decoding_step是:

fn apply_decoding_step(sum: i32, capture: regex::Captures<>) -> Result<i32> {
    let number = parse_number(&capture[2])?;

    match  &capture[1] {
        "+" => Ok(s + number),
        "-" => Ok(s - number),
        "*" => Ok(s * number),
        "/" => Ok(s / number),
        _ => bail!("Unknown step operator"),
    }
}

但我收到了这个错误:

error[E0271]: type mismatch resolving `<fn(i32, regex::Captures<'_>) -> std::result::Result<i32, Error> {apply_decoding_step} as std::ops::FnOnce<(i32, regex::Captures<'_>)>>::Output == i32`
   --> src/main.rs:122:10
    |
122 |         .fold(seed, apply_decoding_step);
    |          ^^^^ expected enum `std::result::Result`, found i32
    |
    = note: expected type `std::result::Result<i32, Error>`
               found type `i32`

我认为这是因为我试图将Result折叠成i32,但因为我需要解析第二个捕获值并且还需要otherwise个案例在我的match中,我该如何解决?

3 个答案:

答案 0 :(得分:5)

作为jupp0r states,初始值必须与闭包的返回值具有相同的类型。

我建议使用Result::and_then在发生错误时不做任何操作:

let result = x.iter().fold(Ok(0), |acc, &i| {
    acc.and_then(|acc| apply_decoding_step(acc, i))
});

达成:

fn main() {
    let x = [("+", "10"), ("-", "20"), ("*", "2")];

    let result = x.iter().fold(Ok(0), |acc, &i| {
        acc.and_then(|acc| apply_decoding_step(acc, i))
    });

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

fn apply_decoding_step(sum: i32, capture: (&str, &str)) -> Result<i32, ()> {
    let number: i32 = capture.1.parse().expect("nope");

    match capture.0 {
        "+" => Ok(sum + number),
        "-" => Ok(sum - number),
        "*" => Ok(sum * number),
        "/" => Ok(sum / number),
        _ => Err(()),
    }
}

这是一个企业级解决方案。我认为这样做的主要好处是迭代将在遇到第一个Err时立即结束,而不是在列表的其余部分中旋转。次要的好处包括能够为每个部分编写非常细粒度的测试(从字符串解析,算术运算,积累等):

fn main() {
    let x = [("+", "10"), ("-", "20"), ("*", "2")];

    let result: Result<Accumulator, ()> = x.iter()
        .map(|&(op, val)| {
            let op = op.parse::<Op>()?;
            let val = val.parse::<i32>().map_err(|_| ())?;
            Ok((op, val))
        })
        .collect();

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

use std::str::FromStr;
use std::iter::FromIterator;

#[derive(Debug)]
enum Op {
    Add,
    Sub,
    Mul,
    Div,
}

impl Op {
    fn apply(&self, a: i32, b: i32) -> i32 {
        use Op::*;

        match *self {
            Add => a + b,
            Sub => a - b,
            Mul => a * b,
            Div => a / b,
        }
    }
}

impl FromStr for Op {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, ()> {
        use Op::*;

        match s {
            "+" => Ok(Add),
            "-" => Ok(Sub),
            "*" => Ok(Mul),
            "/" => Ok(Div),
            _ => Err(()),
        }
    }
}

#[derive(Debug)]
struct Accumulator(i32);

impl<'a> FromIterator<(Op, i32)> for Accumulator {
    fn from_iter<I>(iter: I) -> Self
    where
        I: IntoIterator<Item = (Op, i32)>,
    {
        Accumulator(
            iter.into_iter()
                .fold(0, |acc, (op, val)| op.apply(acc, val)),
        )
    }
}

答案 1 :(得分:2)

仔细查看fold的类型签名:

fn fold<B, F>(self, init: B, f: F) -> B
 where
    F: FnMut(B, Self::Item) -> B,
{ ... }

init必须与f的返回值具有相同的类型。这也是编译器在错误消息中告诉您的内容。你可以做到

fn apply_decoding_step(sum: Result<i32>, capture: regex::Captures<>) -> Result<i32> {
    match sum {
        Err(_) => sum,
        Ok(s) => {      
            let number = parse_number(&capture[2])?;
            match  &capture[1] {
                "+" => Ok(s + number),
                "-" => Ok(s - number),
                "*" => Ok(s * number),
                "/" => Ok(s / number),
                _ => bail!("Unknown step operator"),
           }
       }
    }
}

然后使用Ok种子调用它:

.fold(Ok(seed), apply_decoding_step);

现在,如果发生任何故障,您的fold会返回Err

答案 2 :(得分:0)

您可以通过这样的自定义Iterator功能扩展fold_result(使用Result的完整路径,因为您似乎正在导入生成的Result类型由error_chain):

trait IterExtFoldResult: Iterator + Sized {
    #[inline]
    fn fold_result<B, F, E>(self, mut init: B, mut f: F) -> ::std::result::Result<B, E>
    where
        F: FnMut(B, Self::Item) -> ::std::result::Result<B, E>,
    {
        for i in self {
            init = f(init, i)?;
        }
        Ok(init)
    }
}
impl<I: Iterator> IterExtFoldResult for I {}

并像.fold_result(0, apply_decoding_step)一样使用它。

这种方式foldf返回错误时中止;如果你转发f中的错误,编译器可能会或可能不会优化到早期返回。

Full example in playground