?

Log in

No account? Create an account

Стой под стрелой

Поступки и мысли, о которых могу вспомнить не краснея

Категория: литература

С высоты-3
tonsky

Пост вызвал некоторую дискуссию, в связи с чем я не перестаю удивляться, насколько людям кажется что мир устроен так как им хочется а не так как он устроен на самом деле. Многие живут мыслью что то что они (ну или я) пишу это не настоящая Кложа, а где-то там далеко есть настоящая, и если сильно припрет то взял type hints и поправил и так-то и пишут настоящие джедаи. У них все всегда сразу достигает JVM-уровня скорости потому что они сразу везде указывают примитивные лонги и вообще все специфицируют и раскладывают сразу оптимально. Ну или разложат если будет нужно.

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

Так вот. Я сам не то чтобы чужд оптимизациям и натягиванию ужа на ежа. Я потратил достаточно много времени, пытаясь делать разные вещи быстрее, чем они есть. Придумывал какие-то оптимизированные defrecords в DataScript. Менял функции на макросы для инлайнинга. Городил макро-DSL для работы с массивами. Писал макросы, которые эмитят сразу оптимальный JS. AOT-прекомпилял Кложу в классы для быстрого стартапа. Видел тоже разного: всякие библиотеки для быстрой математики, патченные исходники с отложенной инициализацией var-ов, фоновые «прогретые» JVM для быстрого старта. Даже мантра «держи репл всегда запущенным» в общем-то про то, как бороться со тормозами на старте Кложи. Про все это можно сказать одно: это очень изнурительно, и удовольствия в этом никакого нет, только сложность и постоянное разочарование.

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

Но если уж ввязался, то просто делай как все — будет проще, приятнее и честнее. Если язык предлагает records, бери и используй records, а не городи свой хитрый макрос на deftypes. Если нужно позвать функцию — значит, нужно позвать функцию. Если компилятор при этом продалбывает arity в метаданных, чтож — значит, продалбывает, такова цена. Так устроен язык. Хорошо, если это просто по недосмотру и можно отправить патч в компилятор. Если нет, что ж. Все равно нормальный, обычный код, собранный из того, что дают, в конечном счете, на длинной дистанции, выигрывает именно своей простотой и нормальностью. Потому что локальные оптимизации всегда останутся локальными и будут только мешаться под ногами непредвиденным образом; чем дальше, тем больше.

Нет особой доблести в том, чтобы задачи, которые плохо решаются, например, в Кложе, решать именно на Кложе. Мы же взяли ее чтобы было приятно программировать, а не чтобы бороться с фундаментальным устройством языка и идиоматичным кодом. Не надо пытаться писать на Кложе как на Котлине, как не надо пытаться писать на Котлине как на Кложе. Если уж взял ООП язык, то пиши уже блин классы. Конечно, попробовав ЛИСП, хочется везде засунуть неполную, багнутую, наполовину реализованную его версию, но это обещает только унылый доморощенный цирк и бесконечные головняки, ничего больше.

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

Как верно подметил древнегреческий поэт Архилох (да, я тоже только что его нагуглил и тоже только что офигел), в критической ситуации ты не вырастаешь до уровня своих ожиданий, а падаешь до уровня своей подготовки. Ваша программа не будет написана из тысячи выдроченных микроциклов, она будет написана из тысячи самых обычных, рядовых функций, на многие из которых никто даже второй раз никогда не посмотрит, не то что оптимизации начнет расставлять. Поэтому глупо жить мечтами, что если быстрый код в принципе, теоретически, как-то где-то можно написать, то он везде почему-то будет написан. Не будет. Потому что это неудобно, противоестественно, не нужно. Из «на Кложе можно писать быстро» не следует «код на Кложе будет быстрый». Не будет.

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

[...] при разработке на Кложе удобно думать в математических понятиях (а хешмэп это пожалуй самая близкая к функции структура данных). При разработке на Расте это кажется прямо-таки немного противоестственным, и хочется уже взять плоский кусок памяти, и как-то там его расчерчивать.

Язык определяет мышление, best tool for the job, вот это все. Банально, но на конкретном примере оно всегда как-то доходчивее.


Переосмысляем профессию
усы2
tonsky

Ну что же. Сразу после знаменитого поста я поехал на корпоратив/конференцию Таргетпроцесса. Идеальный способ распрощаться с работой: предполагалось три дня на природе, вдали от техники и интернета, так что я прямо у них в офисе захватил книгу «Дзен и искусство ухода за мотоциклом» (сорри, как-нибудь верну). И что же там пишут?

На фоне довольно условной художественной фабулы там пишут, что есть два типа отношения к жизни: классическое и романтическое. Классическое изучает устройство предметов и деталей, их цель, функцию, иерархии, взаимотношения. Романтическое сосредоточено на общем впечатлении, на роли в жизни и не интересуется деталями. Грубо говоря, классическое про копаться в мотоцикле, чинить его и понимать, как он работает и почему, а романтическое — про ездить на нем и получать удовольствие (или не удовольствие, но в конечном итоге пользоваться им). Это буквально иллюстрируется тем, что лирический герой обожает чинить мотоцикл, а его зять, который с ним путешествует, воспринимает мотоцикл как данность и представить себе не может залезть под капот (ну или что там у мотоциклов) и начать разбираться. Все это красиво и убедительно довольно откапывается: из мелких бытовых конфликтов типа как именно заводить мотоцикл в жару или чем закрепить резинки на рукоятках, когда они расшатались, всё уходит корнями в принципиальный конфликт мировоззрений. Романтики — люди искусства (зять в книге — барабанщик), классики — ну видимо ученые и механики. ГГ вроде как берет на себя квест объединить оба восприятия, но я еще не дочитал.

К делу. По этой классификации программирование — классическое классическое восприятие (да, два раза слово классическое, нет, это не ошибка). Без вопросов. Оно только и исключительно про то, как работают вещи. Без ощущений, без «зачем», без роли в жизни. Болты и гайки.

Я пошел учиться на программиста в 2001, начал возиться с компьютерами году чуть ли не в 1995-м, наверное. Работал программистом ever since. Это где-то 13-14 лет non-stop. Казалось бы, какие уж тут вопросы. Однако! Где-то в районе 2007-2008 я четко помню, как потерял интерес и увлекся юзабилити (то, что сейчас UI/UX). Разобрался (сам), прочитал все главные книжки, даже поработал немножко за деньги юзабилистом. Подсел на регулярные советы Горбунова. Потом вроде как интерес к программированию вернулся, особенно когда начало что-то получаться, но юзабилити и дизайн так и шли фоном.

И идут до сих пор. Например, я рисую логотипы всем своим проектам.

один из любимых

Я сделал собственный дизайн своему ЖЖ, потом tonsky.me. Все слайды для презентация я делаю сам. У меня куплены Sketch, Pixelmator, iPad Pro с пенсилом + ProCreate специально для рисунков. В свое время я довольно много времени провел в Final Cut. Это ли стек программиста? В нескольких докладах я рисовал миллион ручных иллюстраций.

уже классика

Я делал собственные цветовые схемы для LightTable, а потом для VS Code.

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

да, даже про эту

