1.83K subscribers
3.3K photos
132 videos
15 files
3.58K links
Блог со звёздочкой.

Много репостов, немножко программирования.

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
Forwarded from The After Times
Когда твоя программа в целом работает не так, как планировал, но свои функции выполняет
#meme
Я, конечно, понимаю, что это успело обойти все тематические каналы в телеге, но это всё-таки смешно
Forwarded from The After Times
#article #math #prog

Как вы считаете середину отрезка в числах с плавающей точкой? Скорее всего, неправильно. Разбор различных методов сделать это, вместе с доказательствами на ошибки вычислений. И да, 80-битные регистры в x86 опять всё портят.

https://hal.archives-ouvertes.fr/file/index/docid/576641/filename/computing-midpoint.pdf
#prog #rust #моё

Хозяйке на заметку

Иногда бывает ситуация, что у вас на руках есть некое значение, про которое известно, что его тип реализует определённый трейт, и вам нужно вызвать метод этого трейта, но есть небольшая проблема: метод не принимает Self (как, например, Default::default), а воспользоваться синтаксисом турборыбы нельзя, потому что тип не имеет имени, которое можно написать (скажем, если вы получили это значение из функции, возвращающей impl Trait). Как же поступить в подобном случае? Сделать метод на значении самому!

Для определённости сделаем подобный трейт и напишем для него реализацию:

trait WithTypeMethod {
fn method(arg: i32);
}

impl WithTypeMethod for () {
fn method(arg: i32) {
println!("Called `<() as WithTypeMethod>::method` with arg = {}", arg)
}
}


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

fn make_it() -> impl WithTypeMethod + Default {
()
}


Для того, чтобы решить нашу задачу, сделаем для WithTypeMethod extension trait с методом, принимающим &self, который просто вызывает Type::method с нужным аргументом:

trait LowerToInstance: WithTypeMethod {
fn instance_method(&self, arg: i32);
}

impl<T: WithTypeMethod> LowerToInstance for T {
fn instance_method(&self, arg: i32) {
T::method(arg)
}
}


Теперь воспользуемся этим:

let it = make_it();
LowerToInstance::instance_method(&it, 42);


Использование fully qualified синтаксиса для вызова метода тут критично: так мы удостоверяемся, что мы действительно вызываем метод для нужного типа, а не для, скажем, ссылки на этот тип. Из-за deref coercion следующий код тоже работает:

let it = make_it();
(&&&&&&&it).instance_method(18);
Блог*
Мне уже даже про Томаку шутить не хочется
#prog #rust #моё

Самое смешное, что это в итоге так и не работает.

В текущем виде каждый форматировщик имеет имеет три ассоциированных типа (Year, Month и Day), которые описывают, нужна ли этому форматировщику эта часть даты или нет — фактически, этими типами могут быть только Reqired и Optional. Главный метод для форматирования принимает на вход сам форматировщик, буфер и некий обобщённый провайдер, про который известно только то, что он даёт доступ к полям, нужным для форматировщика. В типах это выражается тем, что это обобщённый тип, удовлетворяющий трейту Satisfies<Self::Year, Self::Month, Self::Day>. Второй метод принимает на вход форматировщик, буфер и дату и имеют реализацию по умолчанию, которая создаёт провайдер данных из даты (именно в этот момент применяется оптимизация с вызовом month_day вместо month и day по отдельности) и вызывает на форматировщике первый метод.

Вся эта машинерия создавалась с расчётом на то, что из нескольких форматировщиков можно составить один. В этом случае провайдер создаётся лишь один раз и передаётся субформатировщикам, а требования к провайдеру являются объединением требований субформатировщиков. Именно с последним пунктом у меня возникли проблемы: rustc не понимал, что если провайдер удовлетворяет объединённым, более сильным условиям, то он автоматически удовлетворяет и более слабым требованиям субформатировщиков. Сейчас я понимаю, что мой метод объединения требований непрозрачный для компилятора и не сохраняет эту информацию. В общем, буду думать.

А, ну и, как вы можете видеть, даже если я решу эту задачу, решение, скорее всего, будет слишком сложным для применения на практике. В принципе, это не такая уж и большая потеря: обычно из даты форматируют все три главные компоненты, а в этом случае мой предыдущий метод не даёт дополнительного оверхеда.
Forwarded from Vlad Beskrovnyi
Очень извиняюсь за дикий оффтоп. Моего друга Александра Литреева задержали в Екатеринбурге. Вы очень поможете, если у вас есть квартира под сдачу в Екатеринбурге и вы можете оформить договор до 7 утра 24 февраля. Или у кого-то из ваших знакомых. Или если вы можете кинуть это в другие чаты. Если до 7 утра не найдем, то отъедет в СИЗО почти наверняка.

