Как вернуть результат, содержащий все ошибки от итератора результатов, а не только от первого? - PullRequest
2 голосов
/ 11 марта 2019

Я пытаюсь реализовать простой интерпретатор в Rust, для которого я создал структуру Tokens, которая принимает исходные символы и создает либо Token, либо ScanError внутри Result:

pub struct Tokens<'src> {
    chars: Chars<'src>,
}

impl<'src> Iterator for Tokens<'src> {
    type Item = Result<Token, ScanError>;

    fn next(&mut self) -> Option<Result<Token, ScanError>> {
        //  ...
    }
}

Поскольку Result реализует FromIterator, просто собрать результат в первый ScanError или вектор Token s:

fn scan_tokens(source: &str) -> Result<Vec<Token>, ScanError> {
    let iter = Tokens {
        chars: source.chars(),
    };

    iter.collect()
}

В случае нескольких ошибок я действительно хочу вернуть каждую ошибку:

fn scan_tokens(source: &str) -> Result<Vec<Token>, Vec<ScanError>> {
    // what goes here?
}

Насколько я знаю, невозможно реализовать мою собственную версию FromIterator, потому что ни эта черта, ни Result не являются локальными для моей корзины. Кто-нибудь может предложить чистый способ сделать это?

Я написал реализацию с использованием partition на итераторе, затем развернул каждый Result ниже, но читать его неинтересно и не похоже на хорошее использование итераторов:

type T = Vec<Result<Token, ScanError>>;
fn scan_tokens(source: &str) -> Result<Vec<Token>, Vec<ScanError>> {
    let iter = Tokens {
        chars: source.chars(),
    };

    let (tokens_results, error_results): (T, T) = iter.partition(|result| result.is_ok());
    let errors: Vec<ScanError> = error_results
        .into_iter()
        .map(|result| result.unwrap_err())
        .collect();

    if errors.len() > 0 {
        return Err(errors);
    }

    Ok(tokens_results
        .into_iter()
        .map(|result| result.unwrap())
        .collect())
}

Ответы [ 2 ]

4 голосов
/ 11 марта 2019

распаковка каждого Result

Я бы использовал itertools 'partition_map, чтобы избежать необходимости развертывания:

use itertools::{Either, Itertools}; // 0.8.0

fn iterator() -> impl Iterator<Item = Result<i32, bool>> {
    vec![Ok(1), Err(false), Ok(2), Err(true), Ok(3)].into_iter()
}

fn example() -> Result<Vec<i32>, Vec<bool>> {
    let (values, errors): (Vec<_>, Vec<_>) = iterator().partition_map(|v| match v {
        Ok(v) => Either::Left(v),
        Err(e) => Either::Right(e),
    });

    if errors.is_empty() {
        Ok(values)
    } else {
        Err(errors)
    }
}

Смотри также:

Вы также можете использовать тот факт, что Option и Result реализуют IntoIterator, чтобы избежать точного unwrap, хотя это все равно обрабатывает одну коллекцию дважды:

fn example2() -> Result<Vec<i32>, Vec<bool>> {
    let (values, errors): (Vec<_>, Vec<_>) = iterator().partition(|result| result.is_ok());

    if errors.is_empty() {
        Ok(values.into_iter().flat_map(Result::ok).collect())
    } else {
        Err(errors.into_iter().flat_map(Result::err).collect())
    }
}

Смотри также:

1 голос
/ 11 марта 2019

Императивное решение часто является наиболее выразительным и эффективным способом реализации какого-либо алгоритма. Это Руст, а не Хаскелл; не все должно быть функциональным.

fn scan_tokens(source: &str) -> Result<Vec<Token>, Vec<ScanError>> {
    let iter = Tokens {
        chars: source.chars(),
    };
    let mut tokens = Vec::new();
    let mut errors = Vec::new();
    for result in iter {
        match result {
            Ok(token) => {
                tokens.push(token);
            }
            Err(e) => {
                errors.push(e);
            }
        }
    }
    if errors.is_empty() {
        Ok(tokens)
    } else {
        Err(errors)
    }
}
...