Глеб Лукьянец

Глеб Лукьянец

Неделя
Jul 19, 2021 → Jul 26, 2021
Темы

Архив недели

Понедельник


День добрый. Меня зовут Глеб, я работаю в VK. На этой неделе поговорим в основном про языки и технологии

"План": Дни 1-5: Порассуждаю про языки, побомблю на технологии и программистов День 6: В субботу работать не положено День 7: Поговорю про что-нибудь около.

Дисклеймер: я iOS-разработчик и знаю только айосное. Да и то - не очень. Да и вообще мне ни в чем верить нельзя.

Футноут: у меня вторая неделя короны. Твитов будет немного, но я надеюсь на живое обсуждение.

Для затравки: в swift можно писать self. перед обращением к собственным полям/методам. А можно и не писать. Что напечатает этот код?
notion image

Варианты:
🤔 51.3% "instance"
🤔 31.6% "global"
🤔 4.4% Ничего
🤔 12.7% Не скомпилируется

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

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

У Котлина, кстати, подход аналогичный. А ObjC и Java не пострадали: в первом нет перегрузок, а во втором - глобальных функций, лол.

🔥Тред (Глеб Лукьянец)
Пример c global vs. instance отлично иллюстрирует что разработка ЯП – крайне непростое занятие. Всего лишь две простые фичи (перегрузка и области видимости) – а на их пересечении уже вопросы, баги и непонятки. Ошибиться легко. Сделать идеально - почти невозможно.

Часто слышу "каждый язык хорош под свою задачу". Это не правда: некоторые из них очень даже говно.

Некоторые отдельные даже более говно чем остальные. То есть существуют сорта. Давайте разбираться.

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

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

Во-вторых, следует отделить внутренние условия от внешних. Например, некоторые языки (даже отлично спроектированные!) могут казаться плохими просто потому, что принятые в них подходы вышли из моды.

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

🔥Тред (Глеб Лукьянец)

Вторник


Иногда говорят что сравнивать "разные" языки типа C++ с питоном неправильно. На самом деле, и то и другое – языки общего назначения в одной парадигме. И мы вполне имеем право порассуждать, как хорошо их авторы справились со своими задачами.

Чтобы оценить общий дизайн ЯП, проще всего рассмотреть его как некий набор фич. Нас должна интересовать полнота этого набора и то, как фичи в нем взаимодействуют между собой.

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

Униформность: применимость фичи не должна содержать краевых случаев. Если я могу сделать публичным класс, то должен мочь сделать стракт и инам.

Ортогональность: одновременное применение двух фич не должно вводить краевой случай. Если я могу сделать класс public, а могу final, то я должен мочь сделать его public final и должно это вести себя как public + final.

Например, тот факт, что indirect до сих пор нельзя применять к страктам – пример нарушения униформности. А то, что мутирующие методы не могут быть забинжены в функциональный объект - нарушение ортогональности.

🔥Тред (Глеб Лукьянец)
Раз вспомнили свифт, давайте поговорим про его аспекты, которые откровенно разочаровывают. Без порядка и уровней значимости.

Первое, что приходит в голову, из моего недавнего: key paths не работают со статическими полями.

В свифте есть dynamicMemberLookup, который позволяет строить интересные вещи вокруг lookup pattern: семантические ресурсы на манер android.R; структурированные API; иерархические системы настроек и вообще любой DSL, где пригодилась бы "умная" точка.
notion image

Вот только из-за того, что нельзя построить KeyPath на статическое поле или подтип, этот тип T на картинке выше оказывается зачастую изуродован бессмысленными пропертями. А иные интересные вещи и вовсе сделать невозможно

Вообще, на самом деле, кажется что все эти кипасы и прочие попытки сделать first class properties вообще не нужны, и лучше попытаться устранить ложную дихотомию property vs method. Но об этом как-нибудь в другой раз

