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 Россия ...
Блог*
Уже 150 подписчиков! 🎉 Хорошая скорость, однако
Уже 200! Прекрасно! 🎉 Спасибо, что читаете мой блог!
Блог*
Уже 200! Прекрасно! 🎉 Спасибо, что читаете мой блог!
This media is not supported in your browser
VIEW IN TELEGRAM
#prog #article #gamedev
Статья о том, как портировали на консоли игру, первоначально написанную на JavaScript. Спойлер: динамичность мешается.
habr.com/ru/post/489974/
Статья о том, как портировали на консоли игру, первоначально написанную на JavaScript. Спойлер: динамичность мешается.
habr.com/ru/post/489974/
Хабр
Kha vs HTML5: Компилируем JavaScript в C++
Предлагаю вашему вниманию перевод доклада Роберта Конрада с прошедшего в октябре прошлого года HaxeUp Sessions 2019 Linz. Данный доклад посвящен процессу портиро...
Блог*
#prog #article #gamedev Статья о том, как портировали на консоли игру, первоначально написанную на JavaScript. Спойлер: динамичность мешается. habr.com/ru/post/489974/
Фактически это пересказ вот этого выступления.
YouTube
Robert Konrad - Kha v HTML5
Slides for the talk: https://robdangero.us/haxeup_2019-10-25.pptx
#prog #rust #моё
Rust, будучи достаточно низкоуровневым языком, тем не менее, даёт возможность использовать замыкания. Эти замыкания не требуют динамической аллокации памяти и могут размещаться на стеке. Вкупе с оптимизатором LLVM это увеличивает шансы на то, что в сгенерированном коде замыкания в явном виде вообще не окажутся. Тем не менее, есть и обратная сторона медали: тип замыкания зависит от того, какие значение оно захватывает, поэтому в Rust каждый литерал замыкания имеет свой собственный тип. Например, код вида
Один из паттернов, который можно встретить в коде на Rust — это возврат того или иного замыкания в зависимости от некоторого условия. Для обхода ситуации с уникальными типами замыкания есть как минимум два пути:
а) Поместить замыкание в кучу и вернуть указатель на него, а в качестве возвращаемого типа указать что-то вроде
б) Вернуть замыкание, обёрнутое в один из вариантов некоторого перечисления. Здесь страдает уже эргономика: перечисление приходится разворачивать на стороне вызывающего кода, даже если все варианты имеют одинаковую сигнатуру. С именами также проблема: непонятно, как называть перечисление и его варианты, а различные
Сегодня я расскажу о том, как можно, переместив индирекцию внутрь самого замыкания, разобраться с некоторыми случаями этой проблемы. В качестве примеров я буду приводить код, который хотелось бы написать, но который не компилируется.
1) Одно и тоже выражение замыкания, но захват разных значений одного и того же типа в зависимости от некоторого условия. Пример:
3) Захват разных значений с разными операциями. Пример (спасибо, @psilon):
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)];
GitHub
rust/RELEASES.md at master · rust-lang/rust
Empowering everyone to build reliable and efficient software. - rust-lang/rust
#prog #rust #parsing #моё
При написании парсеров часто требуется отдельно отслеживать, когда мы находимся внутри строки, выделенной кавычками, а когда — вне её. На практике это выливается в то, что появляется цикл и отдельный флаг, который меняется в зависимости от рассматриваемого символа. Эта логика затёсывается где-то между остальными строками кода, внося лишнее состояние, мешая восприятию кода и повышая вероятность совершения ошибок при внесении изменений в код. Переделка цикла на итераторы/стримы — это известный способ повысить понятность кода, но в данном случае наличие внешнего состояния не даёт возможность прямолинейно провести эту трансформацию. К счастью, в Rust есть способ инкапсулировать это состояние так, чтобы оно не маячило перед глазами — и для этого нам даже не понадобится itertools. У трейта Iterator есть метод, который можно назвать "map с состоянием". Встречайте — Iterator::scan!
Вот как выглядит начало цепочки итераторов, которая отслеживает, находимся ли мы сейчас внутри строки или нет:
При написании парсеров часто требуется отдельно отслеживать, когда мы находимся внутри строки, выделенной кавычками, а когда — вне её. На практике это выливается в то, что появляется цикл и отдельный флаг, который меняется в зависимости от рассматриваемого символа. Эта логика затёсывается где-то между остальными строками кода, внося лишнее состояние, мешая восприятию кода и повышая вероятность совершения ошибок при внесении изменений в код. Переделка цикла на итераторы/стримы — это известный способ повысить понятность кода, но в данном случае наличие внешнего состояния не даёт возможность прямолинейно провести эту трансформацию. К счастью, в 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);
Как видите, весь код, относящийся непосредственно к отслеживанию строки, инкапсулирован в одном месте и не отвлекает на себя внимания.doc.rust-lang.org
Iterator in std::iter - Rust
A trait for dealing with iterators.
Закончили ли мы? Не совсем: в практичных языках нам также требуется возможность экранировать кавычки, чтобы включать кавычки в строки. Обычно это приводит к ещё большему зашумлению кода парсера. Что ж, реализуем поддержку этого требования. Для того, чтобы не слишком усложнять код, будем считать, что кавычки экранируются обратным слешем (\), а сам слеш — двойным повтором символа (\\). Код поменяется не очень сильно: нужно добавить ещё один 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'));
Замечательной особенностью этого паттерна является то, что он без особых усилий расширяется на экранирование других символов.