ch
Feedback
Hardcore programmer

Hardcore programmer

前往频道在 Telegram

Продвинутые темы из программирования и computer science. Особенности различных языков программирования. Глубокое погружение в software engineering. Поддержать канал: https://www.donationalerts.com/r/hardcore_programmer

显示更多
2 093
订阅者
无数据24 小时
+17
-1230
帖子存档
Моковое собеседование Rust | Дмитрий Беляев, Wildberries — сегодня в 19:00 https://youtube.com/live/mUiy3TcvT_s Напоминаем, что уже сегодня состоится эфир, который включает в себя: 1️⃣Вопросы на собеседовании 2️⃣Live coding 3️⃣Разбор собеседования 4️⃣Q&A блок А вообще там целый фестиваль для Backend-разработчиков и предстоит много классных стримов: моковые собесы, тестовый скрининг, вебинар про поиск работы от крутых менторов из Яндекса, Вайлдберриз, Райффазен банка Скорее регистрируйтесь, чтобы стать зрителем или участником собеседования!

Мой личный топ 5 естественных стимуляторов мозга

🔝 Я буду вести моковое собеседование по Rust 🔝 16 мая в 19:00

Бесплатные моковые собеседования, разбор CV и помощь с поиском работы для бэкенд-разработчиков Знаете это чувство, когда вы т
Бесплатные моковые собеседования, разбор CV и помощь с поиском работы для бэкенд-разработчиков Знаете это чувство, когда вы тратите кучу времени на поиски работы, но вас нигде не берут и кажется, что крутая работа в IT доступна только избранным? Эту систему можно хакнуть! Для этого нужно: 1️⃣ Научиться правильно презентовать свой опыт в резюме и на собеседовании 2️⃣ Понять, какие вопросы задают рекрутеры на интервью и заранее подготовить ответы на них 3️⃣ Выписать вопросы, которые чаще всего задают на технических собеседованиях и потренироваться отвечать на них 4️⃣ Много практиковаться на реальных или тестовых собеседованиях. С этими задачами помогут наши партнеры — Solvery — сервис по подбору менторов из IT. На следующей неделе ребята проводят Backend Fest по трудоустройству! 🚀 Будут не только технические собеседования, но и моковый скрининг с рекрутером, а также разбор CV и вебинар по поиску работы в 2024 году. Их проведут действующие специалисты из Яндекса, Райффайзен Банка, Wildberries и других компаний. Расписание феста: 13.05 в 18:30 — Разбор CV 14.05 в 19:00 — Моковое собеседование по С++ 15.05 в 19:00 — Моковый скрининг с рекрутером 16.05 в 19:00 — Моковое собеседование по Rust 20.05 в 19:00 — Моковое собеседование по Python 21.05 в 19:00 — Моковое собеседование по Java 22.05 в 19:00 — Как искать работу в 2024 году? 23.05 в 19:00 — Моковое собеседование по С# Вы можете не только прийти зрителем, но и сами пройти тестовое собеседование и разбор резюме – а это очень полезный опыт ✅ Скорее переходите по ссылке и регистрируйтесь, чтобы получить напоминания об эфирах!

🔝 Я буду вести моковое собеседование по Rust 🔝 16 мая в 19:00

Подкаст с моими мыслями о такой вещи как мотивация, о том как она влияет на нашу деятельность + немного саморефлексии. После длительного перерыва возвращаюсь к работе над каналом. Будут некоторые изменения в контенте, за перерыв я многое переосмыслил, о чём так же говорю на записи.

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

