-
Мультиметоды в Кложе
Возможно, я упоминал, что недавно сменил проект и теперь пишу на Кложе. Это современный диалект Лиспа под Джава-машину. Кложа довольно проста, и многие задачи в ней решаются исключительно функциями и коллекциями. Так, на собеседованиях в проект я беседовал с разработчиками, которые ничего кроме словарей и коробочных функций не знали.
Не скажу, чтобы это плохо: язык должен быть простым. Чем меньше правил и бест-практик нужно помнить, тем легче на нем писать. Однако, есть в Кложе классные штуки, точечное применение которых сэкономит время и объем кода.
С сегодняшнего для я решил коротко описывать Кложные фишки, на изучение которых вечно не хватает времени. В сегодняшнем выпуске речь пойдет о мультиметодах. Но сначала коротко о том, что такое мультиметоды вообще.
Мы знаем, что ООП базируется на инкапсуляции, наследовании и полиморфизме. Рассмотрим последний. Полиморфизм – это когда у метода может быть несколько реализаций. Конкретная реализация выбирается в зависимости от типов параметров.
В классическом ООП нам бы привели такой пример. У класса
Geometry
есть метод square для вычисления фигур. На вход могут подать окружность, квадрат и треугольник. Запишу на каком-то выдуманном языке:class Geometry: real square(Circle c): return Math.PI * c.radius * c.radius; real square(Rectangle rect): return rect.a * rect.b; real square(Triangle tri): real p = (tri.a + tri.b + tri.c) / 2; return Math.sqrt(p * (p - tri.a) * (p - tri.b) * (p - tri.c))
В зависимости от типа фигуры подбирается нужный метод. Не может быть два метода с одинаковым набором параметров, даже если их имена различаются. Если передать совершенно другой объект, например,
Rombus
, будет ошибка, причем еще на этапе компиляции.Уверен, каждый помнит это с университетских времен. Но полиморфизм на типах – всего лишь частный случай мультиметодов. В отличии от полиморфизма, мультиметоды не прибиты гвоздями к ООП, а значит, дают большую свободу в идеи и реализации.
Для адептов ООП это, возможно, прозвучит сюпризом, но мультиметоды существуют во многих языках, в т.ч. в которых объектов не существует. Почти любой функциональный язык поддерживает множественные клозы (clause) для функций. Но если в ООП все сводится к типам, то в функциональных языках действует более мощный механизм подбора основанный на паттерн-матчинге.
Рассмотрим Хаскель. То, что выше я записал на выдуманном языке, в Хаскеле будет так:
data Circle = Circle Float data Rectangle = Rectangle Float Float data Triangle = Triangle Float Float Float square :: Circle -> Float square (Circle r) = 3.1415 * r * r square :: Rectangle -> Float square (Rectangle a b) = a * b square :: Triangle -> Float square (Triangle a b c) = sqrt $ p * (p - a) * (p - b) * (p - c) where p = (a + b + c) / 2
Видно, что функция
square
работает с типами окружность, прямоугольник, треугольник. В любой момент мы можем дополнить ее новой фигурой.Есть мультиметоды и в классических диалектах Лиспа. Вот, например, копипаста из Википедии, взятая из кода Астероидов:
(defmethod collide-with ((x asteroid) (y asteroid)) ;; deal with asteroid hitting asteroid ) (defmethod collide-with ((x asteroid) (y spaceship)) ;; deal with asteroid hitting spaceship ) (defmethod collide-with ((x spaceship) (y asteroid)) ;; deal with spaceship hitting asteroid ) (defmethod collide-with ((x spaceship) (y spaceship)) ;; deal with spaceship hitting spaceship )
Особенность примера с Лиспом в том, что второй параметр в каждой паре ведет себя как предикат. В данном случае тип
spaceship
срабатывает как проверка того, чтоx
– экземпляр космического корабля. Однако, вместоspaceship
можно передать другие проверки:integer?
,string?
,even?
, словом, любой унарный предикат.Столь гибкая система мультиметодов позволяет определить разное поведение для одного и того же типа, но разных значений. Например, для отрицательной суммы денег одно поведение, для нуля – второе, для положительной – третье. Для даты: выходной или нет, високосный год или нет и т.д.
Думаю, ясно теперь, чем это выгодней банального полиморфизма на типах.
В Кложе более гибкая система мультиметодов. В рассмотренных выше примерах мы не могли изменить сам принцип подбора метода, или диспатча. В Кложе, наоборот, вы обязаны определить диспатч! Рассмотрим пример:
(defmulti foo class)
Данное определение говорит: прежде чем искать реализацию, метод получит класс аргумента.
Следующее определение расширяет мульти-метод реализацией для
Long
: если передано длинное целое, получим строку"an integer"
.> (defmethod foo Long [x] "an integer") #multifn[foo 0x478c7d41] > (foo 42) "an integer"
Добавим для строки:
> (defmethod foo String [x] (format "%s is a string" x)) #multifn[foo 0x478c7d41] > (foo "test") "test is a string"
Если ни одно соответствие не подошло, будет ошибка:
> (foo nil) IllegalArgumentException No method in multimethod 'foo' for dispatch value: null clojure.lang.MultiFn.getFn (MultiFn.java:156)
Не страшно, добавим реализацию по умолчанию:
> (defmethod foo :default [x] (format "you passed %s" x)) > (foo nil) "you passed null"
Диспатч может работать самым разным способом, например, проверять на типы все аргументы. Повторим пример с площадью фигур:
(defmulti square (fn [& args] (mapv class args))) ;; rectangle (defmethod square [Long Long] [a b] (* a b)) (square 2 3) ;; 6 ;; circle (defmethod square [Double] [r] (* Math/PI r r)) (square 1.1) ;; 3.8013271108436504 ;; triangle (defmethod square [Long Long Long] [a b c] (let [p (/ (+ a b c) 2)] (Math/sqrt (* p (- p a) (- p b) (- p c))))) (square 2 2 2) ;; 1.7320508075688772
Замечу, что на самом деле Кложа проверяет значения диспатча и образца не простым сравнением, а функцией
isa?
, что подразумевает иерархию. Так, чтобы проверку проходили типыPersistentArrayMap
иPersistentHashMap
, достаточно указать базовый классclojure.lang.APersistentMap
.Наконец, проверять можно не только на типы, а банально на значения, диапазоны, предикаты и тд. В текущем проекте я проверяю последний переданный аргумент. Это может быть как айдишка, так и запись БД. В зависимости от этого условия срабатывает разная логика.
Каждый мультиметод может вызвать внутри другой, тогда процесс диспатча начнется заново.
Мультиметоды в Кложе отличаются еще и тем, что расширяемы извне. Это значит, если разработчик определил мультиметод для определенных типов, ничто не мешает расширить его до других типов. И это будет все тот же экземпляр.
Напомню ситуацию с примерами на Джаве и Хаскеле выше. Пусть фукция
square
и классGeometry
находятся в чужих библиотеах. Тогда вы никак не сможете изменить их! В лучшем случае вы унаследуете классGeometry
, добавите свой метод для ромба. Но если есть еще одна библиотека, которая работает сGeometry
, вы никак не сможете на это повлиять.Напротив, идея расширения чужих определений работает в Кложе просто убийственно. Я нигде не видел ничего подобного.
В Кложе мультиметоды используют когда трудно предугадать, какие типы данных будут поступать на вход. Это прекрасное решение для абстракций вроде работы с БД, парсингом данных, декораторов и тд.
Мультиметоды в Лиспах – мощнейшей инструмент, порой в корне меняющий принцип мышления и разработки.
-
Что почитать на выходных №6
С опозданием, но все же:
-
Вышел Питон 3.6. Статья на Хабре и раздел на официальном сайте. Очень рад за развитие языка.
-
The Next Five Years Of Clojurescript
Небольшая заметка к одноименному видео.
-
Максим Ильяхов о пользе для читателя. Очень сильная статья. Приятно, когда в голове вертится, а потом кто-то умный раз – и сформировал.
И конечно:
«Афиша Daily» поговорила с людьми, которые считают себя зависимыми от порнографии, и расспросила психотерапевтов о том, как порно влияет на нашу жизнь и как контролировать это влияние.
-
-
Руководство по кросс-доменным запросам (CORS)
На прошлой неделе я внедрял в проект CORS-запросы – современный способ кросс-доменного Аякса. По следам прочитанной документации и набитых шишек подготовил небольшой мануал. Это вольный пересказ англоязычных статей, вопросов со Стека и скромный личный опыт.
Но сначала короткая предыстория. Я неравнодушен к собеседованиям, как вы, наверное, знаете. Чаще я собеседовал бекенд, т.к. к фронтенду отношусь прохладно. Но тема фронтенда обладает огромным потенциалом для оценки уровня кандидата. Я считаю, что разработчик должен в деталях понимать протокол HTTP и тонкости работы браузера.
Один из вопросов на тему фронтенда звучит банально: как на клиенте получить данные с другого домена?
Некоторые кандидаты отвечают, что проблемы нет, достаточно выполнить Аякс-запрос. И с большим удивлением узнают, что, оказывается, нельзя: сработают какие-то там политики безопасности.
Один собеседуемый заявил, что в Фаерфоксе это работает, достаточно зайти на страницу для разработчиков и что-то там переключить. Я не против такого ответа. Следующий вопрос, как вы заставите всех пользователей установить именно Фаерфокс и поменять системный флаг?
С понятием JSONP вообще беда – никто не может объяснить, как это устроено. Разработчики думают, что это обычный Аякс, только с каким-то P на конце, то ли баг, то ли фича. А это вообще ни разу не Аякс.
Аббревиатура CORS появилась недавно, и спрашивать о ней нет смысла. Этот пробел восполняет данный мануал.
Разберем вопросы из предыдущих абзацев. Действительно, слать Аякс-запросы к серверам с другим доменом запрещено на уровне браузера. Однако, в интернете полно сайтов, где значимая часть контента подгружается со сторонних серверов. Например, этот блог работает на статичном генераторе Jekyll, в котором нет комментариев. Делиться мнениями помогает сервис Discuss: лента комментариев встраивается Джаваскриптом. Получая и отправляя комментарии, вы взаимодействуете с серверами Discuss, а мой блог вообще ни при чем. Значит, слать запросы Аяксом все же можно?
Нет, здесь работает JSONP. Аббревиатура значит JSON with Padding (с подкладкой). Идея основана на лазейке в стандартах: загружать скрипты с других доменов не запрещено! Скажем, если в файле
example.js
на чужом сервере написано что-то вроде:alert("hello!");
, то достаточно подгрузить его тегом
<script>
на страницу:<script src="http://example.com/static/example.js"></script>
и браузер выполнит все, что внутри. В данном случае покажет окошко.
Предположим теперь, что сервер отдает не статичный файл, а генерит его динамически в зависимости от параметров. Например, мы добавляем на страницу скрипт с адресом:
<script src="http://example.com/api.js?method=get_user&user_id=42&callback=processUser" ></script>
В переменой
method
указываем, какое действие требуем от сервера. В данном случае, получить пользователя по идентификатору. Айдишку передаем следующим параметромuser_id
. Пусть такой пользователь найден на сервере, теперь его нужно отдать клиенту. Если просто выплюнуть объект:{name: "Ivan", age: 30}
, то на клиенте ничего не произойдет: объект просто считается в память. Именно поэтому передают третий параметр
callback
– имя функции, объявленной на клиенте, в которую нужно завернуть пользователя. С этим параметром ответ станет другим:processUser({ name: "Ivan", age: 30 });
На клиенте отработает код, зашитый в функцию
processUser
: вывести данные в консоль, отрисовать виджет и т.д. Вот как работает JSONP.Теперь, имея полную картину, нельзя не признать, что технология очень крива. Прежде всего, это лазейка, костыль. Разработчики стандартов просто не были настолько хитры, чтобы предугадать динамическое взаимодействие на уровне скриптов.
Далее, подгрузка скриптов ни разу не безопасней, чем Аякс. Целое семейство вирусов занимается тем, что добавляет на страницу браузера скрипты для отрисовки баннеров порно и казино. Когда вы подключаетесь к интернету через мобильных операторов, обсосы вставляют в HTML-трафик скрипты для отрисовки виджетов (если соединение не HTTPS).
JSONP работает только методом GET, что сводит на нет возможности REST-интерфейса. Для REST-сервисов приходится писать прокладки-прокси, т.е. множить костыли.
Добавив скрипт на страницу, в дальнейшем вы не можете отследить его судьбу. Если у Аякс-запроса есть специальные коллбеки для основных событий (начало, удачное завершение, таймаут, неудачное завершение), то у скрипта ничего такого нет. Загрузился ли он? Ответил ли сервер? Была ли ошибка? Никто не знает.
Ясно, что в 2016 году приложениям на js нужен надежный способ забирать данные с серверов. Чтобы это была законно, а не по-воровски в обход протоколов и стандартов. Таким способом стал
CORS
–Cross-Origin Resource Sharing
, кросс-доменные запросы.Идея проста – пусть клиент шлет Аякс-запрос к чужому серверу. Браузер добавит в запрос особые заголовки с информацией о том, что запрос с другого домена. На их основании сервер решит, как обрабатывать такой запрос, и добавит особые заголовки в ответ. Удобно, правда?
Техническая реализация несколько сложнее. Стандарт CORS различает “простые” и “сложные” запросы. Простым считается запрос методами:
- HEAD
- GET
- POST
и заголовками:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type, но только со значениями:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
Если ваш запрос удовлетворяет этим критериям, можно слать Аякс к другому домену из любого современного браузера. При этом браузер добавит заголовок
Origin
с адресом страницы, откуда инициирован запрос. Подделать заголовок скриптом не удастся.Сервер, получив на обработку подобный запрос, должен прочесть Origin и решить, как его обрабатывать. Заголовок ответа
Access-Control-Allow-Origin
регулирует, с какого домена разрешено запрашивать данные. Это может быть как веб-адрес, так и знак астерикса (звездочки), если разрешено всем. Несколько разных адресов через запятую, к сожалению, не поддерживаются.Пример CORS-запроса:
POST /foo/bar HTTP/1.1 Origin: http://foreign.com Host: test.com
и ответа с разрешением на получение данных:
200 OK HTTP/1.1 Access-Control-Allow-Origin: http://foreign.com Content-Type: text/html; charset=utf-8 <h1>Welldone</h1>
Обратите внимание на такую вещь: мы намерены использовать CORS, чтобы дергать чужие API. С вероятностью почти 100% они работают по протоколу
JSON
, то есть принимают и отдают заголовокContent-Type: application/json
. Вроде бы мелочь, но такой запрос автоматом перестает быть простым и переходит в разряд “сложных”, где схема взаимодействия иная.Сложные запросы проходят в два этапа. Сначала браузер делает запрос по тому же урлу, но методом
OPTIONS
. Сервер должен ответить: какими другими методами и дополнительными заголовками (помимо стандартных) можно обращаться к этому урлу. И только получив разрешение, браузер сделает запрос на основной урл.При этом браузер не дурак и все запомнит: если разрешили только методы GET и POST, то PUT и DELETE не сработают. Аналогично с заголовками: если помимо стандартных разрешено использовать только
Authorization
, то нужно передать его и ничего другого.Пример сложного запроса:
OPTIONS /cors HTTP/1.1 Origin: http://api.bob.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
Клиент хотел отправить Аяксом запрос методом
PUT
на урлhttp://api.alice.com/cors
с сайтаhttp://api.bob.com
. Поскольку это сложный запрос, браузер запросил разрешение: типа, хочу сделать PUT на этот урл с особым заголовкомX-Custom-Header
. Сервер ему на это:200 OK HTTP/1.1 Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8
Или иными слвами: разрешено ходить методами GET, POST, PUT и с заголовком X-Custom-Header. Это подходит под критерии первоначального запроса. Браузер делает второй запрос куда мы намеревались вначале.
Первая стадия, когда делается запрос OPTION, официально называется
preflight request
. Надо сказать, такое взаимодействие весьма прозрачно отражается в браузере. Например, в консоли разработчика в Хроме видны оба запроса со всеми заголовками.Вот такие строгости. В нашем проекте API на стороне сервера требует заголовки Version (версия операции), Authorization (авторизация по токену) и Content-Type (JSON), поэтому в ответе указываем
Access-Control-Allow-Headers: Version, Authorization, Content-Type
, иначе запрос не пройдет.
Теперь все это можно протестировать. Запускаем локальный сервер, открываем консоль Хрома и пишем:
var xhr = new XMLHttpRequest(); xhr.open("GET", "http://127.0.0.1:5000/api/users", true); xhr.setRequestHeader("authorization", "Token xxxxxx"); xhr.setRequestHeader("Version", "1"); xhr.send(); xhr.responseText >> "{"users":[{name "Ivan"...
Забавно, что заголовок
Origin
в этом случае будет равенhttps://google.com
, потому что Хром считает пустой страницей главную Гугла.Видно теперь, что даже сложная версия стандарта весьма понятна, если разобраться. На мой взгляд, она все же избыточна: может быть либо один, либо два запроса. Я бы постарался привести к общему знаменателю. Как этот ад поддерживают разработчики браузеров, не понимаю.
Несмотря на кажущуюся простоту, реализовать поддержку CORS на сервере требует времени. Первоначально я хотел использовать чужую библиотеку, но после 10 минут чтения исходного кода понял, что автор НЕПРАВИЛЬНО понял спецификацию и реализовал ее с ошибками. Лишний раз убедился, авторы сторонних библиотек – не боги, а такие же смертные. Они могут тупить, ошибаться. Лучше потратить день на чтение спеки и сделать все правильно, чем доверять первому встречному решению.
Расскажу теперь о тонкостях, с которыми столкнулся при внедрении CORS в проекте. Прежде всего, чтобы сократить число preflight-запросов, стоит либо кешировать эндпоинт
OPTIONS
заголовками:Cache-Control: no-cache, must-revalidate
, либо вообще объявить его на уровне Nginx:
location / { if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'Version, Authorization, Content-Type'; }
Еще одна тонкость: даже если возвращаете ответ с не-положительным статусом, например, не прошла валидация или нет прав, заголовок
Access-Control-Allow-Origin
обязан присутствовать. Если заголовка нет, браузер решит, что CORS-запрос запрещен и не прочитает ответ.В нашем случае это вылилось в следующее: CORS-декоратор оборачивал ответ в заголовки только в положительном случае. Если не проходила валидация, приложение отсылало ответ еще до того, как доходила очередь декоратора. Браузер не мог внятно сказать, почему не прошел запрос. Пришлось поднять декоратор на самый верх стека.
С CORS разобрались, теперь не будет лишним почитать другое руководство про часовые пояса
Ссылки:
-
Отличный туториал на английском языке, откуда я подчерпнул большую часть материала.
-
Сайт, целиком посвященный CORS: описание, статьи, конфиги, примеры кода.
-
Что почитать на выходных №5
Поехали, техническое:
-
Замечания Егора Бугаенко об известных паттернах.
-
What’s Wrong With Object-Oriented Programming?
У того же автора: подборка цитат известных программистов о том, что ООП отстой.
-
Отличный мануал как прикрутить кросс-доменные запросы к серверу и клиенту. Намереваюсь написать свой туториал по следам недавних изысканий.
На сладкое:
-
Что дает человеку секс с роботом.
-
-
Мой вклад в clj-http
В проект clj-http приняли мой скромный пулл-реквест. Теперь, если запрос был вызван с флагом
:throw-exceptions true
и вернулся плохой ответ (статус не 2хх, 3хх), то во вбрасываемый словарь добавляеся ключ:type :clj-http.client/unexceptional-status
. Это на ура работает с библиотекой Slingshot, потому что отлов исключений по полю:type
стал негласным стандартом.Если абзац выше звучит для вас бессмыслецей, расскажу подробней. Изначально в Кложе идут унылые Java-исключения, которые плохо ложатся на мир структур и коллекций. Но поскольку Кложа это Лисп, а в Лиспе любая проблема решается его же силами, умельцы написали библиотеку для альтернативных исключений slingshot.
Библиотека упрощает работу с исключениями до мыслимого предела. Теперь можно вбрасывать не только объект, унаследованный от Throwable, а вообще что угодно. Лучше всего подходит словарь. Форма отлова исключения, выброшенного из slingshot, может иметь разную структуру. Например, предикат, но удобней и короче будет вектор двух элементов, где первый – ключ, а второй – значение.
Если раньше приходилось передавать неочевидный предикат, который проверял, что в словаре есть поле
status
, и оно числовое:(catch (fn [response] (-> response :statsus integer?)) ...)
, то теперь достаточно сравнить так:
(catch [:type :clj-http.client/unexceptional-status])
Визуально разницы мало, но селектор по ключу
:type
открывает новые горизонты. Например, во фреймворке Compojure API, на котором наш проект, обработка исключений целиком работает на этом принципе. В особом словаре лежат ключи – потенциальные варианты:type
, а значения – функции-обработчики. Хорошие примеры в документации.Пока пулл-реквест не приняли, приходилось писать свой костыль для добавления ключа. Теперь с этим покончено.
-
Статья в Associated Press
В издании Associated Press опубликована статья об айти-специалистах Воронежа, в том числе из нашего офиса. Я немного рассказал про удаленку, деньги и прочие детали, словом, все то, что описывал в посте про удаленную работу. Мой коллега Михаил поделился взглядами на рынок специалистов и воздействие кризиза на айти.
Фотографии в хорошем качестве и видео на сайте AP доступны только по платной подписке.
-
Синтаксис
Незрелый программист судит о языке по синтаксису. Эти скобочки люблю, а такие нет. Языки с отступами не рассматриваю. Требую коммерческую ИДЕ с дебагом мышкой.
Это проблема.
Каждый язык обладает неповторимым портретом. Он складывается из идей, которые вложил в него автор. Как устроены коллекции, как ведут себя типы-примитивы, какая парадигма господствует.
Автор выбирает тот или иной синтаксис, чтобы выразить внутреннее устройство языка явно. Чтобы программисты смогли понять заложенные в языке принципы. Верно их применить.
Бывает, синтаксис не только справляется со своей задачей, но и привносит преимущества, недоступные в другим синтаксисам. Например, любой диалект Лиспа автоматически несет мощь макросов и метапрограммирования в дополнение к языку.
Обыватели полагают, что Кложа – это очередной “современный” Лисп. Это рассуждение дилетанта. Кложа – самостоятельный язык с принципиально новыми идеями, оформленный в виде Лиспа. Его создатель мог бы выбрать сишный или питонячий синтаксис и получить какой-нибудь Котлин или Скалу со своими особенностями.
Синтаксис – это внешность языка. По налогии с людьми, внешность бывает обманчива. Человек заметил это еще в древние времена: не тот друг, кто красиво выглядит, и не тот враг, кто некрасив.
Посмотрите на этого человека. Выглядит он слегка комично. Волос нет, черты лица ассиметричны: нос скошен, левое ухо больше, глаза неровные. А это, на минутку, Сет Годин – мировой эксперт по маркетингу, автор научных работ и бестеллеров.
Сделал он столько всего хорошего, что другим и за несколько жизней не сделать. С такой внешностью, да.
Следующий тезис выделю особо.
Я слышал много раз, что у Лиспа странный синтаксис, но ни разу не слышал, что он не решил какую-то задачу
Когда вы жаловались, пытались сделать что-то полезное? Например, распарсить XML, JSON, дернуть урл или сходить в базу. Спорю, что даже не пытались, потому что все это Лисп делает на раз.
Фразы о синтаксисе обычно исходят от самовлюбленных нарциссов, которым лень пошевелить головой. Малейшая неожиданность, мозг потревожен – включается агрессия. Плохой синтаксис!
Человек привыкает ко всему. Садишься за Лисп, и непривычно. А недавно, после двух месяцев Кложи, сел за Питон и все вызовы функций напихал в скобки. Уже автомат.
Испытываешь приятно чувство, когда видишь стену кода на Лиспе и скачешь по нему вверх и вниз с полным пониманием, что происходит. Синтаксис уходит и остается только смысл, заложенный в код. Я читал, похожее чувствуют спортсмены, когда прорывают очередной рубеж. Только здесь работает голова, а не тело.
Синтаксисы хороши все. Важно видеть за синтаксисом природу языка, тогда не будет проблем с восприятием синтаксиса.
-
Что почитать на выходных №4
Поехали:
-
Starting a Node.js app with ClojureScript and Boot (английский)
Небольшой туториал как начать проект на Ноде с Кложе-скриптом. Да, под Ноду можно спокойно писать на cljs.
-
Why I Don’t Publish E-Books (английский)
Егор Бугаенко о недостатках электронных книг.
-
Николай Товеровский (сотрудник Бюро Горбунова) объясняет разницу между “делать” и “сделать”. В рамочку и на стену.
-
И на сладкое: Голый патриарх, или Закон Микки Мауса
Александр Невзоров как всегда прекрасен.
-
-
Двенадцатая встреча
Провели двенадцатую встречу. Двенадцатую, Карл! Целый год без пропусков, в жару и холод, в будни и на майских. 25 выступлений, 12 докладчиков. Отдельным постом я расскажу, с чего все начиналось и как вообще держится, я пока что свежие видосы.
Антон Чикин рассказал про мердж в Гите:
Артем Трубачев топил за веб-сокеты, послушал с удовольствием:
Косяки: я вел трансляцию в Перископе, ушла только половина эфира. В следующий раз нам грозятся дать личный пароль от Дом.ру, так что есть надежда. Во время записи опять налажал со звуком: писал на встроенный микрофон мимо петличного, звук вышел не ахти. Зато слышен весь бред из зала :-)
На следующую встречу уже запланированы выступления: про Докер и No-SQL базы данных. Следите за новостями. Хотите выступить – пишите в личку. Напомню, на тему Рефакторинга мы общаемся в чате Телеграма.
-
Что почитать на выходных №3
В этом выпуске:
-
Мой скромный опыт в роли HR (русский)
О вечном: работе и найме. Под “мой” в данном случае подразумевается опыт Александра. Тема собеседования мне очень интересна, я писал о нем неоднократно: раз, два, три, четыре.
-
Rich Hickey: Spec-ulation Keynote (видео, английский)
Опубликованы доклады с Clojure/conj 2016 (плейлист). Судя по заголовкам, все доклады интересные, но пусть это будет Рич в качестве главного рекомендуемого к просмотру.
-
На сладкое: Один день из жизни Николая Ивановича (русский)
Просто читать до конца.
-