Добавилась типографика. Я шрифт сделал, блин! У меня есть посты про «читабельную Clojure», где я рекомендую, как сортировать импорты и сколько пробелов ставить после функции. Еще один пост про мою личную систему выравнивания кода пока в заметках.

любой нормальный программист, если он вообще заметит в чем тут разница, покрутит пальцем у виска

Видите, да? То, как программирование выглядит мне не менее важно чем то, что там написано. Нет, это не каприз. Это и правда глубоко меня волнует, без дураков. Я сижу на Маке не потому, что ноутбук хороший, а потому что не могу физически смотреть на шрифты в Линуксе и их несостыкованные панельки, съехавшие панельки и прочие мелочи. Мелочи становятся самым главным (вот пик моего брюзжания, например).

Кроме «как», меня волнуют еще и вопросы «кому» и «зачем». Весь мой ЖЖ, в активную профессиональную фазу, он весь про «программисты! Остановитесь и посмотрите, что ж вы делаете-то!»

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

В общем-то к остальным вещам из жизни у меня отношение тоже сугубо романтическое: машина/телефоны/кинотеатр/приставки/сантехника/мебель. Я люблю хорошие, продуманные вещи, но внутрь лазить не хочется, хочется, чтобы кто-то пришел и сделал. А если что-то не работает, я бешусь и фрустрирую — ровно как барабанщик Джон в книжке. Главное, я даже книжу-то совершенно случайно захватил. Я и понятия не имел, что найду внутри. Что внутри мне всё объяснят про меня же. Совпадение так совпадение.

Что же получается? Мама, я занимаюсь не тем? Может я дизайнер, мама?


Читабельная Кложа
усы2
tonsky
Обнаглел настолько, что код вставляю скриншотами

http://tonsky.me/blog/readable-clojure/

Танцуй, завещай
усы2
tonsky

Мне тут письмо пришло, про мой журнал и, кхм, информационный стиль. Слышали? Вот тут можно почитать. Не бойтесь, там коротко и по делу все. Но я еще короче: это про то, как написать объявление так, чтобы читатель не умер, пока дочитает его до конца (в инфостиле: «читатели читают объявления до конца и не умирают»). Все классно, полезный навык, но есть проблема. Максим Ильяхов — человек-главред — увлеченный и шумный, и среди дизайнерской тусовки чуть ли не единственный, кто пишет про тексты и язык. Пишет много. Если вы думаете начать писать, то вам нужен рецепт, образец, и с большой вероятностью, если такой рецепт поискать в интернете (а где еще искать?), вы наткнетесь на инфостиль. Упрётесь даже, просто в силу скудности альтернатив (будущие Салтыковы-Щедрины ищут и упираются…).

И вот просыпаюсь я однажды, а инфостиль везде: в объявлениях, на сайтах, лендингах, в пабликах и блогах, лонгридах, есть даже книга, написанная в инфостиле (про инфостиль, разумеется). Вы скажете: сколько же нужно знать, чтобы нормально написать объявление? Как минимум 440 страниц, с картинками, правда. Принцип: адепты инфостиля пихают его куда не надо.

И ладно бы рассылки и корпоративные блоги — жанр, придуманный не для живых людей изначально — тут инфостиль помогает авторам коротко и формально отписаться, а читателям безболезненно пройти мимо. Но личные блоги я всегда ценил за индивидуальность, personality. Это же живой язык, прямая речь. Вы в жизни по справочнику разговариваете? Ваш язык — может, самое интересное, что у вас есть, как минимум, это всегда интересно. Пока вы не научились им управлять в коммерческих целях — это ваш взгляд, ваши детали, мир, характер, настроение, мечты. Ну, вы же не гараж продаете, честное слово. Какая мне разница, что за книгу вы прочитали, если я не пойму, что за человек ее прочитал?

И что делать? Как-то писать надо учиться? Мой совет — читайте, практикуйте. Следите, чтобы вам всегда было интересно. Пишите. Пишите абы как, но не убивайте детали. И опять читайте, много, чем больше, тем лучше. Лучше всего — не в интернете. И не обращайте внимания, что вдруг так много появилось материала и даже программа проверки текста. Они для людей на зарплате, пишущих из-под палки.


Сказка о потерянном времени
усы2
tonsky

Потерял вчера полдня, пытаясь понять, почему input рисуется без значения в нашем React/ClojureScript/Rum приложении (и сейчас уже плюс день, чтобы это починить, and counting). Разговорился с интерном, рассказал ему что там за история — и тут он заметил, как его восхищают такие вещи, как интригует ковыряться в таких сложных материях и как он тоже так хотел бы.

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

У нас уже есть JS, CSS и HTML, в которых одни и те же вещи называются и работают тремя разными способами. Теперь умные люди придумывают React (и носятся с ним, как будто он дан нам свыше) и вводят четвертый способ. Реакт работает через JS, поэтому ему надо уметь конвертироваться в DOM-атрибуты (и назад!). Он также хочет рендериться на сервере, поэтому ему приходится уметь и в HTML-атрибуты. Потом появляется ClojureScript со своим kebab-case и естественно вводит пятый способ и библиотеку Sablono. Во всей этой вакханалии невозможно не потерять информацию, и она теряется, возникает куча смешных случаев типа: одно из самых популярных свойств class конфликтует с желанием использовать объекты как хэши в JS, и в итоге там оно называется className, а в ClojureScript этой проблемы нет и оно снова называется class и цепочка получается class (CLJS) → className (React) → className (JS) → class (HTML/CSS). И это я еще про консистентность в SVG атрибутах не начинал. Ну и в итоге все закономерно заканчивается тем, что и React, и мой Rum (у которого свой server-side render) вынуждены хранить полную таблицу соответствия всех возможных атрибутов со всеми. Звучит продуктивно, да?

Окей, но Реакт зато нормализует. Опять же, don’t get me started, как он нормализует, но допустим, что в этом-то плохого? Опять же, это было бы хорошо, если бы он смог если бы он был дан свыше и кроме него ничего бы не существовало. Но если смотреть на React как на просто один небольшой кусочек паззла в grand scheme of things, то он скорее мешает, чем помогает, потому что это еще одна дополнительная сторона, требующая себе внимания. Т.е. я уже не просто пишу HTML-сериализатор, я пишу сериализатор с оглядкой на то, что где-то рядом будет Реакт и их, возможно, будут использовать вместе. А у него, конечно, свой взгляд на вещи, и нужно отдельно, когда работа сделана, тратить чуть ли не еще больше времени, чтобы его уговорить. Плюс в Реакт доложено всякого, и я бы знать про это не знал и был бы счастлив, а теперь вот приходится учитывать.

