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

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

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
Тут Github выпустил бету официального консольного клиента.

Написан конечно же на go и поддерживает linux, Mac OS (есть в brew) и Windows.
Уже поддерживает создание пулл реквестов и issues, позже можно мониторить состояние github actions.

Страница проекта: https://cli.github.com/
Репозиторий: https://github.com/cli/cli
Статья в блоге Github: https://github.blog/2020-02-12-supercharge-your-command-line-experience-github-cli-is-now-in-beta/
#gamedev

Вы когда-нибудь хотели прогуляться по трёхмерному фракталу? Что ж, теперь вы можете.
Всё ещё в разработке, но выглядит потрясающе.

https://bananaft.itch.io/yedomaglobula
Блог*
Photo
Телеграм зашакалил фотку :/
В нормальном качестве можно посмотреть по ссылке
Forwarded from Fail Cascade
я кстати недавно видел вывеску SPA салон и подумал что это вебстудия
Нужно ли тестировать приватный функционал (предполагается, что у вас есть способ делать это без хаков и костылей)?
Anonymous Poll
66%
Да
20%
Нет
14%
Всё не так однозначно (в чат канала)
Блог*
Нужно ли тестировать приватный функционал (предполагается, что у вас есть способ делать это без хаков и костылей)?
Буду признателен, если вы поделитесь этим опросом. Я хочу узнать мнение людей помимо подписчиков каналов
Блог*
Нужно ли тестировать приватный функционал (предполагается, что у вас есть способ делать это без хаков и костылей)?
Забыл упомянуть: приватный функционал считается достаточно нетривиальным, чтобы его тестирование имело смысл. Ну, так, на всякий случай.
Если вы думаете, что я обленился и забыл про блог, то вы совершенно правы вот вам тизер завтрашней статьи:
#meme #моё #prog #rust

You vs the guy she told you not to worry about
#моё #prog #rust

В крейте time есть структура Date с методом format, который возвращает String со датой, отформатированной согласно переданному формату. Выглядит это примерно так (пример из документации):

assert_eq!(date!(2019-01-02).format("%Y-%m-%d"), "2019-01-02");

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

Как примерно должен выглядеть код? Нам нужно абстрагироваться от конкретных типов по операции "отформатируй сюда дату", чтобы из элементарных форматировщиков можно было собрать составной. В самой операции нам, очевидно, требуются: сам форматировщик, дата и буфер, куда будут записываться данные. Мы не хотим возвращать строку непосредственно, потому что это при объединении привело бы ко множеству ненужных мелких аллокаций. Также мы не хотим, чтобы форматировщики могли удалять из буфера данные, поэтому мы сделаем обёртку над строкой с более узким интерфейсом:

use std::fmt;

#[derive(Default)]
pub struct Buf {
inner: String,
}

impl Buf {
pub fn new() -> Self {
Self::default()
}

pub fn append(&mut self, s: &str) {
self.inner += s;
}

// Этот метод позволит нам использовать макрос `write!` на `Buf`.
// Он позволяет не безусловно выделять новую строку,
// а использовать место в уже имеющейся
pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
self.inner.write_fmt(args)
}
}

Сам общий интерфейс форматировщиков:

use std::fmt;
use time::Date;

pub trait FormatDate {
fn format_date(&self, b: &mut Buf, d: Date) -> fmt::Result;
}

(проблем с тем, чтобы принимать Date по значению, нет, потому что это Copy-тип).

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

impl FormatDate for &'_ str {
fn format_date(&self, b: &mut Buf, _: Date) -> fmt::Result {
b.append(self);
Ok(())
}
}

Теперь напишем реализацию для, скажем, дня даты:

pub struct DayOf;

impl FormatDate for DayOf {
fn format_date(&self, b: &mut Buf, d: Date) -> fmt::Result {
write!(b, "{:02}", d.day())
}
}

Формат "{:02}" означает, что для печати дня отведено два места, и если в номере дня всего одна цифра, то вывод будет дополнен слева нулями. Форматировщики для номера месяца и года пишутся аналогично, поэтому не будем подробнее на этом останавливаться.
Как же теперь объединить всё это? Написать реализацию для кортежей форматировщиков, разумеется! Так как вариадических обобщённых типов в Rust нет, а руками писать реализации мне лень, напишем макрос:

macro_rules! impl_for_tuples {
() => {};
($head:ident $(, $rest:ident)*) => {
impl<$head, $($rest),*> FormatDate for ($head, $($rest,)*)
where
$head: FormatDate,
$($rest: FormatDate,)*
{
fn format_date(&self, b: &mut Buf, date: Date) -> fmt::Result {
#[allow(non_snake_case)]
let &(ref $head, $(ref $rest,)*) = self;
$head.format_date(b, date)?;
$($rest.format_date(b, date)?;)*
Ok(())
}
}
impl_for_tuples!($($rest),*);
};
}

impl_for_tuples!(A, B, C, D, E, F, G, H);

Добавим для удобства extension trait:

pub trait DateExt {
fn format_into<F: FormatDate>(self, b: &mut Buf, f: F) -> fmt::Result;
fn format_as<F: FormatDate>(self, f: F) -> String;
}

impl DateExt for Date {
fn format_into<F: FormatDate>(self, b: &mut Buf, f: F) -> fmt::Result {
f.format_date(b, self)
}
fn format_as<F: FormatDate>(self, f: F) -> String {
let mut buf = Buf::default();
let _ = self.format_into(&mut buf, f);
buf.inner
}
}

И проверим, как работает:

use time::date;

// Очень удобный макрос
let d = date!(2020-02-18);
let format = (DayOf, "/", MonthOf, "/", YearOf);
// Обратите внимание на ноль впереди в месяце
assert_eq!(d.format_as(format), "18/02/2020");

Работает!
Продолжение следует...
Блог*
Как же теперь объединить всё это? Написать реализацию для кортежей форматировщиков, разумеется! Так как вариадических обобщённых типов в Rust нет, а руками писать реализации мне лень, напишем макрос: macro_rules! impl_for_tuples { () => {}; ($head:ident…
#моё #prog #rust

Так можем ли мы сделать лучше? Определённо, ведь у нашего решения есть ряд недостатков:
1. Захардкоженные символы для формирования отступов. Не всегда в начале требуются нули.
2. Захардкоженные ширины отступов. Мы не можем легко поменять их.
3. Потенциальная просадка по производительности. В документации к методу Date::month_day сказано, что использовать его эффективнее, чем доставать месяц и день по отдельности. Наш код этого не учитывает.

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

pub trait FormatDate<D> {
fn format_date(&self, b: &mut Buf, d: D) -> fmt::Result;
}

Для удобства сделаем алиас на возвращаемый тип Date::as_ymd: 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;
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!(),
}
}
}

Ширина поля, по хорошему, должна быть константой, но параметризовывать типы значениями в Rust на stable пока нельзя. Не то, чтобы меня это остановило, но в данном случае проблемы решается достаточно просто и без const generics:

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)>,
);
Так как абстрагироваться от символов заполнения толком нельзя, мы просто сделаем несколько несколько маркерных типов и напишем конкретные реализации 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");

К сожалению, писать имена вплотную после литералов нельзя, потому что это синтаксическая ошибка. В остальном — оно работает!