Как стать автором
Обновить
3
0

Опенсорсер

Отправить сообщение

Я вам больше скажу. Бывают случаи, когда нужен не map, а action - некоторое действие с каждым элементов коллекции.

И для этого также есть разные средства (к сожалению, в статье не показанные).
Если этот action просто использует значение (скорее, ссылку на него), то for, который работает на всём, что реализует IntoIterator, который включает в себя (но не ограничивается этим) и сами итераторы:

for item in &vec {
    println!("Item is {item}");
}

Если же нужно in-place, то... Снова for, потому что итерироваться можно ещё и по mut-ссылкам:

for item in &mut vec {
    // in-place увеличиваем каждый элемент на 10
    *item += 10;
}

Ну это уже сферические map/filter в вакууме. В реальности все ограничивается размерами памяти и накладными расходами на ее выделение/освобождение

Вовсе нет. В этом и прекрасная особенность итераторов: они не обязаны соответствовать где-то выделенному куску данных, а являются генераторами некого потока данных, который на каждой итерации порождает как-то следующее значение, причём далеко не обязательно в дальнейшем все эти значения куда-то складывать (в статье, к сожалению, преимущественно такое показано).

И это прекрасно, потому что зачастую функции не нужно собирать все элементы в памяти, ей достаточно единожды обходимого потока данных (самый простой пример - нахождения минимума/максимума). И таким функция достаточно принимать просто некий итератор, а не фиксированный кусок памяти.

Например, в std есть iter::repeat, который просто неограниченно генерирует один и тот же элемент, а уж что с ним будет делать получатель данных - его выбор. И при этом самому итератор нужно O(1) памяти на своё внутренне представление. Или, например, в крейте rand ГПСЧ позволяют получить из них итераторы, возвразающие бесконечный итератор псевдослучайных чисел.

Давайте перескажем вам, как мы вычитали, что в свежих JEP появилось, и как мы хотим это применить на проде

Как один из спикеров, ответственно заявляю, что буду рассказывать именно про то, что реально используем, и почему именно так

Неужто Rust встал таки на кривую дорожку совместимости ABI между разными версиями?

Благо (на мой взгляд) нет, здесь речь только о тех вещах, которые явно используются в FFI (extern "C").

Создание объектов и запуск в отдельных потоках run выполняется одним потоком.

А исполнение тела run() в другом, и именно в этом теле идёт доступ к записанной в другом потоке переменной.

Компайлер может попросить закэшить что-то, если это что-то очень часто и много пишется с одного потока.

А может не попросить, а может и не может. Это никем не гарантируется. А вот то, что изменение (запись строки) может быть не замечено явно описано, причём не в какой-то отдельной спеке, а прямо таки в спецификации языка.

За всю свою многолетнюю (больше 20 лет) практику от платёжных и до высоконагруженных систем, я ни разу не воспользовался этим словом.

Апелляция к опыту это, конечно, прикольно, но она ничего не говорит, кроме того, что лично вы на такое не наткнулись (а, может, просто не заметили). Вот вам конкретный контрпример из Lucene LUCENE-8780.

За повсеместное втыкание final, надо бить по рукам. Что final, что модификаторы видимости - это для библиотек больше. Люди, имеющие доступ к этим модификатором удалят их, если им действительно нужно будет что-то сделать public или перереференсировать.

Плохие разработчики мешают стрелять по ногам. Эти вещи придумали не просто так, а для решения конкретных проблем. Если что-то не должно изменяться, то мне, как разработчику, гораздо удобнее явно иметь признак этого (final на поле) и отсюда какую-то гарантию того, что никто мне поле не поменяет [1]. Как типовой пример, что final -- это хорошо, вспомните, как устроены строки и как они полагаются на свою иммутабельность.

Причём эти вещи позволяют (и тут мы уже переходим к реальным системам) рантайму быть эффективнее, например компилятору (JITу) с чистой совестью решать, что он может кэшировать, а что нет, что он может держать локально, а что должен закоммитить в память.

Надо нанимать людей, которые понимают зачем изменчивость, зачем куча и стек, сильные и слабые стороны ООП и ФП и т.д.