Если смотреть на это как на артефакты цивилизации атлантов, конечно, масштаб впечатляет, а те, кто способен с ним совладать — полубоги. Но как результат человеческого труда, если объективно на него посмотреть, с оговоркой что это всё хорошие люди делают и они наверняка выкладываюстся на 120% и лучше так чем никак, то это артефакты, которые рождают проблемы, а не решают их. Список атрибутов устареет, события будут приходить невовремя, эмуляция placeholder в ИЕ сломается (честно, лучше бы они настоящий использовали), установка onClick на root будет мешаться на iPhone и т.д. А, и два вида инпутов на каждый, переключаться между которыми, типа, не рекомендуется! Я прям вижу, как люди, когда это писали, сидели и думали: а вот тут ты, мой дорогой, да-да, лично ты, будешь страдать, потому что мы такую вот штуку придумали и тебе с ней жить. То есть, конечно, они так не думали, но я бы хотел, чтобы как раз думали, чувствовали это постоянно (я, например, чувствую), а потом взглянули на себя в зеркало, ужаснулись и спросили «что я за человек такой? Кто дал мне право обрекать на муки других людей?»

Все привыкли ругать браузер и DOM, мол, legacy и источник проблем (а все фронтендеры, подразумевается, хорошие и всё всегда делают правильно, ха-ха). Но штука же не в этом, штука в том, что мы не можем взять DOM как данность и пойти делать более сложные вещи. Вся эта возня с JS-миром не ощущается как прогресс. Скорее как толчение воды в ступе. Или нет — как воду решетом носить. Когда вместо того, чтобы заклеить дно скотчем и работать дальше, вместо этого к нему приделывают ручку, дают более быстрому носильщику и начинают придумывать, как сделать, чтобы вода меньше расплескивалась во время бега. Это не решение проблемы и не прогресс. Хочется артефактов, которые закрывают какую-то область, пусть маленькую, но без дыр. Чтобы если ты ее взял, то ты беспокоишься о меньшем количестве вещей, а не о большем.

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


Проблема с ярлыками
усы2
tonsky

Чем отличаются эти две иконки?

Если у вас есть какой-никакой опыт с компьютерами, вопрос покажется вам слишком простым. Однако, есть проблема.

Ярлык — абстрактное понятие. Его можно понять, но для этого требуется усилие и определенный склад ума. Ярлык противоречит бытовому опыту. В жизни, если у вас есть книга, то она либо стоит на полке («все книги»), либо лежит на столе («десктоп»), либо мы ее сейчас читаем («открыта сейчас»). Можно сказать, что у книги есть состояние, ее можно переводить из одного в другое, но — на что можно всегда положиться — книга одна и она всегда в каком-то одном месте.

Так вот, ярлык ломает эту простую концепцию «есть вещь и есть ее место», вместо этого вводя понятие «ссылки на объект», которому в реальном мире ничего не соответствует. Программисты, например, специально этому обучаются, да еще и не у всех получается (см. тест «понимаешь ли ты указатели?»). То, что такой абстрактный концепт просочился в интерфейс — плохо для компьютеров как бытовых устройств (глупо спорить, что компьютеры и телефоны в большинстве своем давно уже что-то вроде очень сложной и неудобной кофеварки).

Положение усугубляется тем, что ярлык нарочно выглядит так же, как и исходный объект, но работает по-другому.

Т.е. есть сильная визуальная метафора (иконка), и есть некий тонкий скрытый нюанс, который кардинально меняет ее поведение. Из-за этого диссонанса (выглядит одинаково, работает по-разному) надо держать в уме дополнительную переменную — тип объекта. Если про него забыть, а это очень легко, сразу появляется ненадежность: может работать так, а может иначе, как именно — не предугадаешь.

Ненадежность в интерфейсе — страшная беда. Она рождает страх, неуверенность, осложняет формирование привычки и сильно поднимает порог обучения. Обучиться надежному действию (нажми кнопку — потечет вода) на порядки проще, чем ненадежному (сначала нажми сюда, чтобы войти в режим настройки, затем сюда, чтобы выбрать дату, затем сюда чтобы скорректировать день). Хуже всего это в интерфейсах, которыми пользуются нечасто — у мозга просто недостаточно усилий, чтобы запомнить все контексты и переходы. Даже если потратить время и «разобраться», через неделю все выветрится.

Что делать? Делать интерфейс надежным (вещи всегда ведут себя одинаково и предсказуемо). Качество интерфейса, в котором хотя бы часть элементов работает всегда одинаково (всегда-всегда, без контекстов, условий, попапов и прочих «но») вырастает на порядки (см. мой пост Единственная надежная кнопка).

Дальше: не вводить абстарктных концепций. Ориентироваться на бытовой опыт. Проще всего с иконками: если у тебя приложение есть, есть и его иконка. Ярлыков нет, иконка одна и где-то в одном месте лежит: в панели быстрого запуска, на десктопе, в папке Хлам. Это очень простая и прямолинейная концепция: иконка=приложение, папки/панели/экраны=место.

Так работает, например, iOS, который перепридумывали с нуля, выбросив наследие десктопных компьютеров:

У каждой иконки может быть только одно место. «Вид» удаления только один, и он совсем удаляет:

А вот Android сохранил ярлыки и создал путаницу:

Даже в свежайшей 6-й версии они путают пользователя тем же вопросом, что и Windows 95: удалять ярлык или приложение? Такие вопросы можно задавать только бородатым 40-летним сисадминам.

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

Формально, как программист, я понимаю ход мысли, но это очень, очень сложно и нелогично на бытовом уровне. У меня на столе лежит книга и я хочу убрать ее на полку. Зачем перетаскивать ее в мусорное ведро, да еще и в другом конце комнаты?

Но прогресс не стоял бы на месте, если бы в Гугле не придумали как сделать хуже чем было в Windows в 95-м году. Ярлык сейчас от приложения визуально не отличается совершенно:

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

Полноты ради, у Windows Phone та же проблема, но с десктопа нельзя приложение совсем удалить, только ярлык. Плюс в том, что хотя бы не нужно принимать решение (Удалить или Удалить?). Это значит, что человек сможет пользоваться своим телефоном без услуги «звонок другу», но все-таки приложения будут копиться. Это все-таки лучше, т.к. прямо сейчас жить можно, а проблему можно решить позже — очень правильное качество «скромных» интерфейсов.

В обсуждении просьба помнить, что это пост о конкретном интерфейсном аспекте, а не о том, что «Андроид говно» (конечно говно, даже windows phone лучше). Просто единственный нормальный пример, где нет этой проблемы — iOS, в остальных местах она везде есть (особенно на десктопах). Также, не надо мне пожалуйста рассказывать что я тупой и не разобрался. Или что вы лично разобрались и не видите проблемы. Это искаженное восприятие и отсутствие эмпатии. Некоторые вещи должны (и могут) быть сделаны так, чтобы не надо было разбираться.

Ну, за независимость!
усы2
tonsky
Управлять зависимостями трудно. Сам факт того, что такое понятие существует, уже намекает, что там есть с чем повозиться (простите мне чересчур мягкую формулировку, я сдерживаюсь изо всех сил). Поэтому один из важных принципов разработки — не тащить зависимости. В институте учат, что переиспользование кода — главная благодетель (и эта мантра наделала ужас сколько вреда). Так вот, переиспользование кода из сторонней библиотеки — тот случай, когда копипаста лучше переиспользования. Бывают библиотеки, большие и сложные, дающие бонус только целиком. Зато он существенный, и без него никак. Тяжелая алгоритмика. Новые качества. Тогда — конечно. А всякие утилитки, функции на десять строчек, удобные оберточки над платформой — задумайтесь, выдохните, и не тащите. Скопируйте одну функцию, если правда удобно. Я уж не говорю про случаи, когда быстрее сделать, чем искать, какая библиотека это уже делает (может это форма социализации такая, использовать чужой код, даже тривиальный? Или неуверенность в себе?). Особо остро выделяются свалки, где много разного, а раз много, значит каждый релиз что-то да сломают. Вообще, жалко, что так мало у нас ценят законченные вещи. Доделанные до конца, с фиксированым скоупом, а значит стабильные, не меняющиеся.

