Блог*
Как же теперь объединить всё это? Написать реализацию для кортежей форматировщиков, разумеется! Так как вариадических обобщённых типов в Rust нет, а руками писать реализации мне лень, напишем макрос: macro_rules! impl_for_tuples { () => {}; ($head:ident…
#моё #prog #rust
Так можем ли мы сделать лучше? Определённо, ведь у нашего решения есть ряд недостатков:
1. Захардкоженные символы для формирования отступов. Не всегда в начале требуются нули.
2. Захардкоженные ширины отступов. Мы не можем легко поменять их.
3. Потенциальная просадка по производительности. В документации к методу Date::month_day сказано, что использовать его эффективнее, чем доставать месяц и день по отдельности. Наш код этого не учитывает.
Третий пункт решается относительно просто: мы делаем у трейта параметр, который и является данными, из которых изымаются компоненты:
Для того, чтобы сделать форматировщик более настраиваемым, нам нужно разделить эту схему форматирования на отдельные части. Этими частями являются:
1. Функция, возвращающая "часть" даты, которая реализует
2. Ширина поля под отформатированные данные;
3. Символ для заполнения незанятой части поля.
Последнее, к сожалению, нельзя передать в стандартные форматирующие макросы параметрами, поэтому абстрагироваться от этого при помощи трейта не получится — чего не скажешь об остальных составляющих.
Выделение нужной части:
Так можем ли мы сделать лучше? Определённо, ведь у нашего решения есть ряд недостатков:
1. Захардкоженные символы для формирования отступов. Не всегда в начале требуются нули.
2. Захардкоженные ширины отступов. Мы не можем легко поменять их.
3. Потенциальная просадка по производительности. В документации к методу Date::month_day сказано, что использовать его эффективнее, чем доставать месяц и день по отдельности. Наш код этого не учитывает.
Третий пункт решается относительно просто: мы делаем у трейта параметр, который и является данными, из которых изымаются компоненты:
pub trait FormatDate<D> {Для удобства сделаем алиас на возвращаемый тип Date::as_ymd:
fn format_date(&self, b: &mut Buf, d: D) -> fmt::Result;
}
pub type Ymd = (i32, u8, u8);
. Если тип умеет форматировать дату в деконструированном виде (Ymd
), то он может форматировать и исходную дату:impl<T: FormatDate<Ymd>> FormatDate<Date> for T {С первыми двумя недочётами разобраться несколько сложнее. Посмотрим на то, как, по идее, выглядит реализация
fn format_date(&self, b: &mut Buf, date: Date) -> fmt::Result {
self.format_date(b, date.as_ymd())
}
}
FormatDate<Ymd>
:impl FormatDate<Ymd> for SomeFormatter {(справка по синтаксису форматных строк)
fn format_date(&self, b: &mut Buf, ymd: Ymd) -> fmt::Result {
let part = some_part_of_date(ymd);
write!(b, "{:0width$}", part, width = SOME_WIDTH)
}
}
Для того, чтобы сделать форматировщик более настраиваемым, нам нужно разделить эту схему форматирования на отдельные части. Этими частями являются:
1. Функция, возвращающая "часть" даты, которая реализует
Display
;2. Ширина поля под отформатированные данные;
3. Символ для заполнения незанятой части поля.
Последнее, к сожалению, нельзя передать в стандартные форматирующие макросы параметрами, поэтому абстрагироваться от этого при помощи трейта не получится — чего не скажешь об остальных составляющих.
Выделение нужной части:
pub trait Extract<Date = Ymd> {Пример реализации:
type Output: fmt::Display;
fn extract(ymd: Date) -> Self::Output;
}
pub struct DayOf;Немного более сложный пример:
impl Extract for DayOf {
type Output = u8;
fn extract((_year, _month, day): Ymd) -> u8 {
day
}
}
pub struct MonthFullWordOf;Ширина поля, по хорошему, должна быть константой, но параметризовывать типы значениями в Rust на stable пока нельзя. Не то, чтобы меня это остановило, но в данном случае проблемы решается достаточно просто и без const generics:
impl Extract for MonthFullWordOf {
type Output = &'static str;
fn extract((_year, month, _day): Ymd) -> &'static str {
match month {
1 => "January",
2 => "February",
3 => "March",
4 => "April",
5 => "May",
6 => "Juny",
7 => "July",
8 => "August",
9 => "September",
10 => "October",
11 => "November",
12 => "December",
// Здесь нормально паниковать, потому что месяц другие номера иметь не может.
// В типах это, к сожалению, не выражено.
_ => unreachable!(),
}
}
}
pub trait Width {Наш составной форматировщик в итоге выглядит следующим образом:
const WIDTH: usize;
}
// Пример реализации
pub struct W2;
impl Width for W2 {
const WIDTH: usize = 2;
}
pub struct Formatter<Extractor, Width, Padding = NoPad>(
std::marker::PhantomData<(Extractor, Width, Padding)>,
);
docs.rs
time::Date - Rust
API documentation for the Rust `Date` struct in crate `time`.
Так как абстрагироваться от символов заполнения толком нельзя, мы просто сделаем несколько несколько маркерных типов и напишем конкретные реализации
FormatDate
для Formatter
, параметризованных различными заполнениями:pub struct PadZeros;Итак, мы приобрели в модульности, но что мы потеряли? Удобство использования! Каждый конкретный форматировщик теперь содержит поле, поэтому проинициализировать его просто по имени уже не получится. К счастью, это обходится достаточно просто: достаточно завести константы с нужными именами:
pub struct NoPad;
impl<Extractor, W> FormatDate<Ymd> for Formatter<Extractor, W, PadZeros>
where
Extractor: Extract,
W: Width,
{
fn format_date(&self, b: &mut Buf, ymd: Ymd) -> fmt::Result {
let part = Extractor::extract(ymd);
write!(b, "{:01$}", part, W::WIDTH)
}
}
impl<Extractor, W> FormatDate<Ymd> for Formatter<Extractor, W, NoPad>
where
Extractor: Extract,
W: Width,
{
fn format_date(&self, b: &mut Buf, ymd: Ymd) -> fmt::Result {
let part = Extractor::extract(ymd);
write!(b, "{:1$}", part, W::WIDTH)
}
}
pub type Day = Formatter<DayOf, W2, PadZeros>;Проверим:
pub const DAY: Day = Formatter(std::marker::PhantomData);
// Аналогично с остальными форматировщиками
let d = date!(2020-02-18);Работает!
let format = (DAY, "/", MONTH, "/", YEAR);
assert_eq!(d.format_as(format), "18/02/2020");
Есть ещё один аспект, который мы можем улучшить: конструирование форматировщика из нескольких. Сейчас это сделано при помощи крайне уродливого и не расширяемого решения: реализации трейта для кортежей. Кортежи всей длины мы охватить не можем, а каждый новый impl добавляет времени к компиляции. Сейчас мы сделаем индуктивное решение: вместо того, чтобы пытаться объять необъятное, мы сделаем cons-список форматировщиков:
pub struct Nil;Разумеется, составлять подобный список руками куда менее удобно, чем кортеж — но мы и не будем! Вместо этого мы сделаем макрос, который будет конструировать список за нас. Мы несколько ужесточим требования к формату — теперь вместо произвольных выражений можно использовать лишь имена и литералы — но это упростит видимый синтаксис, потому что позволяет избавиться от запятых. Сам макрос:
pub struct Cons<T, U>(pub T, pub U);
impl FormatDate<Ymd> for Nil {
fn format_date(&self, _: &mut Buf, _: Ymd) -> fmt::Result {
Ok(())
}
}
impl<T, U> FormatDate<Ymd> for Cons<T, U>
where
T: FormatDate<Ymd>,
U: FormatDate<Ymd>,
{
fn format_date(&self, b: &mut Buf, ymd: Ymd) -> fmt::Result {
self.0.format_date(b, ymd)?;
self.1.format_date(b, ymd)?;
Ok(())
}
}
macro_rules! date_format {Как видите, ничего сложного тут нет 😈. Проверим, как это ведёт себя:
() => { Nil };
($name:ident $($tt:tt)*) => { Cons($name, date_format!($($tt)*)) };
($lit:literal $($tt:tt)*) => { Cons($lit, date_format!($($tt)*)) };
}
let d = date!(2020-02-18);К сожалению, писать имена вплотную после литералов нельзя, потому что это синтаксическая ошибка. В остальном — оно работает!
let format = date_format!(DAY" " MONTH_FULL", " YEAR);
assert_eq!(d.format_as(format), "18 February, 2020");
Можем ли мы теперь объявить торжество zero-cost абстракций? К сожалению, нет: форматировщик, использующий
Ymd
, заставляет вызывать Date::as_ymd
даже в том случае, если используется только одно из значений месяц или день — а переход на Ymd
был совершён именно по соображениям производительности! У меня есть идеи, как можно решить этот недостаток, но... Это потребует несколько более тяжёлой ти́повой наркомании, так что это материал для следующей статьи.Forwarded from мне не нравится реальность
Важный вопрос: прикручивать ли лайки?
(особенно с учётом того, что у канала вроде как есть чат)
(особенно с учётом того, что у канала вроде как есть чат)
Final Results
7%
Да
26%
Да, но только на свои посты
52%
Нет
14%
Кешбери
Блог*
pinned «Важный вопрос: прикручивать ли лайки?
(особенно с учётом того, что у канала вроде как есть чат)»
(особенно с учётом того, что у канала вроде как есть чат)»
Предыдущий пин — не менее важный опрос
Telegram
Блог*
Ради чего вы подписаны на канал?
Мемы / Типострадания на расте / Разбор теоретических концепций (вроде аффинных типов) / Ссылки на (научно-) популярные статьи / Ссылки на статьи по CS / Рекомендации видеоигр / Другое (в @decltype_chat_ptr_t)
Мемы / Типострадания на расте / Разбор теоретических концепций (вроде аффинных типов) / Ссылки на (научно-) популярные статьи / Ссылки на статьи по CS / Рекомендации видеоигр / Другое (в @decltype_chat_ptr_t)
Forwarded from The After Times
Когда твоя программа в целом работает не так, как планировал, но свои функции выполняет
#article #math #prog
Как вы считаете середину отрезка в числах с плавающей точкой? Скорее всего, неправильно. Разбор различных методов сделать это, вместе с доказательствами на ошибки вычислений. И да, 80-битные регистры в x86 опять всё портят.
https://hal.archives-ouvertes.fr/file/index/docid/576641/filename/computing-midpoint.pdf
Как вы считаете середину отрезка в числах с плавающей точкой? Скорее всего, неправильно. Разбор различных методов сделать это, вместе с доказательствами на ошибки вычислений. И да, 80-битные регистры в x86 опять всё портят.
https://hal.archives-ouvertes.fr/file/index/docid/576641/filename/computing-midpoint.pdf
#prog #rust #моё
Хозяйке на заметку
Иногда бывает ситуация, что у вас на руках есть некое значение, про которое известно, что его тип реализует определённый трейт, и вам нужно вызвать метод этого трейта, но есть небольшая проблема: метод не принимает
Для определённости сделаем подобный трейт и напишем для него реализацию:
И напишем функцию, для результата вызова которой мы не можем использовать турборыбу:
Для того, чтобы решить нашу задачу, сделаем для
Теперь воспользуемся этим:
Использование fully qualified синтаксиса для вызова метода тут критично: так мы удостоверяемся, что мы действительно вызываем метод для нужного типа, а не для, скажем, ссылки на этот тип. Из-за deref coercion следующий код тоже работает:
Хозяйке на заметку
Иногда бывает ситуация, что у вас на руках есть некое значение, про которое известно, что его тип реализует определённый трейт, и вам нужно вызвать метод этого трейта, но есть небольшая проблема: метод не принимает
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);
Блог*
Можем ли мы теперь объявить торжество zero-cost абстракций? К сожалению, нет: форматировщик, использующий Ymd, заставляет вызывать Date::as_ymd даже в том случае, если используется только одно из значений месяц или день — а переход на Ymd был совершён именно…
Мне уже даже про Томаку шутить не хочется
Блог*
Мне уже даже про Томаку шутить не хочется
#prog #rust #моё
Самое смешное, что это в итоге так и не работает.
В текущем виде каждый форматировщик имеет имеет три ассоциированных типа (
Вся эта машинерия создавалась с расчётом на то, что из нескольких форматировщиков можно составить один. В этом случае провайдер создаётся лишь один раз и передаётся субформатировщикам, а требования к провайдеру являются объединением требований субформатировщиков. Именно с последним пунктом у меня возникли проблемы: rustc не понимал, что если провайдер удовлетворяет объединённым, более сильным условиям, то он автоматически удовлетворяет и более слабым требованиям субформатировщиков. Сейчас я понимаю, что мой метод объединения требований непрозрачный для компилятора и не сохраняет эту информацию. В общем, буду думать.
А, ну и, как вы можете видеть, даже если я решу эту задачу, решение, скорее всего, будет слишком сложным для применения на практике. В принципе, это не такая уж и большая потеря: обычно из даты форматируют все три главные компоненты, а в этом случае мой предыдущий метод не даёт дополнительного оверхеда.
Самое смешное, что это в итоге так и не работает.
В текущем виде каждый форматировщик имеет имеет три ассоциированных типа (
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
Если у вас есть варианты, пишите в личку мне или его адвокату @ArkChaplygin, пожалуйста.
Еще раз извиняюсь за оффтоп, запиню предыдущий пин обратно завтра
https://zona.media/news/2020/02/23/litreev
Медиазона
В Екатеринбурге по подозрению в покупке экстази задержали IT‑специалиста и создателя «Красной кнопки» Александра Литреева
В Екатеринбурге по подозрению в покупке экстази задержали основателя компании по кибербезопасности Vee Security Россия ...
Vlad Beskrovnyi
Очень извиняюсь за дикий оффтоп. Моего друга Александра Литреева задержали в Екатеринбурге. Вы очень поможете, если у вас есть квартира под сдачу в Екатеринбурге и вы можете оформить договор до 7 утра 24 февраля. Или у кого-то из ваших знакомых. Или если вы…
Немного контекста: https://zona.media/news/2020/02/23/litreev
Медиазона
В Екатеринбурге по подозрению в покупке экстази задержали IT‑специалиста и создателя «Красной кнопки» Александра Литреева
В Екатеринбурге по подозрению в покупке экстази задержали основателя компании по кибербезопасности Vee Security Россия ...