Настало время для серии постов про сети, сетевые протоколы и сетевое программирование. А начнём мы с рассмотрения сетевой модели OSI. Работа сетей обеспечивается большим количеством различных протоколов, многие из которых работают поверх других протоколов. Модель OSI классифицирует сетевые протоколы по 7 уровням: 7. Application (прикладной, уровень приложений). Оперирует формализованными данными, формат которых задается каждым конкретным протоколом. Обеспечивает общение приложений между собой. Примеры: HTTP, FTP, WebSocket, SMTP, DNS 6. Presentation (представление). Оперирует абстрактными данными (наборами байт). Обеспечивает функции преобразования данных (шифрование, сжатие). Примеры: TLS, SSL 5. Session (сессии, сеансы связи). Оперирует абстрактными данными + мета данными обеспечивающими постоянные соединения и аутентификацию соединения. Примеры: TLS (частично), TCP (частично), PAP, L2TP, OpenVPN, WireGuard 4. Transport (транспортный). Оперирует сегментами или датаграммами. Обеспечивает передачу данных на логическом уровне. Примеры: TCP, UDP 3. Network (сетевой). Оперирует пакетами. Обеспечивает логическую адресацию (ip адрес, порт) и маршрутизацию. Примеры: IPv4, IPv6, ICMP 2. Link (канальный). Оперирует кадрами. Обеспечивает физическую адресацию (mac адрес). Примеры: Ethernet, IEEE 802.11 (WiFI), LTE (мобильные сети), PPP, DSL 1. Physical (физический). Оперирует отдельными битами. Обеспечивает передачу данных по физическим каналам связи. Примеры: RJ (витая пара), SFP (оптоволокно), USB Согласно данной модели любые данные проходят все уровни от 7 до 1 при отправке данных и от 1 до 7 при получении. Каждый протокол должен взаимодействовать только с протоколами своего и соседних уровней, что на практике не всегда соблюдается. Так же при передаче данных через множество сетей (что просто необходимо в том же internet) данные могут многократно переходить между уровнями 1, 2 и 3 (маршрутизация), а иногда и между более высокими уровнями (например при балансировке). Кроме того, технологии виртуальных сетей (например VLAN или VPN) могут делать программную эмуляцию более низких уровней фактически передавая данные на более высоких. А границы между уровнями 4, 5, 6 и 7 очень часто оказываются размытыми, например протокол HTTP, находящийся на 7 уровне, может работать прямо поверх протокола TCP, находящегося на 4 уровне. С уровнями 4, 5, 6 и 7 чаще всего взаимодействует прикладное программное обеспечение. В системной разработке можно столкнуться с уровнями 3 и 4. Но и на уровне 2 так же применяются программные решения при разработке прошивок для сетевых устройств. И лишь уровень 1 является полностью аппаратным. Модель OSI не идеальна, но она позволяет соотнести практически каждый сетевой протокол с определенным уровнем модели, что даёт первичное представление о протоколе и его назначении.

В стриме, где я разбирался с языком zig, я упомянул такой термин, как алгебраический тип данных (algebraic data type, ADT). Давайте разберемся, что это такое и зачем нужно. ADT появились в функциональных языках, таких как Haskell или OCaml, но встречаются и в языках ориентированных на другие парадигмы, например в Rust, Zig и TypeScript. ADT представляют собой тип-сумму, объединяющий в себе данные разных типов по принципу ИЛИ, что позволяет хранить в одной переменной данные разных типов, но при этом типобезопасно их получать. Основным способом извлечения данных из ADT является pattern matching (сопоставление с образцом), который позволяет проверить, какой вариант данных сейчас находится в переменной с ADT типом. Давайте посмотрим примеры на различных языках. Пусть у нас есть типы Cat и Dog, а мы хотим объединить их в ADT типе Pet. На Haskell объявление будет выглядеть так:
data Pet = PetCat Cat | PetDog Dog
А на Rust так:
enum Pet {
    Cat(Cat),
    Dog(Dog),
}
А вот так на TypeScript:
type Pet = {
  kind: 'Cat';
  cat: Cat;
} | {
  kind: 'Dog';
  dog: Dog;
};
Имея переменную такого типа Pet мы можем однозначно определять, что внутри - Cat или Dog и выполнять в зависимости от этого разную логику:
f :: Pet -> IO ()
f (PetCat _) = print "Cat"
f (PetDog _) = print "Dog"
match pet {
    Pet::Cat(cat) => println!("Cat: {:?}", cat),
    Pet::Dog(dog) => println!("Dog: {:?}", dog),
}
switch (pet.kind) {
  case 'Cat':
    console.log('Cat', pet.cat);
    break;
  case 'Dog':
    console.log('Dog', pet.dog);
    break;
}
ADT - это достаточно мощный инструмент в тех системах типов и языках, где он есть, позволяющий оперировать данными разных типов в рамках одной сущности, при этом сохраняя безопасность с точки зрения типов.

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