Есть еще конфликты версий, но с этим проще. Следуйте принципу: в своем приложении творите что душе угодно, тащите сколько угодно хлама, если времени не жалко потом его обновлять. Но если делаете библиотеку — у нее не должно быть зависимостей. От слова совсем. Полностью самодостаточный код, зависит только от платформы. Да, несправедливо по отношению к авторам библиотек — не вкусить им плодов прогресса и удобства опенсорсных АПИ (гг). Но эта жертва сэкономит непропорционально много времени всем остальным. Если подумать не о себе, а о потенциальных заинтересованных в библиотеке коллегах, понятно, что у них в приложении организационные вопросы уже как-то решены — логирование, коммуникация, хранение стейта, управление подсистемами, event loop. Если автор библиотеки пытается что-то из этого заиспользовать в библиотеке (упростить жизнь себе? помочь пользователям?), очевидно, он будет только мешать. Крепитесь, это не так уж страшно.

без темы
усы2
tonsky
Вслед http://ermouth.livejournal.com/641180.html

На меня вот эта книга очень сильно повлияла в детстве:



Программировать она не учит, практически совсем. Листинги есть в каждом совете, но их как-то особо не разбирают даже. Там истории интереснее, чем собственно программы — как и должно быть, по идее. То Мцыри печатают, то ищут стратегию для тройственной дуэли из «Хороший, плохой, злой», иногда просто байки травят. Интересно не само программирование, а то, вокруг чего оно используется.

После этого читать «конструктор это метод который...» понятно что нужно, но скууууучно. Короче, такая книжка у каждого программиста должна быть в детстве, чтобы не вырасти неадекватным гиком. Да и вообще, разве может быть плохой книжка, в которой авторы то и дело дают взаимоисключающие советы, а оглавление напечатали на корешке только ради того, чтобы получилось 256 страниц?

Om, React и все-все-все
усы2
tonsky

React.js — это библиотека, недавно перевернувшая JavaScript-мир с ног на голову одной простой идеей: давайте оперировать не настоящими DOM-елементами, а JavaScript-классами, их изображающими. Синхронизацию этого симулякра с настоящим браузером отдадим библиотеке.

Оказалось, что если не трогать DOM, работать с объектами настолько дешево, что можно их даже не хранить, а всегда дергать функцию state → pseudo-dom, генерирующую каждый раз новый псевдо-дом. Это первое достижение React-а, сделать двухстороннюю синхронизацию данные-интерфейс односторонней. Мы всегда пишем, как из данных получить UI, и никогда не пишем, как один UI перевести в другой UI. На всякий случай еще раз: подход «а вот по этой кнопке скроем панельку» был хорош до определенного предела, пока сайт был в основном статический, с немного меняющимися частями. Сейчас же то, что рисуется в браузере при загрузке, и то, что там будет через 2 минуты, может не иметь ничего общего вообще. Поэтому подход «дайвайте опишем, как эволюционирует наш DOM» работает не очень из-за комбинаторного взрыва — слишком много способов, как он может эволюционировать.

Второе достижение React-а в том, что они реализовали концепцию «всё свое ношу с собой», придав компонентам адекватную стандартизованную структуру. Грубо говоря, в момент создания компонент сам себе нужные ресурсы выделяет и сохраняет в собственный стейт (таймеры, сокеты, листенеры), а в момент удаления — прибирает. Звучит банально, но здесь важен факт, что это есть. Ситуация аналогична проблеме неймспейсов — яйца выеденного не стоит, но в JS их забыли и каждый разработчик вынужден пускаться в собственную езду на лыжах по кирпичам. DOM-листенеры тоже, кстати, крепятся к компоненту, но уже самим react-ом. Это опять же важно для highly dynamic интерфейсов — руками за всеми ресурсами следить и замаешься, и не уследишь.

Получаем, что react-овские компоненты это такие маленькие самостоятельные кирпичики, которые действительно можно складывать в стену и они сами будут работать, не захламляя приложение в неожиданных местах типа регистрации в top-level listener или создаваемых, но никем не удаляемых setInterval.

В общем, я примерно всю революцию уже описал. Можно делать самодостаточные, переиспользуемые компоненты (у них честное воткнул-и-готово), очень простой и мощный data binding (произвольная js-функция state→dom, и не надо выкручиваться в ограничениях template bindinds, и вообще html с шаблоном не нужен), быстрая синхронизация с DOM. React.js маленький-легковесный и это, в целом, opinionless библиотека, которую можно совмещать с чем угодно. Используется Фейсбуком и Инстаграмом, куча баек про то, как интерфейс, переписанный на React, избавляется от проблем со скоростью отрисовки. Но главное, что с ростом количества динамических частей на странице код не начинает катиться в говно экспоненциально. В целом, прорыв уровня jQuery.

Теперь Om. Om — это ClojureScript-биндинг к React-у от David Nolen, главного коммитера ClojureScript, и первая вещь, которую надо про него понять — это не биндинг. Да, он использует React, оборачивает его в cljs интерфейсы, но на самом деле под этой (достаточно выгодной) этикеткой продает свои, совсем другие идеи.

Первая, и очень хорошая — зачем нам рендериться на любое изменение стейта, давайте рендериться один раз на requestAnimationFrame. Мы получим те же 60 fps и еще меньшую площадь соприкосновения с DOM, чем в React. За счет этого, например, он обогнал голый React в ToDoMVC перфтесте.

Вторая — это управление состоянием всего приложения. Если React в этом смысле был достаточно нейтрален (ну, ты, это, давай там сам как-нибудь сам), то у Om насчет состояния вполне четкие планы. Дэвид постаралася сохранить переиспользуемость компонентов и добавить несколько бонусов сверху. Если коротко, то состояние всех компонентов в Om — один большой atom, персистентная структура, а все компоненты получают view в эту структуру на то место, которое относится непосредственно к ним. Т.е. они получают такой handle (в терминах om — cursor), через который они могут читать кусочек этого большого дерева и писать в него. Если компоненту нужны подкомпоненты, можно создать под-курсор из своего курсора, например.

Эта простая идея дает два бенефита:

Возможность показывать в разных местах страницы одно и то же значение буквально. То есть, например, слева у нас таблица сотрудников (компонент table), и в колонке «пол» у них или Man, или Woman. А справа у нас text input для редактирования списка полов, и мы в нем меням Man на Male и в таблице все автоматом синхронизируется. Потому что оба компонента работают с курсором на одно и то же место в атоме. Из-за особенностей реализации text inputs в React можно даже сделать так, чтобы значение в таблице менялось по мере набора букв в text input.

