Hardcore programmer
1.99K subscribers
9 photos
3 videos
22 links
Продвинутые темы из программирования и computer science. Особенности различных языков программирования. Глубокое погружение в software engineering.

Поддержать канал:
https://www.donationalerts.com/r/hardcore_programmer
Download Telegram
При использовании статической типизации типы всех переменных должны быть известны на этапе компиляции. Однако явное указание типов для всех переменных может утомлять и отвлекать от решаемой задачи. Решением этой проблемы стал вывод типов - способность компилятора вычислить тип для переменной из контекста, в котором эта переменная используется, а значит допустимостью для программиста не указывать такие типы явно.
В этом посте поговорим про вывод типов.

Простейшим способом вывода типов является задание переменной того же типа, что и тип выражения, которым данная переменная инициализирована. На этом принципе работает 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 например обходит это ограничение требованием явного указания типов в сигнатуре функций (и явного объявления дженериков).

Вывод типов - достаточно мощный инструмент современных языков, сильно упрощающий написание кода. Он так же может давать некоторые дополнительные возможности, вроде анонимных типов, которые без вывода типов были бы просто невозможны.
Сегодня анонсировали 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 зрителей, то на третьем возьму новый для себя ЯП и покажу как можно быстро разобраться в новом языке.
Где-то Новый Год уже наступил, а где-то только на подходе.
В новом году я желаю своим подписчикам счастья, благополучия, мира во всем мире, высоких зарплат и карьерных достижений, прибыльных инвестиционных портфелей!
Продолжайте развиваться и будьте себе на уме.
🥂🍾☃️❄️🎉
Сегодня в 19:00 мск будет ещё один стрим (запись останется, ссылку выложу примерно за пол часа).
В первой части поговорим про то как изучать языки программирования и зачем это нужно.
Во второй части попробую освоить новый для себя язык zig и написать на нём небольшую программку.
В стриме, где я разбирался с языком 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 - это достаточно мощный инструмент в тех системах типов и языках, где он есть, позволяющий оперировать данными разных типов в рамках одной сущности, при этом сохраняя безопасность с точки зрения типов.
Настало время для серии постов про сети, сетевые протоколы и сетевое программирование.
А начнём мы с рассмотрения сетевой модели 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 не идеальна, но она позволяет соотнести практически каждый сетевой протокол с определенным уровнем модели, что даёт первичное представление о протоколе и его назначении.
Всем привет, наконец-то нашёл время порадовать Вас постом, который неожиданно не влез в лимиты телеги и превратился в небольшую статью.

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

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

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

После длительного перерыва возвращаюсь к работе над каналом. Будут некоторые изменения в контенте, за перерыв я многое переосмыслил, о чём так же говорю на записи.
Forwarded from Tina
Бесплатные моковые собеседования, разбор 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 — Моковое собеседование по С#

Вы можете не только прийти зрителем, но и сами пройти тестовое собеседование и разбор резюме – а это очень полезный опыт

Скорее переходите по ссылке и регистрируйтесь, чтобы получить напоминания об эфирах!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔝 Я буду вести моковое собеседование по Rust 🔝

16 мая в 19:00
Please open Telegram to view this post
VIEW IN TELEGRAM
Media is too big
VIEW IN TELEGRAM
Мой личный топ 5 естественных стимуляторов мозга
Forwarded from Tina
Моковое собеседование Rust | Дмитрий Беляев, Wildberries — сегодня в 19:00
https://youtube.com/live/mUiy3TcvT_s

Напоминаем, что уже сегодня состоится эфир, который включает в себя:
1️⃣Вопросы на собеседовании
2️⃣Live coding
3️⃣Разбор собеседования
4️⃣Q&A блок

А вообще там целый фестиваль для Backend-разработчиков и предстоит много классных стримов: моковые собесы, тестовый скрининг, вебинар про поиск работы от крутых менторов из Яндекса, Вайлдберриз, Райффазен банка

Скорее регистрируйтесь, чтобы стать зрителем или участником собеседования!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔝 стартуем через 10 минут 🔝
Please open Telegram to view this post
VIEW IN TELEGRAM
Всем привет!

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

И чтобы вы пока не скучали, сейчас будет небольшой пост про выравнивание данных в памяти.

Ну а бонусом ловите скрин переписки с наглядным пособием как отшивать назойливых рекрутёров, так чтоб они хотели вас ещё больше
Выравнивание памяти - что это и зачем оно нужно?

Если ответить совсем кратко, то это размещение данных в памяти таким образом, чтобы они распологались по адресам кратным некоторой степени 2. Но что это даёт?

Как вам должно быть уже известно, процессоры оперируют блоками данных называемыми машинное слово, размер которого идентичен размеру регистров и разрядности самого процессора, то есть на 64 разрядном процессоре машинное слово будет занимать 64 бита (или 8 байт).

Но наши программы зачастую хотят оперировать типами меньшей размерности. Например многие программы работают со строками, которые по своей сути являются массивами отдельных байт, то есть нам нужно оперировать типом вроде char в языке C. Для процессора не является большой проблемой загрузить подобный тип на регистр, но сделает он это следующим образом:
1. загрузит машинное слово содержащие нужный нам байт,
2. побитовым сдвигом переместит его в 0 позицию,
3. побитовым И откинет лишние биты.
После этого мы можем применять к регистру любые операции, которые мы хотим применить к нашему байту. Проблем здесь нет никаких в каком бы адресе не был размещён наш char. Поэтому его выравнивание будет 1, что позволит разместить его в любом адресе.