Где-то Новый Год уже наступил, а где-то только на подходе. В новом году я желаю своим подписчикам счастья, благополучия, мира во всем мире, высоких зарплат и карьерных достижений, прибыльных инвестиционных портфелей! Продолжайте развиваться и будьте себе на уме. 🥂🍾☃️❄️🎉

Обещанная статья про систему типов в TypeScript. В ней подробный разбор как данная система типов работает.

Сегодня анонсировали Rust 1.75 Обновиться можно будет на следующей неделе. Главной фишкой новой версии станет поддержка синтаксиса impl Trait в качестве возвращаемого типа функций объявленных в трейтах:
trait MyTrait {
    fn some_function() -> impl SomeTrait;
}

impl MyTrait for MyType {
    fn some_function() -> impl SomeTrait {
        todo!();
    }
}
Ну а так как async fn - это по факту сахар над функциями, возвращающими impl Future, async fn так же будут доступны нативно для трейтов:
async fn f() -> i32 { 10 }
// рассахаривается в:
fn f() -> impl Future<Output = i32> { async { 10 } }
// что позволяет теперь делать так:
trait MyTrait {
    async fn f();
}
Анонс конечно описывает некоторые нюансы, но развитие языка - это всегда хорошо. В следующем году ожидается новая редакция языка (они выходят раз в 3 года), вероятно разработчики языка Rust ещё не раз порадуют нас стабилизацией полезных фич в ближайшие месяцы. Последние пару недель у меня не хватало времени на контент, надеюсь вы простите мне такое затишье. Но всё же и я сделаю пару анонсов: - В процессе большая статья про систему типов в TypeScript, надеюсь дописать и опубликовать на этих выходных. - В процессе сценарий для видео, где расскажу зачем разработчику изучать несколько языков, планирую снять и выпустить его до конца года. - 4, 5 и 6 января хочу на пробу провести стримы на ютубе, ориентируйтесь на старт в 19:00 мск, будем общаться + буду кодить небольшие штуки с нуля в прямом эфире, а если на первых двух стримах будет хотя бы по 50 зрителей, то на третьем возьму новый для себя ЯП и покажу как можно быстро разобраться в новом языке.