Если у вас есть варианты, пишите в личку мне или его адвокату @ArkChaplygin, пожалуйста.
Еще раз извиняюсь за оффтоп, запиню предыдущий пин обратно завтра
https://zona.media/news/2020/02/23/litreev
Forwarded from Антон
Народ, приведите, пожалуйста, ситуации, когда вам нужно было в Rust вернуть замыкание, но этого не получалось сделать прямолинейно из-за того, что каждое замыкание имеет свой тип (и приходилось боксить/оборачивать в Either/что-то ещё)
Блог*
Уже 150 подписчиков! 🎉 Хорошая скорость, однако
Уже 200! Прекрасно! 🎉 Спасибо, что читаете мой блог!
#prog #rust #моё

Rust, будучи достаточно низкоуровневым языком, тем не менее, даёт возможность использовать замыкания. Эти замыкания не требуют динамической аллокации памяти и могут размещаться на стеке. Вкупе с оптимизатором LLVM это увеличивает шансы на то, что в сгенерированном коде замыкания в явном виде вообще не окажутся. Тем не менее, есть и обратная сторона медали: тип замыкания зависит от того, какие значение оно захватывает, поэтому в Rust каждый литерал замыкания имеет свой собственный тип. Например, код вида let closures = [|x| x + 1, |x| x + 2]; не компилируется.

Один из паттернов, который можно встретить в коде на Rust — это возврат того или иного замыкания в зависимости от некоторого условия. Для обхода ситуации с уникальными типами замыкания есть как минимум два пути:
а) Поместить замыкание в кучу и вернуть указатель на него, а в качестве возвращаемого типа указать что-то вроде Box<dyn Fn(Foo, Bar) -> Baz>. Очевидный недостаток — падение производительности на ровном месте. Более того, выделение памяти в куче и частичное стирание типа препятствует работе оптимизатора.
б) Вернуть замыкание, обёрнутое в один из вариантов некоторого перечисления. Здесь страдает уже эргономика: перечисление приходится разворачивать на стороне вызывающего кода, даже если все варианты имеют одинаковую сигнатуру. С именами также проблема: непонятно, как называть перечисление и его варианты, а различные EitherN не прибавляют наглядности. Можно реализовать нужные Fn-трейты для перечисления самому, но на сегодняшний день этот вариант доступен лишь на nightly.

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

1) Одно и тоже выражение замыкания, но захват разных значений одного и того же типа в зависимости от некоторого условия. Пример:

let func = if some_condition {
|x| x + foo
} else {
|x| x + bar
};

Решение — явный вынос захватываемого значения в отдельную переменную:

let added = if some_condition { foo } else { bar };
let func = |x| x + added;

2) Захват одних и тех же значений, но разные используемые операции. Пример:

let some_var = ...;
let func = if some_condition {
|x| x + some_var
} else {
|x| x * some_var
};

Решение — захват самой операции:

let some_var = ...;
let func = |x| {
if some_condition {
x + some_var
} else {
x * some_var
}
};

Почему захватывается флаг, а не функция? Потому что это влияет на размер получаемого замыкания. В случае захвата функции компилятору ничего не остаётся, кроме как хранить адрес самой функции, который по размеру совпадает с usize, флаг же занимает лишь один байт. Наглядное доказательство.

3) Захват разных значений с разными операциями. Пример (спасибо, @psilon):

let lambda = match a.cmp(&b) {
Less => || x.do_stuff(),
Equal => || y.do_other_stuff(),
Greater => || z.do_stuff_too(),
};

Для этого случая я адаптировал вариант б), с той разницей, что разбор происходит внутри замыкания:

enum Either3<X, Y, Z> {
X(X),
Y(Y),
Z(Z),
}
// или просто
// use either::Either3;
use Either3::*;

let datum = match a.cmp(&b) {
Less => X(x),
Equal => Y(y),
Greater => Z(z),
};
let lambda = || match datum {
X(x) => x.do_stuff(),
Y(y) => y.do_other_stuff(),
Z(z) => z.do_stuff_too(),
};

Разумеется, я не охватил все возможные варианты использования замыканий, но я надеюсь, что это поможет вам меньше боксить в коде только для того, чтобы удовлетворить тайпчекеру.
👍1
Блог*
#prog #rust #моё Rust, будучи достаточно низкоуровневым языком, тем не менее, даёт возможность использовать замыкания. Эти замыкания не требуют динамической аллокации памяти и могут размещаться на стеке. Вкупе с оптимизатором LLVM это увеличивает шансы на…
Я рассмотрел только варианты, когда значение замыкания требуется лишь одно. А что делать, если замыканий требуется несколько, и при этом необходимо, чтобы у них был один тип? Конечно, с версии Rust 1.26.0 замыкания можно клонировать, но это не поможет, если от разных замыканий требуется разное поведение (технически это, конечно, можно обойти путём кастомной имплементации Clone, но это настолько грязный хак, что я его даже рассматривать не буду). С другой стороны, несмотря на то, что литералы замыканий имеют уникальные типы, типы их возвращаемых значений совпадают. В частности, вызов одного и того замыкания с разными аргументами возвращает значение одного и того же типа (в этом Rust отличается от C++, в котором, начиная с C++14, лямбда-функции могут быть полиморфными).