Но теперь давайте посмотрим на тип int, который занимает 4 байта. Давайте для простоты представим, что он размещён по адресу 3. Вроде проблем пока тоже никаких, процессор применит все те же операции и на регистре окажется наш int. Но что если у нас массив этих int? Тогда следующий из них будет расположен по адресу 7 и будет пересекать границу машинного слова. Процессору уже придётся загрузить сразу 2 машинных слова (по адресам 0 и 8) на разные регистры, каждый из них очистить от лишних бит, выставить биты на нужные позиции, а затем объединить 2 регистра через побитовое ИЛИ. Процессоры Intel и AMD вполне справляются с подобной задачей, но затрачивают на неё гораздо больше операций. А вот процессоры с архитектурой ARM откажутся исполнять такое непотребство, по крайней мере в автоматическом режиме (никто не мешает нам то же самое проделать в собственном коде). Но гораздо эффективнее всегда размещать int только по адресам кратным 4, или по простому выравнивать по 4 байта.

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

struct example {
char a;
int b;
};

Поле a имеет выравнивание 1, а поле b - 4. Что бы это соблюдалось вся структура должна иметь выравнивание, как самое большое выравнивание её полей, то есть 4. Что в итоге приведёт к тому, что наш char так же будет выравнен по 4 байтам. При этом вся структура будет иметь размер 8 байт, а возле поля a окажутся 3 мусорных байта, которые никак не используются. Такие байты называют "паддинг". Это вынужденая трата памяти, которая обеспечивает нам быстродействие и работоспособность на разных платформах.

При проектировании структур имеет смысл учитывать эту особенность. К тому же стандарты языков C и C++ требуют, чтобы поля структуры были расположены в памяти в том же порядке, что и в коде. А вот компилятор языка Rust умеет переставлять поля местами ради оптимизации места занимаемого паддингами, что несомненно плюс, но требует более аккуратного подхода при низкоуровневых операциях с памятью структур. В крайних случаях ему можно указать, чтоб он обращался с конкретными типами так же как принято в C.
В комментах к последним постам несколько человек заинтересовались вакансиями Wildberries. Специально для вас узнал что есть сейчас:

Что такое Wildberries сейчас - это 30 млн пользователей ежедневно, сотни тысяч сотрудников и одна из сильнейших IT-команд в стране. У нас гибкий современный стек, около-rocket-science задачи прилагаются. Можно работать из любой локации🌎
Если ты back-end разработчик, у нас есть, что предложить для тебя в нашем департаменте Инфраструктура:

Rust Developer🔥

Что мы делаем на Rust:
Видеонаблюдение - мы обеспечиваем работу и развитие системы видеонаблюдения на складах и сортировочных центрах. Это сотни локаций, десятки тысяч камер, петабайты видеоданных.

FaceID - наша команда реализовала и развивает систему двухфакторной авторизации на проходных.

Рендер - отвечает за получение медиафайлов, их обработку и отправку в различные хранилища данных. Масштабы: сотни картинок в секунду на входе, тысячи на выходе, сотни тысяч RPS на отдачу.

Сервис отзывов - мы собираем, храним, ранжируем и отдаем пользователям отзывы по товарам. Масштаб системы: десятки тысяч запросов в секунду, более миллиарда отзывов, ежедневно добавляются миллионы новых отзывов.

Что для нас важно в тебе:
- Имеешь опыт разработки на Rust или желание его освоить.
- Имеешь большой опыт разработки и траблшутинга backend-сервисов.
- Хорошо знаешь, что такое highload.
- Отлично знаком с Linux.

Go Senior developer / Tech Lead (управление небольшой командой)🔥

BerryBasket продукт собственной разработки, написанный на GO. Представляет из себя высоконагруженное, шардированное, реплицированное, линейно масштабируемое файловое хранилище. Главные задачи проекта - хранение и защита персональных данных пользователей, простой и надежный канал для чтения/записи данных, легкое и прозрачное масштабирование.

Что для нас важно в тебе:
- Алгоритмы и структуры данных.
- Опыт коммерческой разработки на Golang от 4 лет.
- Опыт работы с БД, а также с брокерами сообщений.
- Опыт работы с Linux.

Что готовы предложить на всех позициях:
- Быстрый процессинг: 1 или 2 технические встречи, быстрый фидбек после интервью.
- Команда профессионалов, у которой можно учиться.
- Возможность работать из любой локации.
- Работа над интересными проектами, которые прокачают твои технические навыки и аналитическое мышление.
- Минимум бюрократии.
Всем привет!

Есть мысли в ближайшую субботу сделать стрим, ориентируемся на 19:00 мск

В программе:
- Ответы на ваши вопросы
- Напишем небольшую либу на Rust и опубликуем её на crates.io
- По изучаем исходники чегоо-нибудь из опенсорс

Свои вопросы к стриму можно задавать заранее в комментах под этим постом