🔥Тред (Глеб Лукьянец)
Кстати, про member access operator: dynamicMemberLookup это самое близкое к его перегрузке, что я когда/где-либо видел. Ну в C++ ещё был пропозал про это, но там перегрузка a.b как (&a)->b, для умных ссылок. То есть немного другое.

Суть проблемы заключается в том, что по правую сторону оператора стоит имя поля/метода, которое, само по себе, не является членом первого порядка. Из-за этого выразить такой оператор средствами языка просто невозможно.

Это можно было бы частично пофиксить сделав правую часть чем-то удобоваримым, но я не знаю ни одного языка со статической типизацией, который бы пошел этим путем (кроме swift!)

Как следствие, optional chaining доступен только специально для Optional<T> и больше ни для чего. А жаль: так хотелось добавить его, например, к Result в своем проекте.

Можно было бы предложить ввести некий протокол Unwrappable, поддержав который мы бы получали optional chaining, optional binding и nil-coalescion для любых наших типов. Но я не уверен, что это хороший путь

🔥Тред (Глеб Лукьянец)
В свифте есть анонимные структуры (кортежи/tuples), но нет анонимных инамов. Мне кажется это жутким нарушением симметрии.

А если бы были, то T? мог бы просто быть алиасом к (T | Void) вместо отдельного Optional<T>, что хорошо сочетается с Void, который просто алиас к (). Never тогда был бы алиасом к (|), что забавно

Таплы тоже, кстати, странные. Во-первых, свифт не допускает таплов от одного элемента. Во-вторых, их нельзя расширять, поддерживать протоколы и.т.п.

Из-за этого часто приходится заменять использование тапла на отдельную структуру когда ну очень нужно чтобы он стал, например, Comparable.

Особенно обидно что в каком-нибудь расте тапл с одним элементом и кучей навешаных трейтов отлично работает и это даже считается идиоматичным

А вы используете таплы в своих проектах?
🤔 14.1% Использую везде
🤔 6.4% Только вне интерфейсов
🤔 42.3% В спец. конструкциях
🤔 37.2% Избегаю везде

Среда


Странные, кстати, не только таплы. Эти два минуса относятся ко всем неноминальным (структурным) типам в свифте: Any/Object, A -> B, A & B, A.Type.

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

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

🔥Тред (Глеб Лукьянец)
Дженерики в свифте: радость через боль и страдания. С одной стороны, всё классно: generic-код получается красивым, типобезопасным и (относительно) эффективным. С другой: чуть шаг в сторону и всё – провал.

Проблема 1: вариантность поддержана только для некоторых типов из стандартной библиотеки, которые свифт знает "в лицо". Например, вот это работает:
notion image

А вот это – нет:
notion image

Обидно видеть подобное упущение. Тем обиднее что даже ObjC __covariant и __contravariant были с самого начала.

Проблема 2: Отсутствие вариадиков и не-типовых аргументов.

Если бы дженерик мог принимать переменное количество аргументов, некоторые задачи решались бы куда лаконичнее. В группе в SwiftUI помещалось бы больше 10 вьюх, а работать с функциями можно было бы без миллиарда перегрузок для каррирования/композиции и.т.п.

Сам язык, местами, тоже мог бы стать проще и лучше: например, dynamicCallable (возможно) мог бы быть протоколом, а тапплы как отдельная сущность могли бы вообще не понадобится.

Как это могло бы выглядеть: вот маленькая функция на С++, которая выполняет аналог операции map над таплами (очень упрощенный пример)
notion image

Сам std::tuple, кстати, в C++ – просто библиотечный тип. Определен он мог бы быть как-то так:
notion image

Чтобы товарищи по ту сторону платформенной пропасти меньше скучали, тем из них, кто еще с этим не был незнаком, порекомендую kotlin puzzlers из докладов @antonkeks: github.com/angryziber/kot…

Мой любимый - двенадцатый из второй пачки:
notion image

Интересно, многое ли из этого удалось пофиксить, и многое ли навсегда стало частью языка? Кто-нибудь ведет счёт?

