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

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

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
Forwarded from <илья as Человек> (ilya sheprut)
Девочки, переписываем программы на японский 💅
Окей, продолжения не было слишком долго. Напишу завтра
Разработчик СУБД не выкидывает мусор, а помечает его как удалённый
Блог*
Ох, постараюсь уложить это в голове. Есть ещё что-то, что нужно знать о замыканиях? Да. Иногда требуется одно и тоже замыкание передать в качестве аргумента в несколько функций. Клонировать в этом случае не получится, потому что замыкание является клонируемым…
#prog #rust #моё

Хроники замыканий

Есть ещё кое-что, что можно сказать про замыкания.

Пожалуй, первое, с чего надо начать — так это с того, что, несмотря на то, что замыкания — это автоматически генерируемые структуры, реализующие определённые трейты, реализовать их самому для своих типов нельзя — по крайней мере, на стабильной версии Rust. Для того, чтобы определить один из Fn*-трейтов для своего типа, нужно активировать две фичи. Первая — fn_traits — позволяет написать определение трейта в обычном виде, а не в засахаренном Fn(Foo) -> Bar, как обычно. Вторая — unboxed_closures — позволяет определять функции с ABI rust-call, которое имеют методы Fn*-трейтов. Если мы их используем, то мы можем сделать то, что невозможно на текущей стабильной версии Rust: перегрузку функции по количеству аргументов. Причина, по которой эти трейты не стабилизированы, видимо, состоит в том, что им в качестве ти́пового параметра требуется не произвольный тип, а кортеж типов аргументов. Это выглядит несколько неловко на фоне отсутствия вариадических обобщённых типов, особенно для функции от одного аргумента — его приходится заключать в одноместный кортеж.

Стоп, в Rust есть одноместный кортеж?!

Да. Он объявляется одинаково на уровне термов и на уровне типов: (x,)

Ужас какой. А что ещё можно сказать про замыкания?

Надо сказать, что замыкания являются, в сущности, обычными типами, которые отличаются в основном анонимностью. И на них, в частности, нужно в некоторых ситуациях накладывать нужные ограничения. Рассмотрим в качестве примера функцию, которая принимает некоторый счётчик и возвращает замыкание, которое инкрементирует этот счётчик на каждый вызов. Для простоты пусть это будет просто u32. Кажется, что можно написать вот так:

fn make_incrementer(counter: &mut u32) -> impl FnMut() {
move || *counter += 1
}

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

А разве компилятор не может сам догадаться, как надо?

Нет. Компилятор проверяет правильность расстановки времён жизни, анализируя лишь сигнатуру функции, а не её тело.

Хорошо. Так как нам заставить этот код компилироваться?

Указать явно, что возвращаемое замыкание не может пережить захваченную ссылку. Мы можем сделать это, введя для времени жизни имя и приписав его ссылке и возвращаемому замыканию:

fn make_incrementer<'a>(counter: &'a mut u32) -> impl FnMut() + 'a { // ...

...или же мы можем побыть ленивыми и, последовав совету компилятора, навернуть анонимное время жизни:

fn make_incrementer(counter: &mut u32) -> impl FnMut() + '_ { // ...

Ясно. А для чего там move?

Это связано с тем, как замыкания в Rust работают с захваченными значениями (т. е. со значениями, объявленными снаружи самого замыкания). По умолчанию замыкания захватывают переменные по ссылке (то есть сгенерированный анонимный тип содержит ссылки на эти значения) — мутабельной или иммутабельной, в зависимости от того, как они используются внутри тела замыкания. Использование ключевого слова move переопределяет это поведение по умолчанию и заставляет замыкание захватывать переменные по значению (т. е. перемещает их в замыкание). Если его убрать, то make_counter не будет компилироваться: аргумент counter — фактически, локальная переменная — захватывается по ссылке, время жизни которой ограничено скоупом make_counter, поэтому вернуть такое замыкание за пределы порождающей его функции нельзя.

Так, стоп, так counter захватывается по ссылке или по значению?

В правильном или неправильном варианте?

...В правильном

По значению.

Но...

Просто в данном случае само захватываемое значение является ссылкой.

А, я понял. Кажется...

Ничего, это действительно поначалу немного сбивает с толку.

Что ж, это всё, что ты хотел рассказать про замыкания?

Всё? ВСЁ? АХАХХАХАХХАХХА

О_о
Блог*
Ох, постараюсь уложить это в голове. Есть ещё что-то, что нужно знать о замыканиях? Да. Иногда требуется одно и тоже замыкание передать в качестве аргумента в несколько функций. Клонировать в этом случае не получится, потому что замыкание является клонируемым…
Прошу прощения. В общем, я хотел рассказать про одну продвинутую фичу системы типов Rust...

