Свернуть с закрытием, которое возвращает результат

Я использую ящик регулярных выражений, чтобы найти текст с этим регулярным выражением:

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, как я могу это исправить?


person Sassa    schedule 15.11.2017    source источник


Ответы (3)


Как указывает jupp0r, начальное значение Iterator::fold должно быть того же типа, что и возвращаемое значение замыкания.

Ржавчина 1.26

Вместо этого вы можете использовать Iterator::try_fold. Это завершит итерацию при первом сбое:

let result = x.iter().try_fold(0, |acc, &i| apply_decoding_step(acc, i));

Полный пример:

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

    let result = x.iter().try_fold(0, |acc, &i| 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(()),
    }
}

Ржавчина 1.0

Я бы рекомендовал использовать Result::and_then, чтобы ничего не пропускать при возникновении ошибки:

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

Проблема здесь в том, что тело fold выполняется для каждого элемента итератора, даже если возникает ошибка.

Вот решение корпоративного уровня, основное преимущество которого заключается в том, что итерация завершится, как только встретится первый 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::iter::FromIterator;
use std::str::FromStr;

#[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)),
        )
    }
}
person Shepmaster    schedule 15.11.2017

Присмотритесь к сигнатуре типа для 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.

person jupp0r    schedule 15.11.2017
comment
Разве let sum = sum?; не будет короче? - person Shepmaster; 16.11.2017

Вы можете расширить 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).

Таким образом, fold прерывается, когда f возвращает ошибку; если вы перенаправите ошибку в f, компилятор может оптимизировать или не оптимизировать до раннего возврата.

Полный пример на игровой площадке

person Stefan    schedule 15.11.2017