Проблема 3: PAT-ы и отсутствие хороших способов с ними совладать.

Суть: когда вы добавляете associated type constraint к протоколу (или используете Self в сигнатуре), он перестает быть типом и становится тем, что называют "protocol with associated types" (PAT). Главное применение для такой штуки – быть generic type constraint-ом.

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

Из-за этого торжествует generic poisoning, проекты полнятся всякими AnyProtocol (пр. AnyHashable, AnyView), а местами вместо хорошо типизированных абстракций – какая-то срамота с кастами.

Помочь ситуации можно было бы сразу несколькими способами. Во-первых, не вижу ни одной причины, почему нельзя разрешать заворачивать значения под PAT-ом в обычные протокольные existential containers с доступом только к тем элементам интерфейса, которые не содержат AT в сигнатурах

То есть, грубо говоря, могло бы быть можно делать как на скриншоте. Для Hashable, на самом деле, нет нужны быть PAT-ом, он просто унаследовал это от Equatable, в котором есть констреинт на Self
notion image

В качестве альтернативы, можно было бы разрешить специфицировать PAT-ы как дженерики (и вызовы дженерик-функций, заодно, тоже, раз такая пляска): например, let foo: Sequence<Element: Int> = [1, 2, 3]

Примечательно что PAT-ы это не совсем то-же самое, что и дженерик-протоколы/интерфейсы (которых в свифте нет). Дженерик-протокол с разными аргументами это два разных протокола. PAT с разными ассоциированными типами это все-еще один и тот-же PAT.

Из этого следует, что вы могли бы законформить один и тот-же дженерик протокол дважды. Крайне полезно для протоколов в духе ConvertibleFrom или ComparableTo. К сожалению, разработчики языка заявляли, что не намерены поддерживать эту фичу.

Четверг


А что бы вы пофиксили/улучшили в дженериках в первую очередь?
🤔 25.8% Ко[нтра]вариантность
🤔 12.9% Вариадики
🤔 32.3% PAT-ы
🤔 29.0% Что-то ещё

🔥Тред (Глеб Лукьянец)
I really like @Kotlin. It takes the second place in my top 5 languages I'd like to use. But sometimes it makes me cry. Not because of .?, !!, ::prop.isInitialized or overriding val with var. Here are some real, serious problems: either bugs or design faults. twitter.com/int02h/status/…
К сожалению, знаю котлин довольно плохо и не могу его дельно критиковать. Зато может мой хороший товарищ: twitter.com/miha_x64/statu…

На самом деле, немного могу: почему некоторые решения в котлине выглядят ну очень дико? Например, inline-функции выглядят как макросы курильщика. А infix-функции как отмазка чтобы не делать нормальные пользовательские операторы как в свифте.

Кто-нибудь знает, почему так? Может быть, это так было в каких-то других языках до котлина? Какой за этими двумя фичами был процесс?

Вот-вот в swift появится structured concurrency с асинком и акторами. Наконец-то! До этого разработчики упорно притворялись что многопоточность не существует: форумы молчали, документация аккуратно обходила эту тему стороной

Нативных тредов в стандартной библиотеке не было, а компилятор так и норовил втихую разломать любое использование мьютексов/атомиков и подобного (в т.ч. dispatch_once). На практике это означало, что остается только пользоваться GCD и молиться.

Теперь уже понятно почему: команда свифта готовила более цельный, всеобъемлющий подход. Очень странно, что готовила она его аж семь лет и дотянула аж до фиксации ABI. За это время все уже успели понаписать килотонны кода на коллбеках.

Про async/await уже сейчас и так не пишет только ленивый. А я хотел бы вас познакомить с идеей, которая позволила бы запилить в язык собственные async/await любому школьнику еще в 2015м.

Возможность запоминать состояние выполнения программы (и, потенциально, работать с ним как данными), называется Continuations (продолжения). В некотором ограниченном смысле, привычные нам замыкания - подвид продолжений.

