Мой уголок – Telegram
Мой уголок
273 subscribers
2 photos
28 links
Щит-репосты и посты о всяких технологиях и не только. @bondiano
Download Telegram
keyof в поисках типа

Жили были функции без типов. Среди них были такие,
как prop, возвращающие поля из объекта.


const prop = (obj, key) => obj[key]

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


const prop = (obj: {}, key: any) => obj[key]
const example = { meow: 'meow' }
prop(example, 'meow') // type: any


"Как так!", — восклицали они, — "Ведь мы видели, что по ключам всегда получаем тип!"

И были правы. В TypeScript существует поиск типов (Lookup Types) , который и позволяет получить связанный с ключем тип.

Тогда к ним пришел важный тип и дал новое магическое слово - keyof.

Оно давало особую способность проникать в публичные имена ключей типов и создавать литеральный объединенный тип (Union).*


const example = { meow: 'meow', murr: 'murr' }
type CatSays = keyof typeof example // "meow" | "murr"


Благодоря знанию о keyof и Lookup Types они быстро сообразили, как затипизировать их функцию prop:


const prop<T, K extends keyof T>(obj: T, key: K) => obj[key]

Теперь они смогут спокойно жить и припевать в типобезопасном мире, типизируя любые функции, которые как-то получают только нужные поля объекта!


* с помощью keyof можно получить все публичные не статические ключи пренадлежащие именно типу, потому в примере мы вынужденны получать запросом типа typeof.
#w_corner_ts
TSLint to ESLint

TSLint обещает скоро уже стать deprecated. И уже пора бы переносить все проекты с ним на новые рельсы. Очевидно, что единсвенный выбор сейчас - ESLint. Ну и куда же в современном проекте без Prettier'а. Пройдемся немного по шагам, которые сделал я для подобного переноса.

1. Во первых, не хотелось переформатировать проект, потому я сразу пошел искать тулзу, которая мне переконвертирует все мои правила из TSLint, и супер, что такая уже есть - tslint-to-eslint-config.

Запускаем npx tslint-to-eslint-config

