Комбинаторы очень интересны для того, чтобы сделать ваш код чище и функциональнее. Почти все определения, которые вы найдете в Интернете, взорвут вашу голову 🤯, потому что они вызывают больше вопросов, чем дают ответов.

Итак, вот мое эмпирическое определение: Комбинаторы — это методы, облегчающие манипуляции с некоторыми типами данных. T. Они предпочитают функциональный стиль кода (цепочка методов).

let sum: u64 = vec![1, 2, 3].into_iter().map(|x| x * x).sum();
Войти в полноэкранный режим

Выйти из полноэкранного режима

Этот раздел будет чистым практическим руководством и примерами из реального мира о том, как комбинаторы облегчают чтение или рефакторинг вашего кода.

Этот пост является отрывком из моей книги Черная шляпа ржавчины
Получите скидку 42% до Четверг, 11 ноября с купоном 1311B892


Итераторы

Давайте начнем с итераторов, потому что это, безусловно, ситуация, когда комбинаторы используются чаще всего.


Получение итератора

Ан Iterator — это объект, который позволяет разработчикам перемещаться по коллекциям.

Итераторы можно получить из большинства коллекций стандартной библиотеки.

Первый, into_iter который предоставляет собственный итератор: коллекция перемещается, и вы больше не можете использовать исходную переменную.

ch_03/snippets/combinators/src/main.rs