А неизменяемость это не какая-то уникальная особенность ФП. Ей есть место много где, включая ООП. Да и Java это не чисто-ООП язык, а мультипарадигмальный. И если посмотреть на вектор развития языка, то многие нововведений предпочитают иммутабельность (records, scoped values, frozen arrays).

[1]: На самом деле, есть штуки три способов в обход final записать поле, но они больно экзотические, плюс могут вести к деоптимизациям, плюс с ними постепенно пытаются бороться в OpenJDK.

Скрытые условия

Гитхаб Вам даже отдельную кнопочку рисует в описании проекта

еще и на иностранном языке

Да и для лицензии есть вполне себе официальный перевод

Да, для конверсий стоит использовать именно трейты From и Into.

Единственное, что из этой пары следует реализовывать именно From, поскольку Into в таком случае реализуется автоматически (обратное неверно), то есть:

impl From<FooInit> for FooReady {
    fn from(init: FooInit) -> Self {
        Self { c: init.a + init.b }
    }
}

Также стоит обратить внимание на трейты TryFrom и TryInto, которые также используются для конверсий, но на этот раз таких, которые могут быть неуспешными (они, кстати, также автоматически реализуются с типом невозможной ошибки Infallible, если реализованы обычные конверсии From).

Ну и, как верно подметил выше @medvedevia, с недавних пор есть компактный let-else:

let Some(cthulhu) = call_cthulhu() else {
    return;
};

// ...
tickle(cthulhu);

А для него необходимо иметь возвращаемое значение вида как минимум Option<()>

Но ведь ничто не мешает в, условно, одну строчку конвертироваться из Option<T> в Result<T, E> и далее сразу его вернуть try-оператором.

fn try_operator_variant() -> Result<String, &'static str> {
    let cthulhu = call_cthulhu()
        .ok_or("Failed to call cthulhu")?;

    tickle(cthulhu);

    todo!("Don't forget about me")
}

у вас сигнатуры возвращаемых данных расходятся

Почему же? Оба варианта правильные.

В случае с if-let-else ещё происходит shadowing предыдущей локальной переменной cthulhu: Option<Cthulhu> новой cthulhu: Cthulhu, полученной в качестве результата if-let-else выражения.

Магия есть, но не чёрная. Это конкретный lang item, для которого (и только) компилятор знает об interior mutabilty.

Высокоуровневые же примитивы для interior mutabilty строятся на его основе.

Ну так ведь это не эквивалентное сравнение. Можно же сделать

let int = maybe_int.unwrap();
for _ in 1..1001 {
  do_something(int);
}

По-моему и логично локализовать fast-math'ную логику в отдельных частях программы (чем бы она не была), а не делать глобальной для всей программы, ломая те вещи, которым разумно полагаться на точность работы с float'ами.

Тем более, что тут ничего сложного изобретать не надо:

  • можно использовать быстрые функции только тогда, когда это действительно уместно (см. fast-math);

  • можно в конкретных задачах использовать свои типы, реализованные с быстрой математикой (например, таковые из fast-floats);

  • можно делать свои типы обощёнными относительно некого генерика, реализованного как на стандартных флотах, так и на ускоренных, а ля

struct Rotation<F: Float32> {
    yaw: F,
    pitch: F,
}

где некий Float32 реализован как на f32, так и на каком-ниубудь FF32.

К сожелению, библиотека строго под Csharp, аналогов этой библиотеки я не встречал в других языках и экосистемах.

  • Java: jOOQ: query builder с максимально близким к SQL DSL и проверкой типов на этапе компиляции

  • Rust: Diesel: гибрид ORM и query builder'а, работающий поверх статически генерируемой схемы

  • Rust: Sqlx: статический валидатор и маппер SQL-запросов

И? То, что оптимизация, на которую я сослался, есть, не значит, что надо изобретать бессмысленные структуры, как Option<NonZero<Num>> (на самом деле, вполне себе осмысленные, но не для данной задачи).

Суть моего комментария -- противопоставление Вашему

0 для отсутствия индекса вполне элегантно, быстро и компактно. В отличие от ADT, который превращается в discriminated union.

Контрпример, демонстрирующий, что сум-тип -- очень даже удобно.

Оптимизация были упомянуты дополнительно как что-то, что, в целом, хорошо дружит с последними, но не как инструкция к написанию нелогичного -- для задачи инексирования -- типа.