2. Тулза оказалась достаточно не плохой. Но все нужные пакеты для использования ESLint она сама не поставила :(. Поставим сами, за одно добавим Prettier. (хотя и создатель сказал не использовать, но я продлжаю выбирать yarn в качестве своего менеджера зависимостей, как минимум тк я подсел на workspaces)

yarn add eslint eslint-config-prettier eslint-plugin-prettier prettier @typenoscript-eslint/parser @typenoscript-eslint/eslint-plugin @typenoscript-eslint/eslint-plugin-tslint -D


3. Перименуем eslintrc.js в .eslintrc.js, либо придётся добавить флаг в скрипте -c eslintrc.js.

4. Обновим поле noscripts в package.json.
"lint": "eslint --ignore-path .gitignore --ext .js,.jsx,.ts,.tsx"

5. Добавим Prettier. Сперва в массив plugins - plugins: [..., 'prettier'], затем в rules - rules: { 'prettier/prettier': 'error', ... }

6. Почему-то tslint-to-eslint-config генерирует несколько не сущесвующих правил в плагине @typenoscript-eslint/eslint-plugin, тч после первого запуска lint'a вам придется их подтереть или поискать альтернативы.

6. PROFIT! Можно удалять tslint и избавляться от правил из @typenoscript-eslint/tslint плагина.

Некоторые мои проекты хранятся в монорепо, тч для нормальной работы ESLint потребовалась небольшая докрутка: parserOptions.project в .eslintrc.js поддерживает также массив, тч потребовалось внести каждое приложение, содержащее tsconfig.json отдельно.

Также vscode сразу не хотел обрабатывать нужные конфиги, не беда, в issue к плагину vscode-eslint предложили рабочее решение.

P.S. Все же с tslint-to-eslint-config некоторые правила останутся не перенесеными, тч смотрим также созланный тулзой файл tslint-to-eslint-config.log.
#w_corner_ts
Второй подход к снаряду поиска типов

Сколько же решений по типизации экшенов выдает Stackoverflow, вплоть до пакетов с тремя звездочками на Github и каждый способ не лишен изъянов.
Возьмем например довольно очевидный способ создания экшена "в лоб":

interface Action<T, P> {
readonly type: T;
readonly payload?: P;
}

const createAction = <T extends string, P>(type: T) => (payload: P): Action<T, P> => {
return { type, payload };
}

В таком случае от нас TS будет требовать пробрасывать вторым аргументом в дженерик payload, что нам не всегда нужно (тк нельзя в дженерик с двумя аргументами передавать только один, ну и если попробуем от этого избавиться, то все равно в конце концов мы теряем тип от type).
И по общей договоренности с командой мы решили, что тип поля type обязательно должен быть строкой.

И так, приступим исправлять типизацию.

Что же для нас такое этот createAction в итоге.
Это функция, принимающая type и payload и возвращающая объект с этими двумя полями, а главное сохраняющая типы.

const createAction = (type) => (payload) => ({
type,
payload,
})

И первый тип, с которым надо нам надо разобраться - это тип Action, содержащий payload, type и более базовый тип - ReduxAction (для него достаточно поля type)

interface ReduxAction<T extends string> {
type: T
}

export interface Action<Type extends string, Payload = void> extends ReduxAction<Type> {
payload?: Payload
}

Тут все довольно просто. Обернем в это createAction.

const createAction = <T extends string, P>(type: T) => (payload?: P): Action<T, P> => ({
type,
payload,
})

Чтож, мы пришли примерно к тому же, что и было в примере с SO. Только payload вне зависимости от экшена не обязательный, а типы этого поля так и не выводятся. Добавим магии lookup types и посмотрим, что у нас получится.

export const createAction = <A extends Action<A['type'], A['payload']>>(type: A['type']) => (payload: A['payload']) => ({
type,
payload,
})

Теперь у нас выводится типы тайпсрипта по типу экшена. Так мы не можем доказать тайпчекеру, что тип у A['type'] - строка, и это не сработает (пример). Впрочем, мы и не пытались.
Чтобы это сделать мы воспользуемся таким вот хитрым хелпером, который позволит доказать, что тип действительно строка (если у вас есть идеи как это сделать проще - велком в личку).

type StingTypeField = {
type: string
}

type LookupStringTypeField<T, K> = K extends keyof T ? (T extends StingTypeField ? T[K] : never) : never

В LookupStringTypeField мы говорим, что поле в объекте точно есть поле K и оно точно строка, а иначе и не может быть (never).

Итоговый createAction:

const createAction = <A extends Action<LookupStringTypeField<A, 'type'>, A['payload']>>(type: A['type']) => (payload: A['payload']) => ({
type,
payload,
})

И конечно ссылка с примером.

P.S. В примерах не исключены ошибки ☺️

Всех с наступающим!🎄
#w_corner_ts
Небольшой пост по безопасности на фронтенде, куда вообще смотреть? Заметил, что мало кто в этой теме разбирается даже на базовом уровне, а тема важная.
Основные вопросы:
Виды атак, какой вектор, какая цель?
XSS, CSRF, clickjacking.
Как защититься?
CORS + SOP, CSP, Cookie: secure, httpOnly, SameSite
https://application.security/free-application-security-training - в интерактивном режиме показывается как можно произвести атаку и как от нее защититься
https://developer.mozilla.org/ru/docs/Web/HTTP/CORS
https://developer.mozilla.org/ru/docs/Web/HTTP/CSP
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
https://developer.mozilla.org/ru/docs/Web/HTTP/Cookies#secure_(%D0%B1%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D1%8B%D0%B5)_%D0%B8_httponly_cookies
https://habr.com/ru/post/340146/ - про то, как работает JWT
#w_corner_secure
MSW
Часто вам приходилось разрабатывать интерфейс без готовой ручки на бэкенде? Все эти временные данные на уровне стора или Promise.resolve с данными на уровне сервисов/sdk и прочие подходы, обеспечивающие нам одни и теже данные. А писать e2e тесты, когда чтобы замокать результат запросов всегда приходилось придумывать свои велосипеды (да-да, е2е с фейковыми данными запросами ужас и моветон - мы всегда поднимаем весь стек и ждём, что нигде ничего не отвалится и нашей машинке хватит сил).
Хватит. Почти всегда для того, чтобы подсунуть фэйковые данные на уровне сети я выбирал nock, но время идёт, индустрия движется, а подход к моку сервера в тестах остаётся прежним. Только не с Mock Service Worker и вот вам несколько причин перейти на него:
1. Простая установка
2. Возможность запускать одни и те же обработчики и для серверных (например,в случае с SSR) и для клиентских тестов из коробки
3. Возможность делать более умные обработчики на этапе жизни «без нужной бэкендовой ручки», а затем переиспользовать их в тестах
4. Поддержка rest и grqphql
5. Максимально прозрачное поведение и простота отладки
6. Ваши обработчики похожи на реальные express мидлвары
Ставим пакет, запускаем npx msw init public/ --save и вперед. Не забываем читать доку, и экспериментировать, думаю, обязательно найдете больше юзкейсов для себя.
#w_corner_testing
Да будет Typenoscript
С этого поста хочу начать последовательный цикл дипдайва в тайпскрипт, в самом широком смысле: от причин использования, простейших типов и их юзкейсов, до устройства системы типов, транспилятора, точек его расширения и всего вокруг этого чудесного инструмента.
Начнем с самого начала: с базовых типов, из которых выводится все остальное.
То, что TypeScript является типизированной надстройкой над JavaScript, от которой после компиляции не остаётся и следа, означает, что первый перенял от второго всю его идеологию. Одним из таких моментов является разделение типов данных на типы значения (примитивные) и ссылочные типы.
Корнем всей иерархии типов можно считать unknown, который является типобезопасным аналогом any. Все типы совместимы с типом unknown, в, то время как сам тип unknown совместим только с самим собой и типом any.
Произвольный тип any — с него для многих и начнется знакомство с системой типов TypeScript.
Далее идут простые примитивные типы, имеющие свои аналоги в JavaScript: number, string, boolean, bigint, symbol. Важно понимать, что типы, идентификаторы которых начинаются с прописной буквы представляют объектные типы (ссылочные типы) описывающие одноимённые типы из JavaScript (Number, String, Boolean и т.д.). В Typenoscript их можно расширить (через extends) и реализовать (через implements).
В отдельную категорию можно вынести примитивные типы null, undefined, void, never, считая их в некотором роде «типами отсутствия».
Enum - перечисление также считается примитивным типом.
Для каждого из этих типов есть литеральный подтип, которые, как можно понять из названия, представляют литералы обычных примитивных типов.
#w_corner_ts
React Testing Library и Enzyme
Все больше и больше проектов уходят с Enzyme и переходят на RTL. Про это сейчас пишут статьи, делают доклады и даже оффициальная дока Recat рекомендует React Testing Library. Чем же так не угодил Enzyme и почему от него стоит отказаться.
1. Он завязан на кишки React, а значит очередное обновление ломает обратную совместимость и требует обновление Enzyme (а также вероятно написанных тестов)
2. Enzyme родился в Airbnb, но сейчас фактически поддерживается одним человеком.
3. Enzyme периодически вынуждает тестировать реализацию как «белый ящик», что также делает тесты более хрупкими.
Как начать писать тесты с RTL? Лично я рекомендую для старта почитать документацию, посмотреть мой доклад и этот плейлист. Для более заинтересованных в тестах также не могу оставить без внимания интенсив Hexlet по тестированию фронтенда.
#w_corner_testing
Заметки

идеальная система не та, в которую уже нечего добавить, а та, из которой уже нечего убрать

За последние лет 8 я попробовал минимум 30 приложений для ведения заметок, дел, чеклистов, WTR, финансов и прочих потоков информации, которые хочу структурировать. 
А хотелось структурировать все. GTD для всех дел. Цеттелькастен для всех заметок. Выжать максимум из приложений для финансов. Еженедельная очистка want to read инбокса. 
Косу нужно было идеально заточить, чтобы косить. 
В результате вместо того, чтобы выполнять задуманную работу, куча времени уходило на поиск и освоение очередного разухабистого инструмента для GTD, обвес и настройка плагинами Obsidian’а, докрутка базы знаний в Notion. В конце концов полное отчаяние от того, что в итоге снова получилось слишком сложно. 
Тогда я решил зайти с противоположного конца. Завел просто файлик в облаке. 
В него писал короткий список с перечислением проектов, над которыми я фокусируюсь в текущую неделю. Сразу под этим списком вписывал все дни недели, а каждый вечер писал список из нескольких пунктов под следующим днем. Тоже самое сделал с финансами.
Через несколько недель такого упрощения я понял, какие именно элементы в моей системе продуктивности были лишние, избавился от них и смог вернуться в Things3.
Так же поступил с моим Obsidian. Создал новый сейф, избавился от всех лишних плагинов и сложных структур в заметках, оставил только самое необходимое.
В итоге пришло осознание, что для Zettelkasten'а достаточно папки с текстовыми файлами, куда я записываю сформировавшиеся мысли и иногда перечитываю их.
Для написания больших текстов вернулся к org-моду emacs.
Итогом пусть будет мысль, что практика - хорошо, а паралич выбора и FOMO - плохо. Надеюсь этот виток ведения дел окажется более удачным.
🔥3❤‍🔥1
Type Programming

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

Это достигается простейшим описанием объектного типа или интерфейса. Куда реже мы переопределяем стандартные типы, вроде чисел и превращаем их в брендированные типы - другой мощный механизм, ставший возможным благодаря широким возможностям системы типов.

Как правило на этих возможностях использование системы типов и заканчивается. В прикладном коде мы стараемся не описывать сложное взаимодействие бизнес сущностей через систему типов. Цена этого довольно велика, а выхлоп туманный.

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

Чтобы решить подобные вопросы в “классических ООП” языках были выделено множество паттернов, которые заставляют стелить соломку абсолютно везде - стратегия на интерфейсе абстрактного класса, не должно быть классов не имплементирующих какой-то интерфейс. И прочие прелестные Джава-чсв “из умных книжек” догмы ака паттерны. А все изначально от ограничений. Пока не завезли дженерики даже достаточно типобезопасной функции мап было не сделать. Првильно, стратегии. Привет, любители Го.

В языках с динамической типизацией отродясь таких проблем не было. Они не задают совершенно никаких ограничений. Действительно хорошие попытки задать подобные ограничения в динамических языках - контрактное программирование, но лично я встречал его только на слайдах. Реальных ограничений в них нет. Что сейчас и привело к различным гибридным подходам к статической системе типов.

На другой чаше весов замечательные языки для “небожителей” аля Хаскель, Идрисы и прочие Окамелы. Из более хайпящих сейчас - Раст. Ух сколько радостей и головоломок приносят типы для тех, кто пишет на них библиотеки. И сколько бы я их не пробовал везде одна проблема - система типов сильно проникает в бизнесовый код. Реальный мир вошел в радужное царство единорогов-математиков. Как итог вместо фабрик, синглтонов и прочего из банды четырех мы получаем монады, монады-трасформеры и прочие монады-состояния. (тут важно разделить ФП и программирование на типах - не путаем)

И тут у Тайпскрипта неплохая позиция. Да, для типичных пользователей это все еще часто недосягаемый уровень. Как запрограммировать обход структуры на системе типов или даже написать обобщенный класс - загадка для многих, решаемая только чат гопотой или везением со SO. Но сама возможность прекрасна и при достаточном желании можно даже писать так, чтобы типы эти не лезли в бизнес слой и ребята продолжали описывать простейшие интерфейсики и объектные типы.

А для тех, кто решит преисполниться в программировании на типах могу порекомендовать курс https://type-level-typenoscript.com - без такого введение в программирование на типах входить было куда сложнее. Только как войдете помните пожалуйста о цене программирования на типах и не заставляйте входить в этот мир других.
🔥3🦄2😱1
Top-down и bottom-up команды

Два разных подхода: продумать и сделать или сесть и начать с экспериментов. И быть готовым выкинуть несколько прототипов.

Оба подхода имеют право на жизнь. В одном мы силами системных архитекторов и аналитиков проектируем систему и потом делаем, а в другом мы должны что-то постоянно пробовать делать.
Провальный подход карго-культа на проектирование классно описан в книге про Agile (про разработку системы для полиции), а провалы вторых можно наблюдать в каждом третьем стартапе и сломанных ad-hoc процессах в ваших компаниях.

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

Хорошо - когда ты понимаешь какая команда у вас и как вам комфортно двигаться вместе. Плохо - "ломать людей" и навязывать свой путь решения проблем.
🔥3
FSMoothy

Сходил на Kolesa.conf. И тут подъехали видео докладов.
Самой мякоткой для меня, конечно, был доклад про конечные автоматы.

Когда узнал, что Кирилл собрался делать доклад на тему КА - я поднапрягся и доделал свою либу для TypeORM (typeorm-fsm). До того, как в проектах мы переехали на нее использовали какой-то огрызок с самописной обвязкой. Тут же я приложил чуть больше своего api-дизайнерского скилла и вроде даже получилось скрестить суть-фундамент с практической пользой.

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

Пробывал ли я еще что-то? Да, посмотрел много вариантов в npm. Везде свои трейдофы. На фронтовом проекте я ранее применял XState. Давайте рассмотрим что меня в нем не устроило:

- Портянка для описания в виде js объекта. Переходы описываются вербозно (зато есть визуализатор), поддерживает стандарт State Chart'ов.
- Observer на action'ах. Описывается очень вербозно. В разных частях портянки объекта-описания.
- Активный конечный автомат (actor). Круто! Но, в таком виде на бэке скорее очень мало куда подойдет.
- Персист данных встроенный в саму либу. На самом же деле логика персиста ложится на клиентскую часть. В экосистеме готового ничего нет.
- Своя система работы с эффектами. Выглядит прикольно, но на деле в таком виде нужно только для активного конечного автомата.

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

- Гварды. Условия на переходах. Без них никуда. Иначе логика также размажется по коду.
- Не все события можно отследить. В моем понимании у события есть следующий следующий жизненный цикл: покидаем предыдущее состояние (onLeave), входим в новое (onEnter), меняем состояние (transition), сработали глобальные подписчики, окончание перехода (onExit). Отсутсвие части этих подписчиков также заставляет размазать часть логики из перехода в клиентский код.
- Вложенные и паралельные состояния. Когда горит красный сигнал светофора - может загореться зеленный сигнал для пешехода. Согласование состояния - одна из фундаментальных фишек автомата. Мы можем сделать отдельное состояние для этого случая или сделать вложенный автомат для состояния "красный сигнал" у светофора. Наличие этой фичи решает проблему роста автомата (state explosion problem).
- Вложенные данные. Автоматы также могут содержать дополнительные атрибуты. Например, счетчик времени. И использовать их на переходах.
- Асинхронные подгрузки. Может потребоваться подгружать начальное состояние и вложенные данные из внешних источников.
...
- и на самом деле много чего еще

Моим решением стало написание собственной либы - fsmoothy. В ней все описанные вещи заложены в дизайн и уже реализованны. Дальнейшее расширение планируется за счет написания отдельных пакетов поверх нее и расширения экосистемы.

Выделяйте автоматы у себя в коде явно, думайте о них при проектировании. Берегите свою кодовую базу! 💎
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍1🦄1
Стримы

В последнее время я регулярно стримлю на канал MemeBattle. У нас кончились кредиты на AWS и мы в рамках стримов сейчас переезжаем на Яндекс Клауд.
Но это не все темы для стримов. Недавно закончился Advent of Typenoscript и мы совместно с @xufostation и другими ребятами, которые прошли весь этот челендж, застримили решение почти всех задачек с довольно подробным разбором. В итоге получился такой мастер класс по Type Level программированию. Думаю, те, кто захочет повторить и послушать как решать такие задачки вынесут много полезного.
В планах на следующие стримы продолжить переписывать одни из наших сервисов для хранения истории Ligretto игр на Platformatic (что это и зачем можно также узнать из стримов), переписать несколько babel плагинов на swc на Rust'е и много чего еще полезного.
В общем, агитирую подписаться на наш канал, ставить лайки, жать на колокольчик чтобы первыми узнавать о предстоящих стримах и конечно же приходить нас смотреть!

https://www.youtube.com/@memebattle_dev/streams
🔥8
Двадцать дней как состоялся релиз первой версии языка Gleam, который должен ознаменовать стабильность и готовность языка к продакшен моментам. И двадцать дней как я пробую этот язык с разных сторон. и мне зашло

Давайте посмотрим, что включено в первый релиз и отмечено core-командой как стабильное:
⭐️Дизайн языка. Все четко, язык похож на Rust+Elixir, но максимально минималистичнее - я бы сказал, что даже минималистичнее Go
⭐️Компилятор. Тут тоже есть чем удивить: реализация TCO для JS, довольно понятный как Erlang, так и JS код на выходе. Написан на Rust’е, так что все работает быстро
⭐️Build Tool. За то время, что пользуюсь ощущение, примерно как от Cargo, четенько
⭐️Форматирование. Немного вкусовщины, но в целом все по кайфу
⭐️Языковой сервер. Пока самая слабенькая часть. Нет, например, подсказок по маркированным(labeled) аргументам, автодополнение довольно глуповатое, не всегда ошибки там где надо показываются и еще пяток мелочей. Но в репозитории языка висит довольно много PRов улучшающих LSP.
⭐️Компилятор в WASM и JS-биндинги. JS-биндинги имеют свой вполне очевидный рантайм оверхед. Прелюдия не большая, что в простых примерах ворочающих списки дает меньший выхлоп в бандл чем тот же ReScript.

Экосистема языка показалась мне не большой, но все минимальное для вебчика уже есть. wisp в качестве минималистичного web-фреймворка. lustre - elm-like фронтенд фреймворк. Есть свой индекс для пакетов - можно посмотреть что вообще есть.

Сам Gleam в первую очередь направлен хоститься на виртуальной машине Erlang - BEAM, что довольно четко очерчивает границы применимости и нишевость языка. Однако хороший интероп в JS с простым FFI и без расскраски функций - сильно расширяют его возможности.

Если вас заинтересовало - для быстрого ознакомления есть тур на оффициальном сайте, а чтобы попрактиковаться есть бесплатный трек с обучением на Exercism.
Please open Telegram to view this post
VIEW IN TELEGRAM
🦄7👍52
Провел сегодня две замечательные сессии на TechTrain в роли эксперта, что поставило окончательную точку после трех месяцев подготовки в роли члена программного комитета. Время пролетело незаметно и как по мне, так вышло очень хорошо.

Мне в целом, нравится помогать готовиться ребятам и наблюдать за тем, что у них получается в итоге. Утренний доклад с Давидом из @it_kachalka про ООП однозначно рекомендую всем к просмотру! Прям хорошая рефлексия и выводы, которые привели его к Функционально Ориентированному Программированию🦀. Дискуссия в конце вообще безумно зарядила меня на ближайшее время.

Если у вас есть что рассказать и вы еще думаете выступать или нет - выступайте. Если думаете о том, стоит ли ходить смотреть доклады - ходите. Ведь митапы, пусть и онлайн - это даже не столько доклады, где вы что-то новое узнаете. Но и тусовка, где можно обсудить свои боли и идеи. Я лично всегда очень тепло вспоминаю, как у нас проходили митапы в Новосибирске, те кулуарные темы и крутые движухи, которые мы устраивали после мероприятий с друзьями. 🌈
Please open Telegram to view this post
VIEW IN TELEGRAM
❤‍🔥3🔥2🦄22
Continuations

Один из моих любимых топиков в программировании - различные способы зафиксировать выполнение процесса программы на определенном месте и затем вернуться к нему. Это также называют Continuation. Впервые возможности которые за этим скрыты меня впечатлили когда я погружался в генераторы и в реализацию Redux Sagas. И теперь в каждом новом языке ищу нечто подобное и нахожу новые возможности, как это можно применять.
В языках, с которыми я сталкивался чаще всего есть одна из форм сontinuation - async/await. Чаще всего мы маркируем функции как async и чтобы зафиксировать состояние программы и отдать управление куда-то еще используем await. Такая маркировка еще называется раскраской функций. Однако, этот подход требует сложной поддержки всего этого дела со стороны рантайма, например, в Rust для этого вообще много интересной низкоуровневой возьни - посмотрите на то как там работает Tokio и какие в нем используются примитивы. Генераторы примерно туда же, хотя они как правило дают чуть больше контроля над процессом выполнения, но с ними все те же проблемы.
Кроме раскраски функций типичный сontinuation в разных языках еще и не сериализуем, а значит не получится продолжить выполнение на другой машине, что вместе с потребляемыми CPU ресурсами ставит предел применимости этой техники.

И тут для меня на сцену вышла виртуальная машина Erlang - Beam. Процессы в Beam легковесны, планировщик встроенный в VM переключает работу с зависших процессов на живые и позволяет их распределять по всем инстансам VM, которые могут находиться на разных тачках. И вооружившись Gleam’ом я попробовал решить одну классическую проблему с Telegram ботами.

Как правило, чтобы имитировать последовательное общение между чат ботом и пользователем нам нужно куда-то сохранять состояние чата с текущим пользователем. И уже на следующий ответ снова запускать всю цепочку обработчиков и проверять состояние чата. Тут в реализации начинают появляться конечные автоматы или всякие специализированные подходы, вроде сцен.
Но по своей природе это задача как раз для континуаций. И тут на сцену выходит Conversations в grammy. Где вы просто пишете const newContext = await conversation.wait(); и оно работает. Но тут все те же проблемы сontinuation’ов и еще немного специфичных ограничений из-за деталей реализации. И Beam это может помочь решить.
Что собственно я и сделал - реализовал аналог Conversation API, но уже на Gleam. Вы уже можете попробовать сделать на этом простых чат-ботов (я +- стабилизировал внешний интерфейс, так что вряд ли что-то большое сломаю). Тут нет покраски функций, нет проблем с сериализацией и распределением между инстансами и как мне кажется довольно просто начать это писать (и читать):


fn set_name_command_handler(
ctx: BotContext,
_,
) -> Result(NameBotSession, String) {
use <- telega.log_context(ctx, "set_name command")
use _ <- result.try(telega_api.reply(ctx, "What's your name?"))
use ctx, name <- telega.wait_text(ctx)
use _ <- result.try(telega_api.reply(ctx, "Your name is: " <> name <> " set!"))

Ok(NameBotSession(name: name))
}


Больше примеров можно посмотреть тут и тут!

В итоге писать чат ботов выходит куда более естественно, чем с различными конечными автоматам. Пробуйте, может вам тоже понравится!
😱6
Forwarded from igrishaev
Когда неподготовленный человек видит Лисп, он как-то реагирует: хихикает, лепит эмодзи, вовлекает других, словом — переживает. В такую минуту он напоминает школьника, который принес эротический журнал: смотрит на груди и попы, конфузится, краснеет, показывает другим под партой. Вроде бы интересно, но что с этим делать — не понятно.

Хорошо, если бы программистам объяснили: Лисп — лучший способ записать код. Любой язык можно улучшить хотя бы тем, что сделать синтаксис лиспо-подобным. Пусть даже парадигма останется прежней.

У скобочной записи есть преимущество: каждое выражение имеет начало и конец. Убедитесь, что прочли последнее предложение вдумчиво. В Лиспе каждая форма имеет начало и конец. В других языках — нет.

Предположим, я вижу выражение

x = foo + bar

Значит ли это, что выражение закончено? Конечно нет. За bar вполне может быть продолжение:

x = foo + bar * kek + lol

Кроме того, что выражение определяется "на глазок", сюда вкрадывается приоритет операторов: bar * kek нельзя разорвать.

В то время на Лиспе первое выражение будет таким:

(define x (+ foo bar))

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

(define x (+ foo (* bar kek) lol))

Все задано явно, вопросов быть не может. Вы, конечно, скажете, что приоритет умножения известен каждому школьнику? Тогда счастливой отладки дебажить что-то такое:

Some shit = foo && bar || test ^ foo;

Из сказанного следует, что в Лиспе удобно работать с выражениями. Например, я могу выделить текущую форму. Обратите внимание — форму! Не метод, не сложение чисел, не класс, а именно форму! Потому что в Лиспе все это — форма. Мне не нужны хоткеи "Select method", "Select class", "Select whatever". Мне достаточно одной клавиши, чтобы покрыть все случаи.

Формы в Лиспе можно разбивать и объединять. Стоит нажать кнопку, и выражение (+ foo bar) становится просто + foo bar. Далее я могу что-то сделать с его элементами. Форму можно двигать выше, ниже по текущему уровню вложенности. Можно втолкнуть ее внутрь. Можно вытолкнуть наверх из-под условия.

Форма может поглощать другие формы. Например, у меня есть код:

(do-some-stuff x y z)

Теперь нужно, чтобы форма была внутри условия. Прямо над ней я пишу:

(when some-condition)
(do-some-stuff x y z)


Далее, находясь внутри when, я жму кнопку, и форма втягивает в себя следующую за ней форму, и получается:

(when some-condition
(do-some-stuff x y z))


Разумеется, есть другая кнопка, чтобы "выплюнуть" форму, и я получу то, что было до поглощения.

Каждый думает, что к нему это не относится, ведь он же пишет не на Лиспе. Но вот реальный пример на Джаве с цепочкой футур:

return prepare(sql, executeParams)
.thenCompose((PreparedStatement stmt) -> sendBind(portal, stmt, executeParams))
.thenCompose((Integer ignored) -> sendDescribePortal(portal))
.thenCompose((Integer ignored) -> sendExecute(portal, executeParams.maxRows()))
.thenCompose((Integer ignored) -> sendClosePortal(portal))
.thenCompose((Integer ignored) -> sendCloseStatement(stmt))
.thenCompose((Integer ignored) -> sendSync())
.thenCompose((Integer ignored) -> sendFlush())
.thenCompose((Integer ignored) -> interact(executeParams))
.thenCompose((Result res) -> CompletableFuture.completedFuture(res.getResult()));


Каждый видит в меру своей испорченности, но я вижу здесь Лисп. Ему немного не повезло: нужно только переставить скобки, и получится нормально. Но вот курьез: Джава-человек в упор этого не видит. Для него это по-прежнему код на Джаве, а переставишь скобки — и все, ржака.

Теперь нужно изменить код так, чтобы stmt оставался в поле видимости на большее число шагов. Получится вот так:
👍4❤‍🔥1🤨11
Малюткам DeFi-dev'ам. Часть 1.

Я успел немного копнуть в разработку DeFi. До этого я попрогал в целом для блокчейнов - смарт контакты, децентрализованное хранение (IPFS) и прочее всякое около. И был достаточно погружен в само явление DeFi как пользователь, холдер, ну и как многие пытался здесь навариться (лудка дело такое).

И если вы, как и я когда-то, заинтересовались программированием для DeFi и не знаете куда копать и с чего начать, то я надеюсь дать вам какую-то базу.
Первое, что стоит помнить и всегда держать в голове - в крипте очень много скама. Это благодатная почва как для лудоманов, так и для мошенников разных мастей (иногда они даже не осознают всю скам-природу их проекта). Идти сюда в надежде залутать шальные деньги - по сути тоже самое, что крутить слоты в надежде на драконий куш. Также слушать блогеров и прочих крипто-чуваков не стоит. Многие из них выезжают только за счет ставок против аудитории и инсайдов. Да и "большой" трейдинг в крипте держится на очень зыбких субстанциях.
Зато, на почве все еще зарождающегося рынка без внятного регулирования, находящейся до сих пор в серой зоне, каждый программист, которому интересна область финансов, может попробовать себя в деле и не прикасаться к реальным банкам с кучей бюрократии и сложностей.

По своей природе DeFi очень интересная область с точки зрения разработки. В ней сперва нашли себя шифропанки, затем стартаперы средней руки, а теперь и выросшие крипто-компании с десятками тысяч сотрудников. Если сейчас посмотреть сверху на рынок того, что тут делается, то за верхушкой айсберга крупных бирж скрывается много чего поменьше, и более интересного.
Вот такой список популярных видов проектов я накидал: AMM (автоматические маркет мейкеры), разного рода MEV автоматизация, анти-скам системы, лаунчпады, кредитование, управление активами, разные страховые системы, DEX'ы, DAO, Yield Farming, кроссчейн протоколы, NFT и прочие стартапчики использующие крипту как платежный шлюз.

В следующих частях обязательно рассмотрим каждый компонент DeFi с точки зрения потребителя и разработки в EVM и Solana сетях (может еще и Ton немного затронем, хотя тут пока с реальными DeFi проектами не густо).
Примерный список ключевых тем (на мой скромный взгляд) по DeFi: чем отличаются сети и чейны, обернутые токены, как запускаются токены, стейбл коины, пулл ликвидности, централизованные и децентрализованные платформы, автоматические маркет мейкеры, феномен Uniswap V3 и почему это круто, мемпулл, валидаторы, арбитраж, снайп и прочие боты (+ разные виды атак).

Что из того, что я перечислил выше вам знакомо? Что интересно в первую очередь?

Ну, и как мне кажется этих тем должно быть достаточно для старта DeFi-dev малютки. И, конечно, буду рад если мощные ребята докинут тем и помогут их разобрать.
👍7🔥6🦄22
Малюткам DeFi-dev'ам. Часть 2.

С майна первого Биткоина по текущий 2024 год успели родиться стони блокчейнов. Тысячи умерли еще в зачатке. И только десятки дожили до наших дней, и буквально парочка действительно окрепла и расправила крылья над полем децентрализованных финансов.

Сам феномен BTC, как валюты - потрясающий, как и идеи стоящие за блочейном Bitcoin, который по сути был создан ради нее. В отличии от классических финансов, где "хост" валюты - государство у Биткоина хостом служит множество нод, образующих сеть Bitcoin.

Чтобы обеспечить существование самого капитализированного токена без устали работают более 55к узлов сети (на август 2024). Они блок-за-блоком проверяют и передают другим узлам сети данные, которые вносят в них пользователи.

Если вы сталкивались с децентрализованным хранилищем (или хотя бы задавались вопросом), то вы скорее всего слышали о задаче двух генералов и CAP теореме. Если нет, то про первое обязательно прочитайте в Вики, а вот про CAP давайте поговорим.

CAP, где C - Согласованность, А - Доступность, P - устойчивость к разделению. Подумайте или вспомните, что предлагает вам ваша любимая база данных или брокер сообщений. Вспомнили? Как правило, просто достигаются два из трех правил, хотя сейчас с оговорками есть базы удовлетворяющие всем трем. С оговорками потому что достаточно немного поразмыслить, чтобы прийти логически к противоречивости наличия третьего. А теперь вернемся к блокчейн сетям.

Когда проектировалась сеть Биткоина, то она сразу задумывалась как децентрализованная сеть, по которой будут проходить финансовые операции. Потому нет ничего хуже отказа в обслуживании и невозможности обеспечить эту самую децентрализацию (все это с поправкой на реальность). А вот над согласованностью нужно поработать. И результатом работы стал механизм консенсуса PoW (Proof Of Work). Дорогой с точки зрения требуемых мощностей компьютеров и потребляемых энергоресурсов. И как показало время надежный и стабильный.

Еще в годы в университете я разрабатывал клиент для торрента. И вся эта возня с блокченами мне дико напоминала то, как работает этот самый торрент-протокол. Хотя, здесь идеи p2p сетей дошли до совершенно другого уровня.

У нас уже есть сети, объединяющие миллионы компьютеров (интернет). И даже есть те, кто готовы платить за электричество, чтобы хранить наши данные и держать их согласованными. В замен они получают придуманную нами награду. А давайте теперь будем выполнять на них какие-нибудь еще программы. С такими идеями в наш мир ворвался Ethereum.

EVM (Ethereum Virtual Machine) - децентрализованная виртуальная машина, сердце всего Ethereum. Для нее сперва пишется контакт на Solidity, который компилируется (с помощью компилятора solc) в специализированный байт код и выполняется на множестве нод. После компиляции полученную программу нужно также развернуть в сети. Если вам интересно все это пощупать, то возьмите Remix в качестве IDE, Hardhat для тестирования и Truffle для развертывания, ну и обязательно попробуйте развернуть стандартной либой ethers.js. Там все просто берем полученную после компиляции abi'шку (файл с содержащий информацию о внешнем интерфейсе вашей программы), бинарник (файл .bin, содержащий вашу програму), рассчитываем нужный газ (берем пока наугад с запасом) и делаем contract.deploy({ data: '0x' + bytecode }).send({}).

С Эфиром работать приятнее всего (в следующих темах постараюсь вас в этом убедить). Пока обращусь к большинству: множество токенов и dapps запущенных на нем - подтверждение моих слов. Комьюнити взрослое, в DeFi понимающее, экосистема самая богатая (но и от нее иногда полыхает мое кресло), так что если решите делать проект - однозначно выбирайте Эфир.

Резюмируя, хоть Биткоин и стал ключевой фигурой в мире, однако, для DeFi это больше историческая фигура, а не что-то, чем сейчас дышит разработка в этой области. Так что его больше мы касаться не будем.

А вот с устройством EVM, нод, токенами на Эфире и вообще всем вокруг EVM-подобных сетей нам предстоит познакомиться далее.
🔥6❤‍🔥33🦄1
Малюткам DeFi-dev'ам. Часть 3.

И так, у нас есть какой-то байткод для EVM. Этот байткод мы будем выполнять сразу на всех нодах в сети Ethereum, тем самым никакой отдельный узел не может цензурировать наш контракт и исключает мухлеж со стороны держателей конкретной ноды (а уязвимости типа 51% решаются другими механиками).
Вообще каждой ноде не обязательно выполнять контракты, так в Ethereum сети есть три типа нод: полные, облегченные, архивные. Код смарт контрактов выполняют только полные и архивные узлы, а облегченные полагаются на них при валидации транзакций.

Так, а что же значит выполнить транзакцию?
Начнем с отправителя и получателя. Это два адреса. Адрес представляет собой 20-байтовое значения и начинаются с Ox. Это может быть либо аккаунт пользователя (Externally Owned Accounts aka EOA), либо контракт, адрес которого генерируется при деплое.
И так мы - пользователь, отправитель. Наш получатель - контакт MessageStore который просто сохраняет наши сообщения на блокчейне. И да, помимо выполнения кода контакты могут записывать данные в блокчейн и хранить свое состояние.

Вот мы зашли на Eatherscan, нашли наш контракт (например, гляньте на контракт Uniswap V2, но далеко не все контракты открытые и есть тулзы для реверса из байткода) и увидели два метода setMessage(string memory newMessage и getMessage() public view returns (string memory). Чтож, пора его вызвать. Берем либу ethers тяп ляп и получаем что-то такое:


import { ethers } from "ethers";
const provider = new ethers.providers.InfuraProvider();
const privateKey = '...';
const wallet = new ethers.Wallet(privateKey, provider);
const abi = [
    "function setMessage(string memory newMessage) public",
    "function getMessage() public view returns (string memory)"
];
const contract = new ethers.Contract(contractAddress, abi, wallet);

const gasPrice = ethers.utils.parseUnits('20', 'gwei');
// Вызываем
const tx = await contract.setMessage("Запись от DeFi малюток!", { gasLimit: 21000, gasPrice });
// Ожидаем подтверждения транзакции
await tx.wait();


При вызове contract.setMessage мы стартанули транзакцию от отправителя (wallet) в контракт.

И вот наша транзакция начала распространяться по сети и попадает в mempool (пул неподтвержденных транзакций).
Узлы начинают проверять достаточно ли у отправителя средств для выполнения транзакции, корректность подписи (можете почитать про алгоритм ECDSA) и верно ли указан Gas Limit. Валидаторы (или майнеры в случае с PoW) выбирают транзакции из mempool и включают их в блоки. Каждый блок имеет заголовок, который включает хеш предыдущего блока, временную метку, nonce и другие метаданные.

Когда уже одна из нод получит наше сообщение через вызов provider'а она начнет считать газ: ~700 газа за вызов функции (операция CALL),~20к газа за изменение (SSTORE) 0 за RETURN. Тогда мы легко пройдем наш лимит (мы взяли чутка с излишком) и заплатим за это ~21000 газа * 20 Gwei = 420000 Gwei (0.00042 ETH). Если не указать явно gasLimit either за нас вызовет метод estimateGas - метод выполняет симуляцию транзакции и возвращает приблизительное количество газа, которое потребуется для её выполнения. В зависимости от состояния сети метод может ошибиться и переоценить (юзер заплатит больше) или недооценить стоимость (тогда транзакция упадет с out of gas). За 20 gwei за газ может случиться такое, что никто из валидаторв не возмет нашу транзакцию в обработку (либо ооч долго не будет брать) и если не указать gasPrice - either также может посчитать значение на основе условий сети.

Стоимость каждой операции в байткоде EVM хорошо задокументирована в спецификации и в нашем простом случае мы смогли легко сами посчитать значение, но так как контакты могут взаимодействовать с другими контрактами не вариант всегда вручную расчитывать цену.

Когда наконец транзакция включена в блок, узлы сети выполняют байткод транзакции на EVM: декодируют данные, вызывается функция, обновляется состояние.

И вот наконец происходит запись изменений в блокчейн, обновляя хранилище контракта. В дальнейшем это состояние будет доступно вызовом функции getMessage.
🔥2🦄2🌚1
Малюткам DeFi-dev'ам. Часть 4. MEV

И нет, мы не будем мяукать. MEV (aka Maximal Extractable Value) - такая мера прибыли, которую можно получить за счет включения, исключения или переупорядочивания транзакций внутри одного блока. Будем рассматривать на примере Ethereum. Напомню, что блок может включать в себя целый список транзакций и на этапе формирования мы все еще можем добавлять транзакции пока валидаторы (в PoS) не сформируют этот блок и не отправят в чейн (от сюда и БЛОКчейн если че). Тогда все, процесс необратим и включенные блоки иммутабельны.

В MEV'е есть три основные практики:

1. Фронтраннинг. Мы можем легально "подкупить" валидаторов назначив более высокую цену за газ и засунуть нашу транзакцию вперед других.
2. Бэкраннинг. Мы можем отправить транзакцию сразу после ожидаемой трнзакции, чтобы извлечь прыбыль из ее результатов
3. Сендвичинг. Тут мы отправляем две транзакции: одна перед и одна после, чтобы манипулировать ценой промежуточной транзакции.

Звучит как изи мани. Так оно и бывало особенно когда транзакции никак от этого не защищены. И тут надо понять что же нужно для такой атаки.

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

Понятное дело, что вручную это никто не делает. Для этого ребята пишут MEV ботов которые сразу и мониторят мемпулл, и определяют выгодную транзакцию, и совершают весь этот "атакующий" процесс, ну, и продают токены, приносят прибыль авторам.

Конечно, если бы все так было просто... Ребята из Эфира давно познали дзен и придумали несколько способов защиты от такой атаки (но есть чейны, где такая защита фундаментально невозможна, либо очень сложна и там пытаются вывезти на других механизмах, привет, Solana).
Эфир изменил механизм оплаты комиссии за транзакции (EIP-1559). Разделив комиссию на Base Fee (автоматически регулируется в зависимости от нагрузки на сеть) и Tip Fee (пользователи могут добавить чаевые валидаторам, но это все еще не гарантирует включение в блок). Это усложнило, не исключило возможности для MEV атак (подсчет рисков и прочая математика делает свое дело). Потому еще и появились отдельные проекты защищающие транзакции от MEV атак.
Тут можно выделить несколько проектов такой защиты.
- Flashbot и MEV-Geth - проект создает прозрачный рынок для MEV (не можешь победить - возглавь!). Во первых они устраивают аукцион и создают приватный пул, где майнеры/валидаторы могут выбирать их без риска фронтраннинга.
- Order Matching Protocols - протокол сопоставления ордеров. Также минимизирует ущерб от MEV. RFQ (Request For Quote) - сделки проходят оффчейн и только окончательная транзакция записывается на блокчейн, Batch Auctions - делаем ставки и ваша транзакция одновременно с другими (вместо поочередного выполнения)
- Time-locked Transactions - механизм на некоторых платформах, который откладывает включение транзакции в блок до поры-до времени.
- Privacy Enhancements - обеспечение приватности транзакций, скрывая их детали до включения блок. Тут популярны два протокола - zk-SNARKs и zk-Rollups.
- Decentralized Sequencers - похоже на RFQ, только децентрализованно (использую тн sequencers). Тут популярны проект Arbitrum и Optimism.
- Commit-Reveal Schemes - тут система такая: пользователь сперва отправляет commit (хеш транзакции), а затем раскрывает ее детали. Это предотвращает фронтраннинг, так как детали транзакции известны только после коммита.

В целом, все это не убило полностью, но сильно усложнило MEV-атаки. Вы всегда можете попробовать поискать другие уязвимости и более изощренные схемы. А я думаю, что пора перейти к расмотрению других блокчейнов.
1🔥3🦄2