При использовании статической типизации типы всех переменных должны быть известны на этапе компиляции. Однако явное указание типов для всех переменных может утомлять и отвлекать от решаемой задачи. Решением этой проблемы стал вывод типов - способность компилятора вычислить тип для переменной из контекста, в котором эта переменная используется, а значит допустимостью для программиста не указывать такие типы явно. В этом посте поговорим про вывод типов. Простейшим способом вывода типов является задание переменной того же типа, что и тип выражения, которым данная переменная инициализирована. На этом принципе работает auto в C++ и var в Java/C#. На нём же работает вывод типов в TypeScript и некоторых других языках.
auto v = 5 * 2; // выведется int
Литералы 5 и 2 имеют тип int, operator*(int, int) возвращает int - результирующий тип всего выражения, а значит и тип переменной v, которая инициализирована этим выражением. Такой подход позволяет так же выводить типы для дженериков/шаблонов из выражений переданных в аргументы функции.
function foo<T>(arg: T): void;
foo(10); // T выведется в number
foo('hello'); // T выведется в string
Хотя такой способ достаточно просто реализуется в компиляторе, но у него есть недостатки. Нельзя вывести тип для переменных, которые инициализируются не в момент их объявления. А в сложных системах типов такой вывод может преподносить сюрпризы:
let array = []; // выведется never[]
array.push(10); // ошибка типов
Более продвинутым способом вывода типов является алгоритм Хиндли-Милнера. В данном способе вывод типов анализирует функцию целиком, строя на её основе систему уравнений, неизвестными в которой являются типы. Затем алгоритм за несколько шагов пытается разрешить эту систему, постепенно замещая неизвестные конкретными типами, вычисленные способом похожим на предыдущий. Вычисления останавливаются, когда больше ничего невозможно вычислить, в результате чего получаются или полностью конкретные типы или могут остаться обобщенные типы. В случае чистых функций, которые по сути являются выражением из аргументов функции в ее возвращаемое значение оставшиеся обобщенные типы не представляют большой проблемы, мы просто получим обобщенную функцию (дженерик). Данный алгоритм применяется в языках семейства ML (Haskell, OCaml), где он может выводить даже сигнатуру для функции, пусть и зачастую в обобщенном виде. Однако и данный алгоритм не всесилен, бывает что для некоторых переменных удается вывести лишь обобщенный тип, и если тип данной переменной не получится однозначно связать с типами в сигнатуре, то и не получится вывести обобщенную сигнатуру функции. Обычно компиляторы в этом случае требуют указать тип явно:
let v = "10".parse(); // ошибка компиляции
println!("{:?}", v);
Метод parse у строк в Rust является дженериком по возвращаемому значению (сигнатура fn parse<T>(&self): T), из контекста не понятен тип переменной v и нужно либо указать его явно либо явно задать дженерик у parse.
let v: i32 = "10".parse(); // ok
let v = "10".parse::<i32>(); // ok
Так же для такого вывода типов может стать большой проблемой неявная типизация. Если типы могут неявно кастоваться в другие, то может возникнуть неоднозначность в местах где одна и та же переменная используется в выражениях, требующих разные типы, непонятно какой из типов считать предпочтительным. Поэтому языки использующие вывод типов по Хиндли-Милнеру как правило стремятся к явной типизации. Еще одной проблемой для данного вывода типов может стать мощная система обобщений (дженериков) внутри системы типов. Rust например обходит это ограничение требованием явного указания типов в сигнатуре функций (и явного объявления дженериков). Вывод типов - достаточно мощный инструмент современных языков, сильно упрощающий написание кода. Он так же может давать некоторые дополнительные возможности, вроде анонимных типов, которые без вывода типов были бы просто невозможны.