В дополнение к тому, что индексация с нуля удобна, она ещё и естественна для многих прикладных вещей.

А с чего вдруг NonZero<Num>, если речь, как раз, о том, чтобы оставить 0 как индекс начинающего элемента?

А в чём конкретно проблема discriminated union, если он нормально реализован и, тем более, хорошо подлежит оптимизациям (TL;DR, если у вас есть дырка из невозможного битового представления, то это невозможное значение и будет представлять собой пустой вариант)?

Как верно написал , просто так рассматривать &Vec<String> как &[&str] не получится. Причина в том, что слайс &[T] подразумевает последовательно идущие данные типа T (с учётом выравниания), в то время как у String представление включает в себя &str, но не только его, из-за чего сугубо такой варинт без копирования (в новую структуру с новым представлением) не реализуем.

Но есть гораздо более гибкий вариант сделать метод более универсальным без необходимости в лишних копированиях, к которому мы придём в несколько шагов улучшения изначального метода:

  1. пусть вместо строго строки (а, фактически, нас волнует даже не String, а то, что мы получаем ссылку ан её str-составляющую) будет абстрактное нечто, что можно преобразовать в &str:

fn show_notes<T: AsRef<str>>(notes: &Vec<T>) {
    // Выводим пустую строку.
    println!();

    // Для каждой заметки в заметках ...
    for note in notes {
        // выводим её на экран.
        println!("{}", note.as_ref())
    }
}

Я метода появился типовой параметр T, который должен быть чем-то, что может быть представлено как ссылка на str.

  1. на место вектора действительно можно поставить просто слайс (фактически, для вектора Vec<T> верно, что он AsRef[T], но тут в этом нет необходимости):

fn show_notes<T: AsRef<str>>(notes: &[T]) {
    // Выводим пустую строку.
    println!();

    // Для каждой заметки в заметках ...
    for note in notes {
        // выводим её на экран.
        println!("{}", note.as_ref())
    }
}
  1. наконец, вво имя красоты и компактности, воспользуемся синтаксическим сахаром, позволяющим описать п.1 более компактно:

fn show_notes(notes: &[impl AsRef<str>]) {
    // Выводим пустую строку.
    println!();

    // Для каждой заметки в заметках ...
    for note in notes {
        // выводим её на экран.
        println!("{}", note.as_ref())
    }
}

По итогу, имеем метод, который принимает слайс, содержащий что-то, что можно представить как ссылку на str, работающий как со String, так со str, так и с произвольными типами, реализующими AsRef<str> (при этом лежащими в любом контейнере, который представим как слайс):

fn main() {
    let notes = vec!["foo".to_string(), "bar".to_string()];
    show_notes(&notes);

    let notes = vec!["baz", "qux"];
    show_notes(&notes);

    let notes = vec!["dora", "prgrm"];
    show_notes(&notes);
}
  1. В качестве более продвинутого варианта, можно принимать даже не слайс (потому что непосредственной необходимости в последовательности данных у нас нет), а нечто, что можно последовательно обходить (итерироваться по этому), или даже то, почему можно устроить обход:

fn show_notes(notes: impl IntoIterator<Item = impl AsRef<str>>) {
    // Выводим пустую строку.
    println!();

    // Для каждой заметки в заметках ...
    for note in notes {
        // выводим её на экран.
        println!("{}", note.as_ref())
    }
}

за счёт чего будет ещё большая гибкость:

let mut notes = HashMap::new();
notes.insert("a", "x");
notes.insert("b", "y");
show_notes(notes.keys());
show_notes(notes.values());

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

Если интересен вопрос производительности, то, де факто, компилятор выполнит мономорфизацию, а именно создаст реализацию метода под каждый использующийся с ним тип (не каждый потенциально доступный, а именно каждый, который реально используется в программе), то есть, фактически, для оригинального варианта там будет всё тот же &Vec<String>, для других &[String], &Vec<&str> и так далее.

Так ведь, в данном случае, это ещё больший костыль, который внешнему пользователю (=тому, кто структурой пользуется) даёт ложное чувство того, что поле действительно необязательно, а пользователю структуры всё так же оставляет необходимость каждое из этих полей проверять на отсутствие.

Не говоря о НЕтипобеопасности

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Работает в
Дата рождения
Зарегистрирован
Активность