Возможность получать продолжения от какого-то конкретного заранее заданного момента называется delimited continuations. Возможность по желанию различать такие моменты - named delimited continuations.

Каждый вызов await как раз создает продолжение которое, кхм, будет "продолжено" когда результат async-выражения станет известен.

Так вот представьте, что каждая функция могла бы отдельным специальным аргументом принимать функцию, которую можно было бы вызвать с текущим продолжением и получить в него от неё ответ. Или не получить. Напоминает что-нибудь? Вот именно!

Все вместе это называется системами / обработчиками эффектов. Другая штука, которую можно ими описать – обработка ошибок. Тут уже do будет устанавливать хендлер, a throw его вызывать. Только continuation всегда будет игнорироваться. Туда-же, кстати, continue и break в циклах.

🔥Тред (Глеб Лукьянец)

Пятница


Уже почти целую неделю говорю про языки, а эту картинку до сих пор не запостил:
notion image

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

На самом деле, словами не описать как я ненавижу мир веб-технологий. Тим Бёрнерс-Ли в 90х придумал как обмениваться научными статьями по медному проводу. Спустя десятки лет мы пытаемся зачем-то строить на базе этого богатые интерактивные приложения, медиасервисы и игры.

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

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

Не зря же самый используемый сегодня браузерный движок (Blink) является форком более старого (WebKit), который является форком очень старого (KHTML), который является патчем совсем уж древней россыпи библиотек для работы со всем этим вот (khtmlw)

Но хуже всего – выстроившаяся вокруг культура разработки. Некачественные програмисты на некачественных технологиях пишут некачественный софт. В ОГРОМНЫХ количествах.

В Яндекс-Музыке постоянно зависает интерфейс и иногда отваливается звук. В дискорде не шлются сообщения. Гитлаб открывает мр за полторы (!!!) минуты. Для каждого из упомянутых это всё основные функции.

Зачем экономить ресурсы, если эти гигабайты памяти и гигагерцы процессора, которые мы тратим - не наши? Вместо этого давайте сэкономим время программистов и подключим очередной npm-пакет на четыре строчки кода. Вон, тот хороший: в нем на два трекера меньше, чем у конкурентов.

И ведь дело одним браузером не заканчивается. Сейчас на моем компьютере два мессенджера, текстовый редактор и несколько лаунчеров содержат в себе по браузеру каждый. Каждый жрет как маленькая ОС и всё-равно умудряется тормозить.

В начале недели пытался в дискорде найти для шапки в тви отличную фотку красного моста, которую постил три года назад. Не смог – падает на двадцатой странице поиска - кончается память.

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

🔥Тред (Глеб Лукьянец)

Суббота


Все чаще слышу, как некоторые разработчики почти что с гордостью заявляют, что они "во всех этих неважных тонкостях" не разбираются. Зато знают чем VIPER отличается PIDOR и все модные фреймворки по именам.

У себя в голове они эффективные программисты – решатели бизнес-проблем. На деле, никаких особых новых проблем такой программист решить, скорее всего, не сможет.

Каждый раз, когда меня спрашивают "А зачем вообще это знать?", хочется ответить: "Вам – незачем. Оставайтесь где есть."

Воскресенье


Неделя постепенно подходит к концу. Из-за ковида и моей постоянной усталости за кадром остались: Почему rust - полное разочарование Почему го не зря начинается с этих двух букв Почему нужно избегать hype driven development Почему менеджер – худший враг программиста

Сегодня я хочу поговорить про организацию рабочего места. За своим столом приходится проводить 6-10 часов в день. Правильная обстановка помогает легче войти в поток и дольше в нём оставаться. Это всё очень важно.

Шаг 1: мебель. Стол должен регулироваться по высоте и быть достаточно глубоким, чтобы на нем помещались локти. Если ваш стол неправильной высоты – правильной осанки вы уже не добъетесь.