fn vector() {
    let v = vec![
        1, 2, 3,
    ];

    for x in v.into_iter() {
        println!("{}", x);
    }

    // you can't longer use v
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Затем, iter который предоставляет заимствованный итератор. Здесь key а также value переменные являются ссылками (&String в таком случае).

fn hashmap() {
    let mut h = HashMap::new();
    h.insert(String::from("Hello"), String::from("World"));

    for (key, value) in h.iter() {
        println!("{}: {}", key, value);
    }
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Начиная с версии 1.53 (выпущенной 17 июня 2021 г.) итераторы также можно получить из массивов:

ch_03/snippets/combinators/src/main.rs

fn array() {
    let a =[
        1, 2, 3,
    ];

    for x in a.iter() {
        println!("{}", x);
    }
}
Войти в полноэкранный режим

Выйти из полноэкранного режима


Использование итераторов

Итераторы ленивы: они ничего не сделают, если их не использовать.

Как мы только что видели, Итераторы можно использовать с for x in петли. Но это не то место, где они используются чаще всего. Идиоматический Rust отдает предпочтение функциональному программированию. Он лучше подходит для своей модели владения.

для каждого является функциональным эквивалентом for .. in .. петли:

ch_03/snippets/combinators/src/main.rs

fn for_each() {
    let v = vec!["Hello", "World", "!"].into_iter();

    v.for_each(|word| {
        println!("{}", word);
    });
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

собирать можно использовать для преобразования итератора в коллекцию:

ch_03/snippets/combinators/src/main.rs

fn collect() {
    let x = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_iter();

    let _: Vec<u64> = x.collect();
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

И наоборот, вы можете получить HashMap (или BTreeMapили другие коллекции, см. https://doc.rust-lang.org/std/iter/trait.FromIterator.html#implementorsс использованием from_iter:

ch_03/snippets/combinators/src/main.rs

fn from_iter() {
    let x = vec![(1, 2), (3, 4), (5, 6)].into_iter();

    let _: HashMap<u64, u64> = HashMap::from_iter(x);
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

уменьшать накапливается по итератору, применяя замыкание:

ch_03/snippets/combinators/src/main.rs

fn reduce() {
    let values = vec![1, 2, 3, 4, 5].into_iter();

    let _sum = values.reduce(|acc, x| acc + x);
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Здесь _sum = 1 + 2 + 3 + 4 + 5 = 15

складывать как reduce но может возвращать аккумулятор другого типа, чем элементы итератора:

ch_03/snippets/combinators/src/main.rs

fn fold() {
    let values = vec!["Hello", "World", "!"].into_iter();

    let _sentence = values.fold(String::new(), |acc, x| acc + x);
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Здесь _sentence это Stringа элементы итератора имеют тип &str.


Комбинаторы

Во-первых, один из самых известных и доступных почти на всех языках: фильтр:

ch_03/snippets/combinators/src/main.rs

fn filter() {
    let v = vec![-1, 2, -3, 4, 5].into_iter();

    let _positive_numbers: Vec<i32> = v.filter(|x: &i32| x.is_positive()).collect();
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

осмотреть можно использовать для… проверки значений, проходящих через итератор:

ch_03/snippets/combinators/src/main.rs

fn inspect() {
    let v = vec![-1, 2, -3, 4, 5].into_iter();

    let _positive_numbers: Vec<i32> = v
        .inspect(|x| println!("Before filter: {}", x))
        .filter(|x: &i32| x.is_positive())
        .inspect(|x| println!("After filter: {}", x))
        .collect();
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

карта используется для преобразования элементов итератора из одного типа в другой:

ch_03/snippets/combinators/src/main.rs

fn map() {
    let v = vec!["Hello", "World", "!"].into_iter();

    let w: Vec<String> = v.map(String::from).collect();
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Здесь из &str к String.

filter_map похоже на цепочку map а также filter. Преимущество заключается в том, что он имеет дело с Option вместо bool:

ch_03/snippets/combinators/src/main.rs

fn filter_map() {
    let v = vec!["Hello", "World", "!"].into_iter();

    let w: Vec<String> = v
        .filter_map(|x| {
            if x.len() > 2 {
                Some(String::from(x))
            } else {
                None
            }
        })
        .collect();

    assert_eq!(w, vec!["Hello".to_string(), "World".to_string()]);
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

цепь объединяет два итератора:

ch_03/snippets/combinators/src/main.rs

fn chain() {
    let x = vec![1, 2, 3, 4, 5].into_iter();
    let y = vec![6, 7, 8, 9, 10].into_iter();

    let z: Vec<u64> = x.chain(y).collect();
    assert_eq!(z.len(), 10);
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

сгладить может использоваться для выравнивания коллекций коллекций:

ch_03/snippets/combinators/src/main.rs

fn flatten() {
    let x = vec![vec![1, 2, 3, 4, 5], vec![6, 7, 8, 9, 10]].into_iter();

    let z: Vec<u64> = x.flatten().collect();
    assert_eq!(z.len(), 10);
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

В настоящее время z = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];


Составление комбинаторов

Именно в этом проявляются комбинаторы: они делают ваш код более элегантным и (в большинстве случаев) более легким для чтения, потому что они ближе к тому, как думают люди, чем к тому, как работают компьютеры.

ch_03/snippets/combinators/src/main.rs

#[test]
fn combinators() {
    let a = vec![
        "1",
        "2",
        "-1",
        "4",
        "-4",
        "100",
        "invalid",
        "Not a number",
        "",
    ];

    let _only_positive_numbers: Vec<i64> = a
        .into_iter()
        .filter_map(|x| x.parse::<i64>().ok())
        .filter(|x| x > &0)
        .collect();
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

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

  • Попробуйте разобрать массив набора строк на числа
  • отфильтровать неверные результаты
  • номера фильтров меньше 0
  • собрать все в новый вектор

Его преимущество в том, что он работает с неизменяемыми данными и, таким образом, снижает вероятность ошибок.

Этот пост является отрывком из моей книги Черная шляпа ржавчины
Получите скидку 42% до Четверг, 11 ноября с купоном 1311B892



Option

Используйте значение по умолчанию: развернуть_или

fn option_unwrap_or() {
    let _port = std::env::var("PORT").ok().unwrap_or(String::from("8080"));
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Использовать значение по умолчанию Option ценность: или же

// config.port is an Option<String>
let _port = config.port.or(std::env::var("PORT").ok());
// _port is an Option<String>
Войти в полноэкранный режим

Выйти из полноэкранного режима

Вызвать функцию, если Option является Some: а потом

fn port_to_address() -> Option<String> {
    // ...
}

let _address = std::env::var("PORT").ok().and_then(port_to_address);
Войти в полноэкранный режим

Выйти из полноэкранного режима

Вызвать функцию, если Option является None: or_else

fn get_default_port() -> Option<String> {
    // ...
}

let _port = std::env::var("PORT").ok().or_else(get_default_port);
Войти в полноэкранный режим

Выйти из полноэкранного режима

И две чрезвычайно полезные функции для Option тип:
is_some а также is_none

is_some возвращается true является Option является Some (содержит значение):

let a: Option<u32> = Some(1);

if a.is_some() {
    println!("will be printed");
}

let b: Option<u32> = None;

if b.is_some() {
    println!("will NOT be printed");
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

is_none возвращается true является Option является None (делает нет содержать значение):

let a: Option<u32> = Some(1);

if a.is_none() {
    println!("will NOT be printed");
}


let b: Option<u32> = None;

if b.is_none() {
    println!("will be printed");
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Вы можете найти другие (и, по моему опыту, менее часто используемые) комбинаторы для Option введите онлайн: https://doc.rust-lang.org/std/option/enum.Option.html.



Result

Конвертировать Result чтобы Option с ok:

ch_03/snippets/combinators/src/main.rs

fn result_ok() {
    let _port: Option<String> = std::env::var("PORT").ok();
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Использовать значение по умолчанию Result если Result является Err с or:

ch_03/snippets/combinators/src/main.rs

fn result_or() {
    let _port: Result<String, std::env::VarError> =
        std::env::var("PORT").or(Ok(String::from("8080")));
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

map_err преобразует Result<T, E> к Result<T, F> вызвав функцию:

fn convert_error(err: ErrorType1) -> ErrorType2 {
    // ...
}


let _port: Result<String, ErrorType2> = std::env::var("PORT").map_err(convert_error);
Войти в полноэкранный режим

Выйти из полноэкранного режима

Вызвать функцию, если Results является Ok: а потом.

fn port_to_address() -> Option<String> {
    // ...
}

let _address = std::env::var("PORT").and_then(port_to_address);
Войти в полноэкранный режим

Выйти из полноэкранного режима

Вызов функции и значение по умолчанию: карта_или

let http_port = std::env::var("PORT")
    .map_or(Ok(String::from("8080")), |env_val| env_val.parse::<u16>())?;
Войти в полноэкранный режим

Выйти из полноэкранного режима

Связать функцию, если Result является Ok: карта

let master_key = std::env::var("MASTER_KEY")
    .map_err(|_| env_not_found("MASTER_KEY"))
    .map(base64::decode)??;
Войти в полноэкранный режим

Выйти из полноэкранного режима

И две последние крайне полезные функции для Result тип:
is_ok а также is_err

is_ok возвращается true является Result является Ok:

if std::env::var("DOES_EXIST").is_ok() {
    println!("will be printed");
}

if std::env::var("DOES_NOT_EXIST").is_ok() {
    println!("will NOT be printed");
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

is_err возвращается true является Result является Err:

if std::env::var("DOES_NOT_EXIST").is_err() {
    println!("will be printed");
}

if std::env::var("DOES_EXIST").is_err() {
    println!("will NOT be printed");
}
Войти в полноэкранный режим

Выйти из полноэкранного режима

Вы можете найти другие (и, по моему опыту, менее часто используемые) комбинаторы для Result введите онлайн: https://doc.rust-lang.org/std/result/enum.Result.html.


Когда использовать .unwrap() а также .expect()

unwrap а также expect можно использовать на обоих Option а также Result. Они могут привести к сбою вашей программы, поэтому используйте их с осторожностью.

Я вижу 2 ситуации, когда их можно использовать:

  • Либо при выполнении исследования, либо в быстрых программах, подобных сценариям, чтобы не беспокоиться об обработке всех крайних случаев.
  • Когда ты уверен, что они никогда не разобьются, ноони должны сопровождаться комментарием, объясняющим, почему их безопасно использовать и почему они не приведут к сбою программы.

Этот пост является отрывком из моей книги Черная шляпа ржавчины
Получите скидку 42% до Четверг, 11 ноября с купоном 1311B892