Полиморфизм - какая первая ассоциация всплывает у вас, когда вы слышите это понятие? Вопрос может показаться странным, если не знать на сколько полиморфизм многогранен. Полиморфизм - он не един, его есть несколько видов, многие языки могут включать в себя сразу несколько таких видов. Хотя все эти виды все же имеют одну общую черту - связь с системой типов, а если точнее любой полиморфизм дает возможность иметь сущности, работающие не с конкретными типами, а с некоторым множеством типов. Прежде чем рассмотреть основные виды полиморфизма, стоит упомянуть одну важную характеристику любого вида полиморфизма - полиморфизм бывает статическим и динамическим. При статическом полиморфизме полиморфные сущности разрешаются в конкретные (обладающие конкретными типами) в compiletime. При динамическом - сущности остаются полиморфными после компиляции, а разрешаются уже в runtime за счет метаинформации о типах (например с помощью таблиц виртуальных функций или средствами рефлексии). Полиморфизм подтипов Данный вид возникает, когда система типов поддерживает возможность выстраивать типы в иерархии. При этом типы стоящие ниже по иерархии по отношению к типам стоящим выше называют подтипами. Основная идея тут в том, что сущность представленная подтипом так же представлена любым его надтипом (типом стоящим по иерархии выше). Этот вид полиморфизма часто ассоциируют с ООП (с самым распространенным на сегодня его видом, смотри видео про парадигмы если непонятно о чем речь). Иерархия здесь образуется при наследовании классов (наследник является подтипом базового класса) и при имплементации интерфейсов (имплементатор является подтипом интерфейса). Однако иерархия может появляться и в других случаях, яркий пример тут система типов в TypeScript, в которой все типы (за исключением разве типа any) выстроены в одну огромную иерархию. Параметрический полиморфизм Данный вид дает возможность сущностям языка принимать типы в качестве параметров. Полиморфные сущности в данном виде имеют возможность работать с типами обобщенно. Самый распространенный представитель параметрического полиморфизма - это дженерики. Вершиной параметрического полиморфизма являются типы высших порядков (HKT - Higher-Kinded Types), при поддержке которых возможно частичное применение параметров типа, а так же возможность передачи частичных типов в качестве параметров. Всю мощь HKT позволяет оценить язык Haskell. Ad-hoc полиморфизм Данный вид позволяет сущностям иметь несколько реализаций, при этом конкретная реализация определяется на основе типов в составе сущности. Самым ярким представителем тут являются перегрузки функций, когда мы имеем несколько реализаций для функции, а конкретная реализация выбирается на основе типов аргументов. Важное уточнение, перегрузки в TypeScript относятся к параметрическому полиморфизму, так как у всех перегрузок будет единая реализация. Сюда же можно отнести специализацию шаблонов в C++. Шаблоны в C++ одновременно являются представителем как параметрического так и ad-hoc полиморфизма. Утиная типизация Так же является видом полиморфизма, в котором полиморфные сущности определяются по наличию у их типа некоторого набора свойств (например наличия набора методов с определенными сигнатурами). Яркий представитель - интерфейсы в языке Go. Так же условно к этому виду можно отнести все системы типов основанные на структурной типизации, условно так как чаще всего структурная типизация использует полиморфизм подтипов. В завершении хочу упомянуть динамическую типизацию, в которой все сущности являются полиморфными по самой природе таких систем типов.

Хочу собрать немного статистики. Сколько языков программирования вы знаете (на уровне не меньше чем "могу что-то написать для себя")?
Anonymous voting

Иногда система типов может предложить интересные решения дающие больше контроля над типизированными сущностями. В этом посте я хочу рассмотреть 2 таких решения, которые сосредоточены на управлении ресурсами посредством системы типов, а именно линейных и аффинных типах. Линейные типы описывают объекты, которые должны быть использованы строго 1 раз. Это означает, что получив сущность линейного типа мы обязаны ей воспользоваться, а воспользовавшись однажды сделать это второй раз не выйдет. Если же нарушить эти правила, то программа не пройдет проверку типов. Попробовать линейные типы можно в языках Haskell (пока что включив расширение компилятора) и Idris 2. Аффинные типы описывают объекты, которые могут быть использованы не более одного раза. Отличие от линейных типов здесь в том, что у нас нет обязательства использовать сущность аффинного типа. На аффинных типах построена концепция владения в языке Rust. Все типы в Rust являются аффинными по умолчанию, при передаче по значению используется move семантика, а корректность ее использования (невозможность использовать перемещенное значение) возлагается на проверку типов. Однако в Rust есть 2 послабления касательно аффинных типов: - тип может быть промаркирован трейтом Copy, что отключает для него move семантику (при передаче по значению происходит копирование) и делает такой тип обычным; - дополнительно вводится концепция заимствования через ссылки, что позволяет обращаться к сущности многократно, правда с рядом ограничений. В целом линейные и аффинные типы позволяют языку вводить дополнительные гарантии управления ресурсами на уровне системы типов. Линейные типы так же дают дополнительную гарантию освобождения ресурсов, тогда как в случае аффинных типов такой гарантии нет, хотя она и может быть переложена на другие механизмы языка (Rust например дает такую гарантию за счет идиомы RAII).