Так, мне уже это не нравится

...Которая формально не связана с замыканиями, но фактически была введена ради них. Я говорю про higher-ranked trait bounds. В качестве демонстрационного примера я возьму несколько переработанный пример из rustonomicon. Пусть у нас есть структура, в которой лежат какие-то данные и замыкание, которое можно к эти данным применить:

struct Point {
x: u32,
y: u32,
z: u32,
}

struct Closure<F> {
data: Point,
func: F,
}

Попробуем написать для Closure метод, который применяет функцию func к point:

impl<F> Closure<F>
where
F: Fn(&Point) -> &u32,
{
fn apply(&self) -> &u32 {
(self.func)(&self.data)
// ^---- эти скобки необходимы, потому без них это будет означать
// вызов метода func
}
}

Пока вроде всё нормально, но мы знаем, что в данном случае Rust вывел времена жизни за нас, в том числе и в ограничении на F. Как это выглядит в развёрнутом виде? Попробуем написать:

impl<F> Closure<F>
where
F: Fn(&'??? Point) -> &'??? u32,
{
fn apply<'a>(&'a self) -> &'a u32 {
(self.func)(&self.data)
}
}

Непонятно, что писать вместо ???: тут должно быть какое-то время жизни, но мы не можем написать 'a, потому что это время жизни объявлено на методе, а не на impl-блоке целиком. Собственно, снаружи это время жизни 'a может быть, в сущности, любым, его выбирает вызывающий код. Нам нужно как-то выразить тот факт, что F реализует трейт функции с произвольным временем жизни, а не каким-то фиксированным жизни. Higher-ranked trait bounds aka HRTB (ограничение трейтов высшего ранга) позволяют выразить именно это:

impl<F> Closure<F>
where
F: for<'a> Fn(&'a Point) -> &'a u32
// далее без изменений

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

А почему первый пример не требовал этой страхолюдины?

На самом деле требовал, просто это очередной синтаксический сахар: для ограничений Fn*-трейтов Rust подставляет HRTB автоматически, при этом он следует обычным правилам lifetime elision.

А можно в for использовать тип, а не время жизни?

Увы, нет.

Ясно. А есть пример, когда этот синтаксический сахар ломается?

Ну, он ломается примерно тогда же, когда ломается lifetime elision. Скажем, если взять example_hof из вот этого моего поста и попробовать переписать его на Rust, то в лоб это сделать не удастся:

fn example_hof<F>(n: u32, s: &str, func: F) -> &str
where
F: FnOnce(&str, &str) -> &str,
{
let n = n.to_string();
func(&n, s)
}

На этот код ругается компилятор, причём примерно так же, как на функцию с сигнатурой fn(&str, &str) -> &str: здесь не хватает информации о том, как результат F зависит от её аргументов. Один из способов поправить этот код — переписать ограничение так:

F: for<'a> FnOnce(&str, &'a str) -> &'a str,

Но здесь вроде можно обойтись без это higher-ranked хренотени, добавив `'a` в число обобщённых параметров `example_hof`

Конечно. Да и лайфтаймы можно расставить по другому. Но в других, более сложных, случаях обойтись без HRTB невозможно.
Блог*
Ох, постараюсь уложить это в голове. Есть ещё что-то, что нужно знать о замыканиях? Да. Иногда требуется одно и тоже замыкание передать в качестве аргумента в несколько функций. Клонировать в этом случае не получится, потому что замыкание является клонируемым…
Так что же, эти HRTB ограничены одними лишь замыканиями?

Отнюдь. Например, в библиотеке serde есть трейт Deserialize<'de> и DeserializeOwned. Первый трейт параметризован временем жизни входных данных 'de, его реализуют типы, которые заимствуют из входа (скажем, строки) какие-то данные. Второй же, как следует из его названия, реализуется типами, которые не заимствуют данные из входа и являются владеющими. Очевидно, если тип реализует DeserializeOwned, то он реализует и Deserialize<'de>, причём для произвольного 'de. И действительно, DeserializeOwned объявлен следующим образом:

pub trait DeserializeOwned: for<'de> Deserialize<'de> { }

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

fn example_hof<F>(n: u32, s: &str, func: for<'a> fn(&str, &'a str) -> &'a str) -> &str;

Фух, это было довольно много. Это всё?

Да. Во всяком случае, пока что.

Спасибо, после общения с тобой я чувствую себя... Мудрее.

Что, правда?

Нет, конечно, лол