Посмотрим, как можно сделать массив в начале поста:

let make_closure = |added| {
move |x: i32| x + added
};
let closures = [make_closure(10), make_closure(20)];

Аналогичные "фабричные" функции (не обязательно замыкания) можно сделать и для остальных примеров. Естественно, в этом случае несколько страдает эргономика, поскольку ранее захваченные переменные приходится передавать явно, но динамическая аллокация всё же не требуется
#prog #rust #parsing #моё

При написании парсеров часто требуется отдельно отслеживать, когда мы находимся внутри строки, выделенной кавычками, а когда — вне её. На практике это выливается в то, что появляется цикл и отдельный флаг, который меняется в зависимости от рассматриваемого символа. Эта логика затёсывается где-то между остальными строками кода, внося лишнее состояние, мешая восприятию кода и повышая вероятность совершения ошибок при внесении изменений в код. Переделка цикла на итераторы/стримы — это известный способ повысить понятность кода, но в данном случае наличие внешнего состояния не даёт возможность прямолинейно провести эту трансформацию. К счастью, в Rust есть способ инкапсулировать это состояние так, чтобы оно не маячило перед глазами — и для этого нам даже не понадобится itertools. У трейта Iterator есть метод, который можно назвать "map с состоянием". Встречайте — Iterator::scan!

Вот как выглядит начало цепочки итераторов, которая отслеживает, находимся ли мы сейчас внутри строки или нет:
s.chars()
.scan(false, |in_string, ch| {
*in_string ^= ch == '"';
Some((ch, *in_string))
})
.some_other_adapter(...)

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

fn find_outside_string(s: &str, needle: char) -> Option<usize> {
// .chars() поменялось на .char_indices()
// для корректного отслеживания позиции.
s.char_indices()
.scan(false, |in_string, (i, ch)| {
*in_string ^= ch == '"';
Some((i, ch, *in_string))
})
.find_map(|(i, ch, in_string)| {
if ch == needle && !in_string {
Some(i)
} else {
None
}
})
}

И немного тестов:

assert_eq!(find_outside_string("Hey!", '!'), Some(3));
assert_eq!(find_outside_string(r#"Outside "Inside!" Outside!"#, '!'), Some(25));
assert_eq!(find_outside_string("Nope", '?'), None);

Как видите, весь код, относящийся непосредственно к отслеживанию строки, инкапсулирован в одном месте и не отвлекает на себя внимания.
Закончили ли мы? Не совсем: в практичных языках нам также требуется возможность экранировать кавычки, чтобы включать кавычки в строки. Обычно это приводит к ещё большему зашумлению кода парсера. Что ж, реализуем поддержку этого требования. Для того, чтобы не слишком усложнять код, будем считать, что кавычки экранируются обратным слешем (\), а сам слеш — двойным повтором символа (\\). Код поменяется не очень сильно: нужно добавить ещё один scan для отслеживания слешей:

s.chars()
.scan(false, |prev_was_slash, ch| {
let escaped = *prev_was_slash;
*prev_was_slash = ch == '\\';
Some((ch, escaped))
})
// если слеш экранирован, то второй символ
// не надо пропускать дальше
.filter_map(|(ch, escaped)| if ch == '\\' && escaped {
None
} else {
Some((ch, escaped))
})
.scan(false, |in_string, (ch, escaped)| {
// экранированые кавычки не переключают строку
if !escaped {
*in_string ^= ch == '"';
}
Some((ch, in_string))
.some_other_adapter(...)

Для демонстрации перепишем find_outside_string с поддержкой экранирования:

fn find_outside_string(s: &str, needle: char) -> Option<usize> {
s.char_indices()
.scan(false, |prev_was_slash, (i, ch)| {
let escaped = *prev_was_slash;
*prev_was_slash = ch == '\\';
Some((i, ch, escaped))
})
.filter_map(|(i, ch, escaped)| {
if ch == '\\' && escaped {
None
} else {
Some((i, ch, escaped))
}
})
.scan(false, |in_string, (i, ch, escaped)| {
if !escaped {
*in_string ^= ch == '"';
}
Some((i, ch, *in_string))
})
.find_map(|(i, ch, in_string)| {
if ch == needle && !in_string {
Some(i)
} else {
None
}
})
}

Проверим, что оно действительно работает:

let s = r#"An "Escaped \" quote" quote"#;
assert_eq!(find_outside_string(s, 'q'), Some(22));
// результат отличается от результата str::find
assert_ne!(find_outside_string(s, 'q'), s.find('q'));

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