Второй бенефит — это возможность работать с состоянием всего приложения как с чем-то цельным. Это персистентная структура, значит можно хранить историю состояний всего на 100 (например) версий назад, каждое состояние иммутабельное и все они разделяют кусочки, которые не изменялись. Элементарно прикручивается undo, причем на любую версию: ты просто говоришь om-у (а он react-у): давай сейчас вот это состояние рисуй, и react сам вычисляет оптимальный способ его нарисовать из текущего dom: где что удалить/добавить/подвинуть, чтобы получить целевое. Палитру History в фотошопе видели? Короче, бонус в том, что управление состоянием вынесено за скобки. Можно прикрутить логику синхронизации с сервером, undo, сохранение в localStorage — и всё это будет снаружи и отдельно, а не размазано по GUI-компонентам.

Там не всё идеально — документация мутная, курсоры излишне хитрожопо сделаны и не описаны (точнее, сейчас описаны, но это я доку написал), и вообще оно alpha. Но это уже, наверное, самый востребованный GUI-фреймворк на cljs — я думаю, из-за hype вокруг React + имени создателя. Я не то чтобы рекомендую, я скорее популяризирую хорошие идеи, плюс это еще такой спасательный круг тем, кто полезет в Om разбираться.

Вот здесь можно посмотреть на мои эксперименты: игра «41 носок» вживую, исходники.


Скобочки (извините)
усы2
tonsky
Про синтаксис Clojure.

Мне кажется глупым что-то из этого обсуждать, но я знаю, что для многих это настоящая проблема. Не завихрение ума, не отмазка, а прямо проблема. Реальная проблема, когда синтаксис не C-like. Я посмеивался, но конечно это неправильно. Надо объяснять, а не издеваться.

Итак.

Clojure — это LISP.

Clojure — это современный LISP. Читай — многое сделано лучше и изящней. Например, сильно сокращено количество скобок. Скобки разного назначения различаются визуально.

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

Если бы это был не LISP, большая часть плюсов у языка бы осталась.

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

Подставьте в предыдущий абзац OCaml, если вы знаете Ruby или пример вас не убедил.

Синтаксиса меньше и он более последовательный, чем в других языках (и Ruby, и Python, и OCaml какой-нибудь с Haskell). Читай — меньше учить, проще использовать, сложнее ошибиться или сказать что-нибудь не то.

Единственный язык, к которому я слышал столь нерациональные претензии по синтаксису — это значащие пробелы в Python.

Хороший синтаксис не сделает вашу систему надежнее, производительнее, проще в понимании (кроме совсем уж глупых косяков вроде global vars by default в JS). Мы говорим здесь исключительно о вкусах.

Представьте, что вы написали сервис на Clojure и запустили. Потом переписали его по функционалу 1-в-1 на Ruby и тоже запустили. Потом пришел я и начал пользоваться. Сколько плюсов/минусов я испытаю благодаря тому, что сервис написан в синтаксисе Ruby, а не Clojure? Не из-за того что VM разные, или библиотеки разные, или скорость исполнения разная, а именно из-за того что используется именно синтаксис Ruby? Чувствуете, насколько существеннен синтаксис, как сильно он влияет на продукт?

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

Синтаксис Clojure не сложен для восприятия, не содержит неявных умолчаний, очень компактен, целен, последователен, подходит для хранения как данных, так и программ. Стандартная библиотека позволяет писать код компактнее чем где-либо еще (ок, кроме J). Из всех отпугивающих недостатков объективно можно согласиться только с непривычностью.

Все написанное касается не только Clojure, конечно, а любого синтаксиса вообще. Я готов обсуждать особенности синтаксиса, которые увеличивают/уменьшают количество ошибок на единицу кода. Знакомость, привычность и эстетику я не готов обсуждать (не хочу грубить).

Это как с девушками — конечно, всем приятно когда она красивая, опрятно одевается. А теперь представьте, что вам нужно строить мост, и вы ее собеседуете к себе в команду инженером. Так вот, программисты почему-то любят пообсуждать красоту девушек, а не то, как строить мост.

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

И еще раз: Clojure не страдает никакими проблемами из-за своего синтаксиса. Она страдает от ожиданий и предрассудков, которые люди этому синтаксису приписывают.

Introducing net.async.tcp
усы2
tonsky
Мне тут понадобилось гонять данные между нодами. В принципе, в концепцию укладывались любые протоколы — tcp, amqp, zeromq, websocket, и даже идея сделать заменяемые протоколы. Ну вот, я решил начать с TCP/IP.

Идея сделать максимально высокоуровневый интерфейс к спрятанному под него протоколу. То есть:

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

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

В случае TCP это вылилось в:

  1. Сообщения, не стриминг. Все равно почти всегда нужны сообщения. С ними также хорошо что — оно либо дошло всё, либо не дошло совсем.

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

  3. Стабильный интерфейс: безо всех этих вот выкидываемых Socket, ручного менеджмента объектов и скоупов. Если библиотека выдала мне handle, он будет валидным до самого конца, пока я его не закрою. Мне не нужно следить, как он себя чувствует и не поломался ли он.

  4. Автоматическое обнаружение разрывов и подвисших соединений через heartbeats.

  5. Отсутствие exceptions: разрывы это нормально и ожидаемо, это корректный state сокета, полноценно встроенный в интерфейс на базовом уровне.

  6. Естественно, это всё на nio и селекторе, то есть в одном потоке независимо от количества клиентов (плюс пул горутин из core.async, конечно).