Стоячие рабочие места – отличная история, но люди не устроены чтобы по восемь часов подряд стоять. Как и сидеть. Идеальным вариантом был бы стол с электрическим подъемником, если вы готовы менять его положение 2-3 раза в день.

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

"Игровые" сулья - полная ересь. Они смоделированы по образу спортивных автокресел, которые сделаны чтобы максимально поддерживать тело в продольных и, особенно, в боковых перегрузках. Когда вы пишете код – ИХ НЕТ.

Кстати. Странно видеть как компании, наняв разраба за козулю в месяц и купив ему оборудования ещё на столько-же, садят его за стул, который не дотягивает даже до ikea markus. Тут отдельный лайк Яндексу - у них в офисах кругом отличная мебель.

🔥Тред (Глеб Лукьянец)
Шаг 2: Разложить штуки на столе: Клавиатура должна быть на столе в досягаемости, но не близко, чтобы не нужно было сильно сгибать локти с запястьями. До мыши/трекпада должно быть можно дотянуться лишь распрямив локоть.

Монитор должен быть на уровне глаз (или даже немного выше), чтобы вы могли смотреть прямо. Дополнительные мониторы лучше всего расположить горизонтально рядом, на одном уровне, чтобы они не выпадали из поля зрения.

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

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

🔥Тред (Глеб Лукьянец)
Шаг 3: Устройства ввода.

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

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

TKL/80% > 100% > 60%. Блок цифровых клавиш в работе не нужен, зато очень мешает правой руке с мышкой. А вот отсутствие стрелок в 60% портит абсолютно всё. Тут могут понабежать адепты Стокгольмского текстового редактора со своими аргументами, но я им не поддамся.

Ортолинейность не нужна. Удивительно, но с культурой ноутбуков и повсеместной привычке держать руки на клавиатуре косо, классический сдвиг рядов приходится очень кстати. На сплитах, кстати, немного другая история.

Кстати, о сплитах. Как эргономичный вариант они вполне ничего, но отсутствие палм-рестов и наклона, на самом деле, немного убивает всю затею. Зато между половинок можно поставить стакан кофе с трубочкой. Или телефон на подставке.

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

Механика нужна, потому что клац-клац.

Пользуясь случаем предостерегу от покупки ergodox-like клавиатур: ради симметрии оттуда убрали справа три кнопки с пунктуацией и стрелки. Конечно-же, авторы забыли что другие языки существуют. На тех трех кнопках в русской раскладке были буквы. В т.ч. самая важная - х.

Мыши: ой, сколько их я перепробовал.

Жесты на эппловой magic mouse – отличная вещь. Продуктивность и все дела. Жаль, как мышь она не очень. А за порт зарядки на подошве проходящие мимо андроидеры стебут

Magic Trackpad вообще божественен: огромный, с taptic engine, жесты и вообще. Жаль, что моему КТС-у он не понравился.

Другой интересный вариант - трекпоинт. На моем ThinkPad x120e это работало отлично, однако, на больших экранах становится слишком заметен компромисс между скоростью и точностью. В общем, моя lenovo track point keyboard отправилась в ящик к трекпаду

В итоге, перешел на трекболы. Начал с нескольких разных от Kensington. У них классное кольцо прокрутки, но не слишком хорошее исполнение.
notion image

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

В итоге сейчас остановился на Elecom HUGE. У гигантского шара большая инерция и это весьма прикольно)
notion image

🔥Тред (Глеб Лукьянец)

Понедельник


Неделя подходит к концу. Итоги: Пять дней душнил про языки. Никому не зашло. Потом денёк красочно и неконструктивно побомбил. Всем зашло. Выводы: не хватило тредов про выгорание и зарплаты.

Пользуясь случаем, порекламирую VK. У нас работается даже лучше, чем вам может показаться :) Отдельное спасибо @miha_x64 за контент и @nikitonsky за офигенный шрифт со скриншотов.

Найти меня можно в вк: vk.com/rd телеграме: t.me/glukianets ...или встретить вживую на peerlab-spb.

Ссылки