Ну и зараза
Блог*
Мне прилетел случай, когда моё решение давало ответ, на единицу отличающийся от правильного. Это выглядело странно, потому что это не могло быть эффектом ошибки на единицу — в противном случае более простые примеры давали бы ту же ошибку. Я внимательно изучил…
#prog #моё

НЕ ЧИТАЙТЕ, ЕСЛИ ВЫ ЕЩЁ НЕ РЕШАЛИ СЕГОДНЯШНЮЮ ЗАДАЧУ AUGUST LEETCODING CHALLENGE, НО ХОТИТЕ РЕШИТЬ ЕЁ САМИ

А вот в сегодняшней задаче union-find таки пригодилась. В задаче дан массив чисел, и требуется для графа, в котором вершинами являются числа из этого массива, а рёбра соединяют числа, не являющиеся взаимно простыми, найти размер наибольшей (по числу вершин) связной компоненты. Я, конечно, сначала сделал поиск в глубину, но тест на наличие ребра оказался довольно дорогим даже при предварительном разложении чисел на множители, что приводило к тому, что мои решения отваливались по таймауту на больших тесткейсах (100 000 чисел в диапазоне от 1 до 20 000). Объединение чисел по общему простому множителю в разложении дало куда более хорошую асимптотику и, как следствие, производительность. Единственное, на что нужно было обратить внимание при решении — это изменить процедуру union так, чтобы она возвращала размер вновь образованного множества (естественно, в этом случае нужно объединение по размеру, а не по рангу) и своевременно обновлять текущее максимальное значение.
Хочется найти новую работу, но на hh и linkedin предлагают работать администратором БД в Пятёрочке или двигать формочки на электроне за 2 косаря в месяц? Можешь зайти на @profunctor_jobs, там публикуют нормальные вакансии, которых больше нет нигде.

Пост проплачен обитателями Нибиру
Forwarded from rusta::mann
Опытный разработчик ударился в религию и создал не только образ докера, но и подобие
This media is not supported in your browser
VIEW IN TELEGRAM
Это я, привыкший лично приветствовать каждого нового подписчика, но лишённый возможности это делать из-за того, что кто-то пожаловался на спам
Раз уж тут так много новых людей — и особенно много тех, с кем я совершенно не знаком — пожалуй, стоит рассказать немного о себе и об этом канале.

Меня зовут Антон, я студент человек студенческого возраста и в настоящий момент я работаю программистом, по работе пишу в основном... Да, на Rust, а как вы догадались? Я люблю Rust и ненавижу примерно все остальные языки программирования. Круг моих интересов относительно широк, но на канал изливается в основном программирование (серьёзно, около половины постов с хештегом #prog). Этот канал изначально планировался как удобная свалка ссылок, материалов и #meme-ов, и... Он таковым, в сущности, и остался. В своё оправдание я могу сказать, что все статьи (выкладываемые с хештегом #article) я всегда читаю перед тем, как выложить, так что делюсь я тем, что считаю сто‌ящим своей аудитории.

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

Для удобства привожу список всех хештегов на канале (может быть пополнен в будущем):

#3dcg
#abnormalprogramming
#algo
#amazingopensource
#anime
#art
#article
#bash
#bio
#blogrecommendation
#c
#cinema
#clojure
#cpp
#csharp
#db
#demoscene
#design
#dotnet
#erlang
#game
#go
#idris
#itsec
#haskell
#js
#java
#julia
#justrustaceanthings
#kbd
#life
#math
#mechanics
#meme
#menacingopensource
#ml
#mood
#music
#outoflinestorage
#parsing
#performancetrap
#php
#pixelart
#politota
#postgresql
#prog
#psy
#puzzle
#python
#quotes
#regex
#retroit
#r
#rust
#rustasync
#rustforlinux
#rustreleasenotes
#rustlib
#scala
#science
#serde
#shell
#soc
#softskills
#sql
#successstory
#suckassstory
#tips
#typescript
#video
#web
#zig

#бомбёжкипост
#культурнаяпрограмма
#лингво
#моё
#право
#трудовыебудни

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

Добро пожаловать на канал!
👍8🤮1💩1🤡1
Блог*
Ради чего вы подписаны на канал?
Ну а как осмотритесь — пожалуйста, отметитесь в этом опросе (если что, там можно выбрать несколько вариантов)
Блог* pinned «Раз уж тут так много новых людей — и особенно много тех, с кем я совершенно не знаком — пожалуй, стоит рассказать немного о себе и об этом канале. Меня зовут Антон, я студент человек студенческого возраста и в настоящий момент я работаю программистом, по…»
This media is not supported in your browser
VIEW IN TELEGRAM