Сам интерфейс выглядит таким образом:

    (require '[clojure.core.async :refer [<!! >!! close!]])
    (use 'net.async.tcp)

    (event-loop) → <event-loop>

    // client

    (connect <event-loop> {:host "127.0.0.1" :port 9988}) → <client-socket>
    (<!! read-chan) → :connected | :disconnected | :closed | <payload>
    (>!! write-chan <payload>)
    (close! write-chan)

    // server

    (accept <event-loop> {:port 9988}) → <accept-socket>
    (<!! accept-chan)                  → <client-socket>
    (close! accept-chan)

    // shutdown

    (shutdown! <event-loop>)

Здесь подразумевается, что

    <client-socket> is { :read-chan  <chan>
                         :write-chan <chan> }
    <accept-socket> is { :accept-chan  }
    <payload>       is byte[]

Интерфейс работы с библиотекой — честные core.async каналы, я их никуда не прятал, чтобы с ними работали все нужные фишки типа go-блоков, alts!! и так далее.

Пройдемся по реализации, заодно расскажу про то что сейчас модно в Кложе.

  • В момент создания (connect/accept) сокет добавляется в event-loop и находится там до момента закрытия.

  • Внутреннее представление сокета разделено на две части, см. картинку.



  • Левая половина — это user-facing каналы и :state. Эта часть полностью декларативная, ее можно менять, править, но никаких непосредственных действий в ней не выполняется. Это только хинты, декларация намерений.

  • Правая — это ByteBuffers на запись и на чтение, инстанс java.nio.channels.SocketChannel (net-chan), время последнего чтения. С правой частью синхорнно работает event loop. Работа очень простая: есть read-bufs? Пытаемся в них читать. Есть write-bufs? Пытаемся их записать в net-chan. Нету/сдох net-chan? Пересоздаем и коннектим (клиентский сокет) или закрываем и удаляем сокет (серверный).

  • Вся работа с сетевой частью происходит только в event loop. Это сильно помогает, потому что все потенциально опасные, дохнущие операции сосредоточены в одной функции.

  • В случае, когда event-loop-у удалось достичь чего-нибудь существенного (заполнил read-bufs, слил write-bufs, приконнектился, соединение порвалось), он дергает callback. Callback как правило обновляет состояние сокета и шедулит асинхронную горутину. Например, в случае завершившегося чтения он ставит сокету состояние «пока не надо читать», а в горутине пытается засунуть прочитанное сообщение в клиентский канал и после этого выставить сокету состояние «можно опять читать».

  • Event-loop подписан на изменение состояния всех зарегистрированных сокетов (внутренних, не сетевых) через add-watch. Как только состояние сокета меняется, синхронно или асинхронно, selector просыпается из ожидания и переосмысляет, что ему нужно делать теперь. Иногда он просыпается зря, зато просто и железобетонно.

  • Backpressure сделан за счет тех самых горутин. Так как внешни интерфейсом являются core.async каналы, внутри мы из них же асинхронно читаем/пишем и не блокируем при этом никаких лишних потоков. Пока сообщение не протолкнулось в пользовательский канал, мы не шедулим чтение из сокета в селектор. Аналогично с записью, пока мы не слили в сетевой сокет данные, клиент будет заблокирован на записи во write-chan.

  • Backpressure можно управлять, регулируя размер и тип буфера под read/write-chans. Например, можно дропать неуспевшие отправиться сообщения (sliding-buffer) или дропать вновь поступающие (dropping-buffer).

  • Нет глобального состояния, event лупов можно насоздавать сколько угодно. Это используется в тестах для эмуляции нескольких хостов. Я даже написал пару функциональных тестов.

  • Мутабельного состояния, наоборот, куча. Все постоянно меняется, специфика такая. Это очень тяжело, пока не придумаешь правильных границ и зон ответственности.

  • Есть ощущение, что реализация могла бы быть еще проще. Я переписал текущую пару раз, пока она не стала достаточно хороша, чтобы я смог понять и объяснить, что с чем связано. Критерием успеха для меня стало то, что последние две фичи — закрытие канала и heartbeats — я написал за полчаса и ничего не сломал.

  • core.async это один сплошной вин. Очень просто работать, хотя внутри шайтанство какое-то, конечно. Я получил от него профит даже при том, что использовал его только для общения с клиентом.

  • Интерфейс я придумал сразу и почти не менял, он очень естественный получился.

  • Протокол сообщения — 4 байта размер сообщения и само сообщение. Для меня это пока good enough. Можно поставить любой другой байтоебский протокол, но это чуть лишней работы, чтобы абстрагироваться в нужно месте, мне пока не важно.

  • Текущая модель местами неоптимальна в мелочах. Простота реализации этого стоит. Я бы даже сказал, выбор идет между «или так, или вообще никак». Сделать подобное на Джаве близко к невозможному. На фоне сетевых задержек, я думаю, вообще никто ничего не заметит.


Репозиторий можно посмотреть тут: https://github.com/tonsky/net.async. Там же есть пример echo server прямо в readme.

Работать это дело как-то работает, но, сами понимаете, как — только вчера дописал. Зато интерфейс получился очень уж удобный. Стабилизировать его — и никакой Erlang не нужен, вперед сетевые сервисы клепать.

on etcd
усы2
tonsky
Я опять наткнулся на https://github.com/coreos/etcd (легкий Zookeeper, написанный на гошечке) и считаю нужным сказать.

Программисты давно в большинстве своем обленились. Идея тонкого клиента настолько разъела мозг, что думать об уместности никто не хочет. Мы хотим http-api (желательно REST), потому что вдруг мне понадобится распределенный кластер из javascript-страниц, а вся сложная логика пусть будет в серверной части библиотеки.

(на самом деле, конечно, http берут не от хорошей жизни, а от того, что tcp неудобный, а zeromq слишком маргинальный и тоже не без проблем, а больше ничего универсального как-то и нет)

Я в принципе не против. Приятно, когда чтобы заиспользовать тот или иной сервер, не нужно никаких зависимостей и клиентских библиотек, достаточно http-клиента. Но только если это _уместно_.

Так вот, consistent distributed storage это как раз тот случай, когда тонким клиентом не обойтись. В распределенной среде даже клиенты — полноценные участники событий, и требуют чего-то более изобретательного, чем long-polling. В самом деле, на кой черт городить огород со всей этой strong consistency, если я не могу достоверно получить последнее значение по ключу?

Если сделать
  GET /key
  GET /key/watch

то значение может поменяться между вызовами и мы об этом никогда не узнаем.

Если сделать
  GET /key/watch
  GET /key

(в разных коннекциях, потому что первый вызов блокируется до первого change по ключу), то вообще говоря ответы надо как-то упорядочить, и то что A пришло вперед B не значит, что они именно в таком порядке отправились с сервера.

Да даже банальное переподписывание после того, как выстрелил /key/watch, непонятно как сделать — пока я переподписываюсь, значение того и гляди поменяется.

Я верю, что сервер-сайд у них клевый и алгоритм Raft определенно стоит внимания, но они просто в трубу слили все усилия, постаравшись сделать клиента тупым. У Zookeeper та же проблема.

В правильной реализации клиент должен быть полноценной нодой, работать в пространстве того процесса, который данные из него и потребляет. Понятно, что это означает переписывать для каждого языка заново, но зато это закрывает все вопросы по distributed consistent storage. А если закрывать их не все, то я не вижу смысла даже начинать что-то делать. Используйте БД с master-slave тогда, ничем не хуже.

без темы
усы2
tonsky
Простейший парсер опций командной строки на Clojure выглядит как-то так:
  (ns optlist.core)

  (defn -main [& { role    "-role"
                   name    "-name"
                   threads "-p"
                  :or {
                   role    ".*"
                   name    "default" }}]
    (println "Opts:" role name threads))

  $> lein run -m optlist.core -p 10 -role abc
  Opts: abc default 10

Позиционные аргументы тоже можно:
  (defn -main [& [arg1 arg2 arg3 & rest]]
    (println "Opts:" arg1 arg2 arg3 rest))

  $> lein run -m optlist.core -a -b
  Opts: -a -b nil nil

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

Clojure impresses
усы2
tonsky
Нашему Clojure-проекту исполнилось полгода, из них последние три месяца его используют заказчики. Пора подводить промежуточные итоги.

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

С одной, это правильный Питон — писать быстро, динамично, код компактный, легкий, лаконичный. Куда более лаконичный, чем питоновский, за счет ФП, макросов и прекрасной стандартной библиотеки.

Удобный перочинный нож на каждый день. Присылают .docx файл с табличкой — копирую его в Sublime, парой регэкспов подчищаю до регулярного формата (в sublime я этому научился-таки, а в vim регэкспы запомнить так и не могу, к вопросу о юзабилити vim-а), потом тремя-пятью трехстрочными функциями паршу и генерю что мне нужно.

Короче, когнитивного оверхеда никакого нет (особенно если repl уже запущен), и мне кажется, вообще всё надо на Кложе писать. Какой-нибудь енвайромент соорудить заместо bash, в котором все будет просто, логично и все главные нужные, но забытые в линуксе вещи будут, какой-нибудь feature-full process/task management framework, желательно network-transparent; и оттуда строить светлое будущее.

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

Плюс скорость. Тут сразу получается чуть хуже, чем у Скалы (по слухам) и голой Джавы, и иногда для разгона надо приложить усилия — типы кое-где дописать, инты анбокснуть, ленивости убрать или, наоборот, добавить. Написать джава-класс, в конце концов. Я в этом плохого ничего не вижу, потому что мы уже получили два в одном — быстрый прототип можно плавно и инкрементально перевести в производительное промышленное приложение. Такой мостик, которого нет у Питона, Руби (мы же не считаем всерьез мостиком возможность «переписать на C»?), Джавы.

С третьей стороны, и совсем не так давно, Кложа — это еще и правильный Джаваскрипт. Я на 100% оценил идею, что код сервера и клиента должны быть на одном языке, только кто решил, что это должен быть Джаваскрипт?

На бытовом уровне ClojureScript круче js хотя бы потому, что в js ты постоянно вошкаешься в поисках библиотеки, объединяющей строки или еще чего-то банального, но забытого разработчиками Netscape. Ну, по крайней мере, пока ты не профессионал и не написал свой utils.js. В cljs все сразу под рукой в стандартной библиотеке.

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

Ну и он, надо сказать, прёт вперед с каждым месяцем. Сейчас source maps прикручивают, скоро никаких аргументов за js не останется.

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

Вот чему я научился бы, свяжи я свою карьеру с Питоном? The Zen of Python (хорошая штука, чего уж там), Джанге, WSGI и PyPy? А если бы с Руби, так вообще нахватался бы плохого. Вот в нашем Кложе-мире много новых взглядов на старые проблемы. Они, хоть и практичный язык, на самом деле на Джаве только бутстрапились, а сейчас потихоньку всё переписывают. Datomic, core.logic, reducers, cljs, enlive, lighttable, в самом языке stm, protocols, подход к стейту, много интересного короче и пищи для размышлений. Не любой код на Кложе — идеал, конечно, но это тоже работает в плюс — смотришь на хорошее, смотришь на плохое, анализируешь, почему так и в чем существенная разница. Да даже просто наблюдение сложности maven на фоне изящного lein подарило мне немало минут медитации об устройстве мира. Правда, как они ТАК всё зафакапили на ровном месте?

Тут секрет еще в чем? Чтобы выучить Кложу, надо посмотреть хотя бы пяток лекций Рича Хики. То есть, можно конечно и по-другому выучить, но тогда я не понимаю в чем смысл. А Рич Хики крутой чувак и все время правильные вещи говорит, универсальные. Если и есть надежда, что кто-то нашу software industry спасет, так это может быть он. Ну не DHH же в конце концов. И, давайте будем реалистичными, не Хаскель. Хаскель больше про объяснения и рефлексию, чем про спасение.

В лекции про муравьев, например, Рич раскрыл практическую сторону Кложи — симуляцию колонии муравьев можно сделать и на Джаве (JVM же общая), но очень сложно. Кложа дает правильные примитивы, в терминах которых можно мыслить — stm, транзакции, стейт, его изменение и синхронизация (например, традиционные actors здесь называются agents, плюс много других concurrent примитивов). Когда этого нет, никто конечно не мешает их написать, но люди, у которых есть жизнь, скорее вляпают глобальный лок на время рендеринга или еще какую-нибудь гадость. Потому что просто и идиоматично для Джавы. И потом будут бодаться с бизнесом — и тормозит, и ненадежно, и исправляется только выбрасыванием. Чтобы сделать правильно, надо было с самого начала правильно идти по довольно тонкой тропке выверенных решений; в условиях реальных проектов кто ж так сумеет? Нужен правильный инструмент.

Пользуясь случаем, хочу сказать, что мыслить в терминах Кложи про стейт и его изменение легко, удобно, и трудно ошибиться. Мне когда-то казалось, что STM это что-то толстое, серьезное и страшное, вроде БД, но нет, удобное и понятное, и пригождается даже в мелочах.

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

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

Макросы — простые и понятные пушистые зверьки, позволяющие кое-где срезать углы или отложить вычисления. Адское непонянтное программирование на них (как в Midje) считается какашкой, обычно за макросом просматривается все, во что он разворачивается.

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

С синтаксисом всё в порядке — управляющих конструкций больше, чем в любом языке, что я видел (любят заточенные под разные случаи частные случаи макросы вроде ->> или condp).

И немного приятных мелочей. Кложа — это действительно очень прикольный язык трансформации данных. Его надо каким-то DSL, мне кажется, оформить и во все остальные языки встраивать, или в базы данных, или в NoSQL вместо js.

(->>
  (str "http://api.twitter.com/1/users/lookup.json?screen_name=" (str/join "," names))
  slurp
  json/read-json
  (map (juxt (comp str/lower-case :screen_name) :id_str))
  (into {}))

(reduce #(min %1 (@emitted %2 0)) limit ins)

(reduce #(%2 %1) (update-in record [:rules] conj name) actions)

(reduce (fn [s [f t r]] (str (subs s 0 f) r (subs s t)))
        (:text tweet)
        (reverse (sort replacements)))

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

Это нормальные-дефисы-в-именах вместо уродливых_подчеркиваний или ВерблюжьегоРегистра, да и вообще много прикольных спецсимволов в именах можно использовать. ->> например вполне валидный идентификатор.

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


Clojure@Echo 02 Библиотеки, web-стек, ClojureScript
усы2
tonsky
Третья лекция про язык Clojure. Лекция будет полезна всем интересующимся пригодностью Clojure на практике (батарейками) и задающимся вопросом «а не написали ли на ней killer-app уже?». Где-то после первого часа начинаются вопросы, не знаю, насколько там все разборчиво. Видео в этот раз особенно урывочное (фотоаппарат барахлил), но немного есть.

Видео:


Слайды можно посмотреть отдельно на Спикердеке, а можно скачать pdf. Видео можно скачать на Vimeo.

Предыдущие лекции выложены тут и тут.

The Joy of Clojure
tonsky
Дочитал The Joy of Clojure авторов, которых я уже практически в лицо узнаю — активные деятели коммьюнити.

Это не введение в язык, а скорее обзор основных особенностей, фич, плюс рекомендации по использованию — на мой вкус, самый правильный жанр. В других, более худших книгах, бывает, распишут, что вот такое есть, и еще такое, и так можно, и сяк; если повезет, то даже пример разберут, но обязательно высосанный из пальца. А тут лучше, тут пишут, для чего каждая фича нужна, когда использовать и когда не использовать. Встречаются даже наставления, что является идиоматическим кодом на Кложе. Мне кажется, один из важнейших вопросов для изучающих язык.

Клевая, в общем, книжка, хоть в основном и про направление (об этом честно предупреждают в начале); чтобы обрасти «мясом», желательно параллельно читать документацию по стандартной библиотеке и код каких-нибудь проектов.

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

без темы
tonsky
Хм, Vim, значит, пишет полностью транзакционный лог всех операций и может восстановить после падения все изменения, которые я налабал, однако сохранять файл надо вручную. Автосейва нет даже как опции. В чем прикол? Они там вообще следят за ui/ux литературой 20-летней давности, ну или хотя бы за сегодняшним мейнстримом, развиваются, гонятся как-то?

Итак, что же я думаю по поводу Clojure?
tonsky
Расширяемый чат-бот [1] оказался куда более толковым для освоения языка материалом, чем Проект Эйлер. Я потрогал практически всё, что было в Кложе, и почти не повторялся.

Страх перед скобочками сильно преувеличен. Их перестаешь замечать в первый день, куча гораздо более важного происходит внутри них. Пишешь себе код как везде, выравниваешь, копи-пастишь, читаешь, никаких проблем. Ставить их вокруг функции намного логичнее, чем часть выражения оставлять снаружи, часть внутри, и не дай бог можно опускать. Мне когда-то это казалось отличной идеей, а-ля ML, Coffee Script, Perl — мусора меньше, но потом я как-то увидел чужой исходник на Ocaml-е — пространство, равномерно заполненное двух- и трех-буквенными аббревиатурами, и понял, что скобки, они к лучшему, не мешают, а помогают читать.

Синтаксис в Кложе самый крутой, конструкции навернуты невероятные [2], но если поначалу тупишь [2], то потом все отлично, привыкаешь. Причем чтобы с этим всем разобраться, надо ковырять стандартную библиотеку, а не книгу по языку, что тоже довольно скоро начинает казаться естественным и логичным. И даже, разделение элементов списка пробелами, без запятых — как-то вдруг свежо, стройно и красиво решает проблему их назойливого болтания в начале и конце.

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

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

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

Если об ощущениях, то это выглядит так: сидишь, думаешь, здесь так должно работать, а в этих случаях так, и если это, то то, и еще в этом случае надо вот это учесть, потом думаешь: ну всё, надо уже писать. Пишешь, пишешь. Проверяешь, пробегаешься по всем случаям, что продумал. Да, вроде всё учтено. Оглядываешься — написал четыре строчки.

Если еще об ощущениях, то чем больше я погружаюсь, тем больше паззл сходится. Последний раз такое было с Эрленгом, в котором все части очень грамотно и красиво дополняли друг друга, но там это укладывалось в одну нетолстую книжку. Здесь все началось с Рича Хики, который сначала рекомендовал всем не быть хипстерами, а выявлять настоящие проблемы и решать их [3]; продолжилось им же в толке про деконструкцию устоявшихся программистских конструкций на элементарные ортогональные части [4], и блестящим воплощением этих идей по всей Кложе.

Ну и в конце концов, из Кложи, возможно ввиду ее молодости, еще не высосали весь фан. Я, например, оторвался с антропоморфными названиями частей чатбота и лог-функциями типа (omg! …). Если попытаться представить что-то подобное в другом языке, вдруг понимаешь, что в Джаве запрещены имена классов из менее чем трех слов, в Скале названия берут из математических теорем, на Хаскелле пришлось бы называть их по-гречески (что круто, но крутизну оценили бы те восемь человек, которые его используют)1. На Питоне пришлось бы соревноваться в остроумии с Гвидо, чтобы писать на С, нужна борода, а на Ноде-ЖС, если давать осмысленные имена, велик риск обнаружить, что задница управляет мозгом через коллбеки. Получается, что единственное место, где можно ловить кайф по части названий — это Кложа.

Когда я узнал, что они всю эту красоту еще и в браузер перенесли (в очередной раз сделав это очень умно и по делу — [5]), стало понятно, что деваться некуда, скоро все будут писать на Кложе, поэтому надо работать на опережение. Я, в общем-то, даже с JVM уже примирился, хотя нечаянно закрыть repl пока ощутимо больно. Oracle, давайте уже допиливайте свою модульность и быстрый старт, будет с вас хоть шерсти клок.

Резюме: всем брать пример с Рича Хики. Кложа опционально, но горячо рекомендую.

Ссылки:
  1. Разбор кода чат-бота, in english.
  2. Пример одной из сложнейших конструкций condp.
  3. Мини-конспект первой презентации Рича Хики про решение проблем.
  4. Конспект второй презентации Рича Хики про элементарные ортогональные части.
  5. Introduction Стюарта Сиерры и видео Рича Хики про ClojureScript, in english.


1 На самом деле, если мне когда-либо доведется что-либо писать на Хаскелле, абсолютно точно первый свой тип я назову Пиписька. Просто чтобы сбить пафос.

Новогодние подарки
усы2
tonsky

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

Условие простое — книгу, которую вы хотите забрать, вы еще не читали, и обязательно прочитаете (а не поставите на полку «для коллекции»).

Самовывоз со станции метро Красный проспект. Звоните +7 983 135-08-35 (Никита, UPD: номер исправлен :) с 1300 до 2400, отдаю вплоть до 7 января включительно, потом я уеду; кто первый договорится забрать, того и тапки.

UPD2 Что сумел, раздал, аукцион закрыт.

Про сами книги можно почитать щелчком по картинке (ссылки на books.ru) или (про некоторые) по тегу книги.

Художественное и около

Научпоп

Дизайн вообще

Проектирование интерфейсов

Около программирования


без темы
tonsky
Пишет Роман Волобуев:
Извините, я немножко устал произносить один и тот же набор банальностей, поэтому напишу, чтоб не возвращаться.

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

Сейчас — наше издание 91-го года. Что большое везение — не каждое поколение получает такую возможность, нам вообще дико везет. Мы не идиоты, мы все понимаем. Немцов — придурок, Навальный — Памела Андерсон, остальных вообще не существует. Любую победу в итоге крадут, особенно здесь. И что мы сейчас отгрызем — не очень понятно. В 91-м все тоже рассчитывали на большее, чем просто возможность ездить заграницу и читать любые книжки. Тогда получили заграницу и книжки, теперь — что-то еще. И да — потом все перегрызутся, раздружатся и сто раз посыплют голову пеплом. Но, может быть, нам пару лет не будет стыдно за наши уродливые паспорта. И мы будем немножко про себя понимать, кто есть кто. Девчонки, понятно, не в счет. Извините, еще раз.

Больше не буду.

Меня во всем сложившемся расстраивает то, что вот эти правила, которые вроде как законы, нарушаются и ничего не происходит. Не в ситуации, когда бабушка надвое сказала, а в ситуациях кристальной ясности. Не «всё плохо из-за президента, потому что он плохо руководит», а конкретные нарушения на местах. По уровню обид, эмоций, тупизны и приемчикам происходящее вокруг голосования напоминает школу, наверное, только места школьников заняли дяди и тёти из ИК.

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

Кто там кого выбрал и нравится мне это или нет — я согласен на любое, но пусть это будет честно.

Точка зрения «давайте сидеть дома и оставим всё как есть, потому что непонятно, что из этого выйдет» — ну, это не способ выражать свой протест, скажем так. Тем более что потом будет нужен новый повод.

Главное сейчас — не начинать читать Кафку.


Картинка из ленты nponeccop