2010-10-17

Роль CTO в технологической компании

Есть известная максима, давно проповедуемая Полом Гремом и взятая на вооружение большей частью американского стартап-сообщества, о том, что технологические стартапы должны иметь минимум двух основателей. Можно много дискутировать на тему, с чем это связанно: например, есть версия, что "если предприниматель не способен убедить в своей идее хотя бы еще одного человека, то он тем более не сможет убедить в этом рынок". Я же пришел к выводу, что всё упирается в ответ на вопрос, что можно аутсорсить, а что нет? Как известно, нельзя этого делать с ключевыми функциями компании. И первой ключевой функцией является формирование ее идентичности на рынке. Этим занимается человек, называемый американцами CEO, хотя более правильно было бы назвать его просто лидером проекта. По своей базе он может быть как инженером, так и маркетологом, финансистом или продажником. Это не столь важно, поскольку его главная роль, которая заключается в том, чтобы вести компанию в правильном направлении и рассказывать об этом рынку через маркетинг, биздев, продажи, PR и т.д. и т.п., не требует особой специализации. Очевидно, что при этом он должен стоять у истоков идеи, которая реализуется, и сам крепко верить в нее, иначе он не сможет убедить в ней других. И эта роль настолько обширна, что она должна быть единственной сферой ответственности такого человека. Да, он может быть крутым программистом и вносить свой вклад, а, воможно, и наибольшее ноу-хау в технологии своей компании, но он не может быть ответственным за эту сферу в целом. Потому что, если отвечать за два дела одновременно, то ни одно из них не выйдет сделать хорошо. И это показывает опыт, в том числе и мой собственный.

Вот здесь и выходит на арену CTO, роль которого в компании заключается в технологическом лидерстве. Часто считается, что СТО должен написать основную часть кода, если это касается софтверного стартапа. Это не обязательно. Код могут написать хоть 10 фрилансеров или 100 миллионов обезьян, поскольку это довольно стандартная работа, и если правильно поставить задачу, с ней прекрасно справится наемный сотрудник. Но вот, что сложно — так это поставить задачу и сформулировать рамки, в которых должен развиваться проект, а потом следить за тем, чтобы они соблюдались. В технологическом бизнесе, как ни крути, а основные риски все-таки технологические. И все остальные участники процесса (наемные сотрудники, клиенты и т.д.) будут их только увеличивать. Единственный, кто может и должен с ними бороться — это СТО.

СТО — это роль того, кто принимает окончательное решение, выбрать технологию Х, за которую выступает разработчик, или Y, которая сейчас на волне хайпа. Это тот, кто не спит ночами из-за того, что не настроен бекап базы данных. Это тот, кто вводит практику ревью кода и добивается ее реального воплощения. Тот, кто отвечает программистам на вопросы: "зачем?", "почему?" и "когда?" Тот, кто отбирает подходящих инженеров. И принимает еще 100500 разных решений, а потом добивается их исполнения. В общем, это тот, кто создает технологическую культуру компании и не дает появляться технологическуму долгу, который в будущем (и, скорее, близком, чем отдаленном) потянет компанию ко дну или же не даст ей развиваться необходимыми темпами.

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

2010-08-13

ASDF как язык описания систем

Это третья статья в серии про ASDF. Первая часть — об ASDF 2, вторая — об архитектуре ASDF.

Как было замечено ранее, ASDF выполняет 2 связанные, но все же довольно разные по требованиям со стороны пользователей функции: описание систем и управление их сборкой. В этой статье я постараюсь перечислить распространенные (и не очень) шаблоны его применения для этих задач, собранные мной из более 70 open-source Lisp библиотек, с которыми приходилось работать. Я думаю, что систематизация этих знаний сослужит хорошую службу Lisp-сообществу и будет полезна как начинающим, так и экспертам, которые смогут усовешенствовать свои техники.

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

Прямые ссылки на полезные вещи:

Начало работы с ASDF


ASDF нельзя назвать инструментом, который можно освоить за 5 минут. Я сам, например, очень долго вникал в идеологию и особенности его работы, и делал это в основном методом проб и ошибок (мануал мне плохо помогал :). Однако, сейчас мне кажется, что эти трудности связанны не с особенностями ASDF (какими-то неудачными архитектурными решениями и т.п.) или Lisp'а, а, в первую очередь, с отсутствием должной документации и best-practices. Именно этот пробел я и хочу заполнить. Да, всё, что будет показано ниже, почерпнуто из широкодоступного кода Lisp-библиотек, однако часто нужны скорее tutorial'ы, которые позволят быстро начать работу и сразу получить ожидаемый результат. Поэтому начнем именно с такого самого простого примера.

Итак, чтобы создать новый проект с помощью ASDF — нужно создать директорию проекта (пусть будет testprj), в которой поместить имеющиеся lisp-исходники (пусть это будет файл app.lisp) и создать в этой директории файл testprj.asd, в который поместить следующую форму:
(asdf:defsystem #:testprj
:components ((:file "app")))
Для загрузки нового проекта в Lisp-среду нужно, чтобы наш testprj.asd файл был в известных ASDF местах. На данный момент поддерживается 2 способа задания таких мест: аналог PATH (*central-registry*) и source-registry (использующий конфигурационные файлы). Самый распространенный подход (в POSIX-окружении) — это создать ссылку на asd-файл в какой-то специальной директории (например, ~/.lisp), которая добавлена в *central-registry*.

В общем полная настройка такого варианта выглядит так:
$ ln -s <path-to-testprj.asd> ~/.lisp/testprj.asd

;; обычно это делается в rc-файле lisp'а (например, .sbclrc)
CL-USER> (push "~/.lisp/" asdf:*central-registry*)

;; далее можно выполнять (для ASDF 2):
CL-USER> (asdf:load-system :testprj)
;; или для любой версии ASDF:
CL-USER> (asdf:oos 'asdf:load-op :testprj)
;; или в большинстве окружений (например, SBCL), даже:
CL-USER> (require :testprj)
Вот и всё.

Другие формы в ASD-файле


На самом деле, ASD-файл — это обычный lisp-исходник, просто с другим расширением, что помогает найти его ASDF'у, поэтому в нем могут находится и другие lisp-формы. Впрочем, прежде, чем помещать туда произвольные формы, стоит очень хорошо подумать. Во всяком случае, ASD-файл следует сохранить полностью декларативным, поскольку большинство инcтрументов, созданных вокруг ASDF, должны иметь возможность просто читать этот файл, не исполняя.

Пакет для defsystem


Единственный класс форм, размещение которых в ASD-файле необходимо (помимо defsystem и некоторых других ASDF-специфичных форм, о которых будет сказано далее) — это формы работы с пакетом.

Сейчас в Lisp-сообществе есть 2 конкурирующих взгляда на то, как это делать:
  • простой — определять системы в пакете ASDF (in-package :asdf)
  • скурпулезный — определять отдельный пакет только для ASDF-описания системы:
    ;; Пример из описания системы ARCHIVE:
    (defpackage :archive-system (:use :cl :asdf))
    (in-package :archive-system)
Лично я являюсь сторонником первого подхода, поскольку он не добавляет избыточной сложности. Его единственный недостаток — в возможном конфликте символов, однако он решается использованием особых символов (#:testprj или :testprj). Единственным случаем, когда вариант отдельного пакета может оказаться препочтительнее — это какие-то очень сложные описания систем с зависимостями от дополнительных пакетов. Впрочем, это верный признак того, что вы делаете что-то не так и, просто, не пользуетесь всеми возможностями ASDF.

Использование символов


Имена ASDF-систем в форме defsystem, как и CL пакетов в defpackage, можно задавать несколькими способами:
  • символом: (defsystem testprj ...)
  • кивордом: (defsystem :testprj ...)
  • неинтернированным символом: (defsystem #:testprj ...)
  • строкой: (defsystem "TESTPRJ" ...)
Мне не сразу стал понятен принцип выбора, но он прост: нужно использовать неинтернированные символы (а все остальные встречающиеся варианты связанны с тем, что авторы просто не знали или не понимали этот вариант :).

Вариант строки, в принципе, эквивалентен, но не эстетичен, киворда — приводит к "засорению" пакета keywords,— а просто символа — подвержен риску конфликта имен (его точно не стоит использовать, если описывать систему внутри ASDF-пакета).

Задание зависимостей


Я бы сказал, что ключевой функцией ASDF является разрешени зависимостей между исходными файлами в рамках одной системы и между разными системами. Все зависимости в ASDF задаются ключевым словом :depends-on в описании соответствующего компонента (файла, модуля, системы и т.д.)

Зависимости от других систем


Разумеется, любая серьезная система существует не в вакууме, а использует множество других библиотек. Несмотря на миф об их отсутствии в lisp-экосистеме :), большие проекты, над которыми мне приходилось работать, как правило, использовали порядка 20-30 сторонних библиотек (включая рекурсивные зависимости).

Предположим, что наш проект использует библиотеку утилит RUTILS. В таком случае нам нужно немного расширить его описание:
(asdf:defsystem #:testprj
:depends-on (#:rutils)
:components ((:file "app")))
Опять же для задания имени зависимости мы используем неинтернированный символ. ASDF позволяет дать и более точное описание такой зависимости (которое пока используется крайне редко, и о котором в отдельной статье, посвященной версиям).

Зависимости между файлами


Если в проекте больше одного lisp-файла, то стандартной практикой является добавления файла packages.lisp, в котором описываются 1 или несколько пакетов, которые будут использоваться (создавать любой неигрушечный проект в рамках cl-user пакета строго не рекомендуется :).

Предположим, что помимо app.lisp, мы также используем файл support.lisp. В таком случае наше описание приобритет следующую форму:
(asdf:defsystem #:testprj
:depends-on (#:rutils)
:components ((:file "packages")
(:file "support")
(:file "app")))
Однако мы не задалт порядок загрузки отдельных файлов, что может привести к неожиданным результатам (скажем, Lisp будет ругаться на форму (in-package #:testprj) в файле support.lisp, поскольку файл packages.lisp еще не загружен. Поэтому эту форму нужно доработать:
(asdf:defsystem #:testprj
:depends-on (#:rutils)
:components ((:file "packages")
(:file "support" :depends-on "packages")
(:file "app" :depends-on "support")))
Тут мы не пишем, что app зависит от packages, поскольку зависимости транзитивны.

Такой последовательный (serial) вариант зависимостей характерен для доброй половины проектов, поэтому для него есть специальная декларация для defsystem: :serial t. С ней наше описание снова упростится:
(asdf:defsystem #:testprj
:depends-on (#:rutils)
:serial t
:components ((:file "packages")
(:file "support")
(:file "app")))
В основе ASDF лежит расширяемая объектная модель компонентов и операций над ними. Некоторые разработчики используют в описании систем прямое имя класса :cl-source-file, а не :file. А вот класса file в ASDF как раз нет: конкретный класс определяется из слота default-component-class модуля (система — потомок модуля) и по умолчанию, конечно, является как раз lisp-исходником.

Помимо этого описан ряд других вариантов, компонент, а также всегда можно описать свой собственный (об этом — в следующей статье).

Например, интересной практикой является подобная декларация компонента:
:components ((:static-file "cl-oauth.asd")
...
(static-file — это любой статичный файл, который не обрабатывается компилятором, например файл лицензии или, как в данном случае, собственно файл описания системы — ведь он обрабатывается только ASDF. Зачем его добавлять? Например, если мы расчитываем, что будем реализовывать какую-нибудь операцию, типа publish-op, для создания дистрибутива из исходных файлов).

Модули


Модули ASDF — это логические компоненты системы, которые объединяют несколько других компонент. Использование модулей позволяет решить 2 задачи:
  • аггрегированно управлять зависимостями

    Скажем, у нас есть 2 части системы: бэкенд и фронтенд, которые зависят от общего файла утилит. И при изменении каждой из них мы не хотим перекомпилировать другую. В таком случае логично будет описать каждую часть в виде отдельного модуля
  • распределить исходники по разным директориям (в какой-то степени это аналог модулей в Python, но без управления видимостью — об этом в следующей статье)
    (defsystem :arnesi
    ...
    :components
    ((:module :src
    :components ((:file "accumulation"
    :depends-on ("packages" "one-liners"))
    (:file "asdf" :depends-on ("packages" "io"))
    (:file "csv" :depends-on ("packages" "string"))
    (:file "compat" :depends-on ("packages"))
    (:module :call-cc
    :components ((:file "interpreter")
    (:file "handlers")
    (:file "apply")
    (:file "generic-functions")
    (:file "common-lisp-cc"))
    :serial t
    :depends-on ("packages" "walk"
    "flow-control" "lambda-list"
    "list" "string"
    "defclass-struct"))))
    ...))
А можно ли разложить файлы по разным директориям не привязываясь к модели зависимостей, которая накладывается модулями? Да. У любого компонента есть слот :pathname, который позволяет явно задать путь к нему. Однако его использование имеет свои особенности — об этом дальше.

Имена компонент и путь к ним


У любого ASDF-компонента есть обязательный аттрибут имя, который используется при его поиске и разрешении зависимотстей. Однако этот аттрибут *не задается* декларацией :name в описании компонента.
(defsystem :ch-image
:name "ch-image"
...)
В этом коде :name "ch-image" несет чисто эстетический смысл (не верите? :)

Имя задается первым символом в декларации компонента: в данном случае ch-image, или же в случае модуля выше — :src, или же "accumulation" для файла там же. Все внутренние функции ASDF умеют работать как с символьным, так и со строковым представлением имен, описанных выше.

Кроме того, у каждого компонента есть аттрибут pathname, который определяет его положение в файловой системе. Однако, в отличие от имени, его как раз можно задать соответствующей декларацией. Например, этот пример задает относительный собственно ASD-файла путь к модулю io.multiplex библиотеки IOLIB: :pathname (merge-pathnames #p"io.multiplex/" *load-truename*).

Если же путь не задавать явно, то он вычисляется из имени, расширения (которое определяется типом компонента) и положения в иерархии модулей. Таким образом для примера задания модуля в arnesi (выше) для компонента :file "interpreter" будет вычислен такой путь: src/call-cc/interpreter.lisp.

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

Мета-информация


Defsystem-форма позволяет задать большое количество полезных метаданных для системы. Очень важно указать как минимум следующие:
  • :version, например :version "0.0.1" (подробнее о версиях — в отдельной статье)
  • :author или :maintainer (с указанием e-mail'а, чтобы к вам впоследствии смогли обратиться и предложить миллионы за доработку и поддержку вашей прекрасной библиотеки :)
  • :licence — чтобы люди знали, как они могут пользоваться вашими поделками

Платформо-зависимое описание систем


CL предоставляет исключительно удобный механизм условной компиляции и выполнения кода (#+/#-). И как раз в описаниях систем он, разумеется, находит широкое применение:
  • предотвращение загрузки системы в целом — тут интересны примеры из двух альтернативных библиотек для FFI:
    #+(or allegro lispworks cmu openmcl digitool cormanlisp sbcl scl)
    (defsystem uffi ...)

    ;; CFFI: этот вариант, безусловно, правильнее, чем просто тихо ничего не сделать
    #-(or openmcl sbcl cmu scl clisp lispworks ecl allegro cormanlisp)
    (error "Sorry, this Lisp is not yet supported. Patches welcome!")
  • вынесение функций, зависящих от конкретной lisp-среды в отдельные файлы — пример из все того же CFFI:
    :components (#+openmcl    (:file "cffi-openmcl")
    #+sbcl (:file "cffi-sbcl")
    #+cmu (:file "cffi-cmucl")
    #+scl (:file "cffi-scl")
    #+clisp (:file "cffi-clisp")
    #+lispworks (:file "cffi-lispworks")
    #+ecl (:file "cffi-ecl")
    #+allegro (:file "cffi-allegro")
    #+cormanlisp (:file "cffi-corman")
  • закладка нескольких вариантов построения библиотеки, в зависимсоти от каких-то условий. Тут проще всего привести примеры:
    ;; использовать ли acl-regexp2-engine?
    (defsystem :cl-ppcre
    :version "2.0.3"
    :serial t
    :components ((:file "packages")
    (:file "specials")
    (:file "util")
    (:file "errors")
    (:file "charset")
    (:file "charmap")
    (:file "chartest")
    #-:use-acl-regexp2-engine
    (:file "lexer")
    #-:use-acl-regexp2-engine
    (:file "parser")
    #-:use-acl-regexp2-engine
    (:file "regex-class")
    #-:use-acl-regexp2-engine
    (:file "regex-class-util")
    #-:use-acl-regexp2-engine
    (:file "convert")
    #-:use-acl-regexp2-engine
    (:file "optimize")
    #-:use-acl-regexp2-engine
    (:file "closures")
    #-:use-acl-regexp2-engine
    (:file "repetition-closures")
    #-:use-acl-regexp2-engine
    (:file "scanner")
    (:file "api")))
    ;; какие бэкенды генерации графических файлов доступны?
    (defsystem :ch-image
    ...
    (:module
    :io
    :components
    (#+ch-image-has-tiff-ffi
    (:cl-source-file "tiffimage")
    #+ch-image-has-cl-jpeg
    (:cl-source-file "jpegimage")
    #+(and ch-image-has-zpng)
    (:cl-source-file "pngimage")
    (:cl-source-file "imageio"
    :depends-on (#+ch-image-has-tiff-ffi
    "tiffimage"
    #+ch-image-has-cl-jpeg
    "jpegimage")))
    :depends-on (:src))



;; :name не имеет значения
CL-USER> (defsystem :ch-image
:name "ch-image1")
#<SYSTEM "ch-image" {AA7A911}>
CL-USER> (describe *)
#<SYSTEM "ch-image" {AA7A911}>
[standard-object]

Slots with :INSTANCE allocation:
NAME = "ch-image"
VERSION = #<unbound slot>
IN-ORDER-TO = NIL
DO-FIRST = ((COMPILE-OP (LOAD-OP)))
INLINE-METHODS = NIL
PARENT = NIL
RELATIVE-PATHNAME = #P"/home/vs/lib/lisp/ch-image_0.4.1/"
OPERATION-TIMES = #<HASH-TABLE :TEST EQL :COUNT 0 {AA7AB61}>
PROPERTIES = NIL
COMPONENTS = NIL
IF-COMPONENT-DEP-FAILS = :FAIL
DEFAULT-COMPONENT-CLASS = NIL
DESCRIPTION = #<unbound slot>
LONG-DESCRIPTION = #<unbound slot>
AUTHOR = #<unbound slot>
MAINTAINER = #<unbound slot>
LICENCE = #<unbound slot>

2010-07-09

Новости Redis и cl-redis

Наконец, дошли руки обновить наш клиент для Redis и доработать его для поддержки новых режимов, которые добавились в последних версиях БД. Заодно и проверить утверждения из статьи о том, что клиент тривиально расширяемый и легко адаптируется к изменениям.

На самом деле, пришлось немного повозиться. Но не потому, что утверждения ошибочны, а из-за стандартной проблемы подавляющего большинства софтверных проектов — неадекватной документации. Впрочем, в случае с Redis'ом все не однозначно. С одной стороны, в общем, документация хорошая и правильная: во-первых, не перегруженная, во-вторых, описан протокол взаимодействия, и для каждой команды более-менее указано, как она использует протокол (а еще и другие полезные вещи: иногда примеры использования, а также, что мне понравилось, временные характеристики). Но, в том то и дело, что "более-менее" указано, и некоторые важные моменты упущены, так что в этот раз пришлось лезть в код родного клиента redis-cli и даже прослушивать его взаимодействие с сервером tcpdump'ом (жалко, что я не додумался с этого начать :)

Короче говоря, у Redis появилась поддержка хеш-таблиц, но обнаружился баг у всех команд, которые передают ключи (в терминах Redis — "поля") в таблице (самый простой пример: HGET table field). Почему этого не заметили разработчики? Видимо, потому, что родной клиент использует для общения с сервером не описанный протокол с разными вариантами передачи команд: inline, bulk и т.п.,— а простой унифицированный способ в форме multi-bulk, т.е. вместо строки "HGET table field", которую посылал наш клиент, вот что:
"*3
$4
HGET
$5
table
$5
field"

Соответственно, наверно, и не заметили особенность при обработке полей в случае передачи команды в inline-форме.

Справедливости ради, нужно сказать, что если порыться в документации, то можно найти замечание, что "A good client library may implement unknown commands using this command format in order to support new commands out of the box without modifications." (т.е. самый продуктивный путь был — снова перечитать спецификацию протокола :) Теперь вот, задумался, может стоит перевести все команды на эту multi-bulk форму. И API упростится, будет не:
(def-cmd HGET (key field)
"Retrieve the value of the specified hash FIELD."
:generic :bulk)
а
(def-cmd HGET (key field) :bulk
"Retrieve the value of the specified hash FIELD.")
С другой стороны, немного уменьшиться производительность.

Вообще, Redis все в большем количестве мест использует multi-bulk форму (поскольку она наиболее общая). Поддержка PubSub сделана также на ней, хотя и с небольшим отклонением.

PubSub — это сейчас такой горячий пирожок, который все хотят съесть. Как это реализовано здесь? Есть команды SUBSCRIBE и PSUBSCRIBE, позволяющие подписаться на каналы соответственно по имени и по шаблону (типа "news.*"). И есть PUBLISH, которая посылает сообщения. Сообщения доставляются условно мгновенно и приходят в рамках тех активных соединений, в которых произведена подписка. Таким образом, типичный подход к применению этого, насколько я понимаю — это когда у нас есть отдельная нить-слушатель, которая обрабатывает приходящие сообщения и иницирует какую-то реакцию в программе. Где-то так:
(with-connection ()
(red-subscribe "chan")
(loop
(let ((msg (expect :multi)))
(bt:make-thread (lambda ()
(process (elt msg 2))))
;; или же, например
(bt:with-lock-held (*lock*)
(push (elt msg 2) *internal-queue*)))))
Сообщения имеют вид '("message" <канал> <сообщение>) для простой подписки и '(<шаблон> <канал> <сообщение>) для PSUBSCRIBE. Вот, собственно, и всё.

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

Redis действительно быстро развивается и много чего меняется, добавляются радикально новые варианты использования: в данном случае транзакции и PubSub. Но для поддержки всего этого в клиенте хватает точечных изменений на уровне реализации протокола, никакого рефакторинга. В этот раз нужно было добавить еще несколько вариантов ожидания ответа: :queued (для транзакций), при котором считывается сразу несколько разнородных ответов подряд; :float; а также :pubsub,— поменять несколько определений команд, потому что поменялась сама их спецификация. Ну и добавилась обработка особого случая транзакций, когда любая команда возвращает "QUEUED" вместо своего стандартного ответа.

PS. Да, и еще про транзакции: теперь, как видите, Redis и их поддерживает. Что меня заинтересовало — это обсуждение гарантий целостности (на этой же странице внизу), которые, на первый взгляд, недостаточны: нет условия успешного завершения всех команд в рамках транзакции, чтобы транзакция была признана успешной. Т.е. ROLLBACK не предусмотрен. Но вот, что пишет на этот счет Сальваторе Санфилиппо:
I think you are missing the point about MULTI/EXEC, that is, a Redis command only fails on syntax error or type mismatch. That is, in code without errors the transaction will either be executed as whole or nothing. Still it is true that if there are programming errors like using a list command against a set, or a syntax error, only the sane commands will be performed and the others instead will fail, but it's very hard for this to happen in production.

Так что транзакции в Redis кислотные по-своему, и нужно хорошо уяснить для себя их семантику, прежде чем браться применять. (И опять, возвращаясь к тому, с чего я начинал: встает проблема адекватности документации. А идеальная документация — исполняемая... ;)

Внутреннее устройство ASDF

Это вторая статья в серии про ASDF. Первая рассказывала про нововведения в ASDF 2.

Итак, рассказ о внутренностях ASDF начнем с того, что меня самого испугала бы задача создать с нуля подобную систему. В данном случае в идеале нужно единомоментно получить программу, обладающую одновременно такими довольно противоречивыми характеристиками:
  • хорошо покрывающую основные варианты использования (в случае ASDF — это и средство описания систем (для их последующего распространения), и менеджер сборки)
  • простую и удобную для непосвященных в детали пользователей
  • хорошо расширяемую для того, чтобы позволить развивать сопутствующую инфраструктуру (например, такие средства как ASDF-INSTALL)
  • ну и, разумеется, сразу корректно работающую
ASDF была впервые написана Деном Барлоу в 2001 году. Как я понимаю, подспорьем при ее создании был фундаментальный труд Кента Питмана, обобщающий опыт в этой сфере, "Описание больших систем" (1984 года), а также опыт эксплуатации ее предшественника MK:DEFSYSTEM. Т.е., по сути, ASDF была "второй системой", но в данном случае, к счастью, удалось избежать реализации соответствующего синдрома.

Что же представляет из себя эта сама по себе довольно большая система изнутри? Хребтом ASDF является иерархия классов componentmodulesystem, которые содержат информацию об именах, местоположении и зависимостях систем и их компонент, метаинформацию, а также служебную информацию самой ASDF.

Кроме того описаны классы операций, которые могут выполняться над системами, как то: compile-op, load-op, test-op и т.д. Почему операции являются объектами, а не просто ключами, что напрашивается на первый взгляд? Во-первых, это позволяет наследовать от них и при этом точечно менять поведение связанных обобщенных функций. Но даже более важно то, что объект операции имеет определенные свойства: операцию-родитель, является ли операция форсированной (хотя это свойство на данный момент не работает корректно), таблицы отработанных и еще не отработанных узлов и т.д. Это имеет и свой недостаток, поскольку кажется несколько избыточным для обычного пользователя. В версии ASDF 2 для его устранения введены функции-обертки load-system, compile-system и test-system.

На уровне пользовательского API на основе всех этих классов функционирует макро defsystem, а также обобщенная функция operate.

Defsystem — это хороший инструмент описания систем, знакомый и понятный, я думаю, каждому. Он ведет свою историю еще от Lisp Machine Lisp DEFSYSTEM, хотя с тех пор и существенно эволюционировал в сторону упрощения интерфейса.

Параметры defsystem-формы не передаются, как можно было бы предположить, напрямую в (make-instance 'system ...), а сперва обрабатываются функцией parse-component-form. При этом часть параметров передается как есть, а часть транслируется или используется в качестве мета-параметров. Остановлюсь на двух из них:
  • defsystem можно применять не только для стандартных систем, но и их потомков за счет параметра :class
  • параметр :depends-on:weakly-depends-on1), на самом деле, не присутствует в качестве слота в классе component. Его содержимое транслируется в содержимое слота in-order-to, которое описывает зависимости более гранулярно отдельно для каждой операции. Кстати, этот слот можно задать напрямую в defsystem-описании, чем иногда пользуются при необходимости указания нестандартных сценариев поведения. Впрочем, среди установленных у меня порядка 70 библиотек я нашел только несколько примеров такого использования, самый интересный из которых — в описании системы weblocks-prevalance (в остальных случаях это применяется для указания зависимостей систем-тестовых наборов). В данном случае устанавливается зависимость от дополнительно определенной операции prepare-prevalance-op:
(defsystem weblocks-prevalence
:name "weblocks-prevalence"
;; кстати, нет смысла задавать параметр `name' для системы,
;; поскольку имя берется из символа, передаваемого в defsystem
:description "A weblocks backend for cl-prevalence."
:depends-on (:metatilities :cl-ppcre :cl-prevalence :bordeaux-threads)
:components ((:file "prevalence"))
:in-order-to ((compile-op (prepare-prevalence-op :weblocks-prevalence))
(load-op (prepare-prevalence-op :weblocks-prevalence))))
А сама операция prepare-prevalence-op характеризуется всего одним дополнительным методом, отвечающим за подгрузку дополнительной системы, находящейся по внутреннему пути, не известному в *central-registry*:

(defmethod perform ((op prepare-prevalence-op)
(c (eql (find-system :weblocks-prevalence))))
(unless (find-package :weblocks-memory)
;; load weblocks if necessary
(unless (find-package :weblocks)
(asdf:oos 'asdf:load-op :weblocks))
;; load weblocks-memory.asd
(load (merge-pathnames
(make-pathname :directory '(:relative "src" "store" "memory")
:name "weblocks-memory" :type "asd")
(funcall (symbol-function
(find-symbol (symbol-name '#:asdf-system-directory)
(find-package :weblocks)))
:weblocks)))
;; load weblocks-memory
(asdf:oos 'asdf:load-op :weblocks-memory)))
Впрочем, это можно было бы сделать и иначе :)

Проблемой использования defsystem, которой я коснусь в следующей статье на тему шаблонов применения ASDF, является то, что есть соблазн отступить о чисто декларативного описания системы и добавить в него некоторые исполняемые элементы, например, чтение и подстановку версии системы из отдельного файла. Как по мне, было бы разумно обрабатывать эту форму в рамках (let ((*read-evel* nil) ...), чтобы исключить такие варианты. Причина тут в том, что ASDF-описание может обрабатыватся более, чем 1 раз при поиске систем и разрешении зависимостей, и работа с ним в таком случае выполняется в режиме просто чтения. Возможно, это ограничение будет со временем установлено: во всяком случае это уже обсуждалось в рассылке.

Теперь рассмотрим функцию operate, которая является точкой входа в область собственно ядра ASDF, которое отвечает за поиск и выполнение операций над зависимыми компонентами. Она опирается на обобщенные функции traverse, роль которой — в построении плана выполнения той или иной операции, и perform, которая собственно выполняет конечные действия, будь то компиляция, загрузка файлов и т.д, а также на функцию find-system. Кроме того, интересным дополнением (неким альтер-его) perform является explain, которая только указывает, какое действие должно быть выполнено. Хорошая иллюстрация возможностей применения explain дана в статье Питмана:
(DOLIST (STEP (SYSDEF:GENERATE-PLAN system :UPDATE))
(SYSDEF:EXPLAIN-ACTION system STEP)
(UNLESS (NOT (Y-OR-N-P "OK? "))
(SYSDEF:EXECUTE-ACTION system STEP)))
Этот код на Lisp Machine Lisp позволяет пошагово выполнять обновление системы при условии согласия пользователя на каждом шаге.

Операция traverse реализует алгоритм поиска и разрешения зависимостей ASDF. Сам по себе он не стоит отдельного рассмотрения, но что интересно, это то, что алгоритм может обрабатывать 3 типа зависимостей:
  • привычную простую форму (:depends-on (:cl-ppcre ...))
  • версионированную форму (:depends-on ((:version :cl-ppcre "1.2.3") ...))
  • зависимость от фичи (:depends-on ((:feature :x86) ...))
Ко второй форме мы еще вернемся в теме о поддержке версионности. А на счет третьей, то к ней относится интересный комментарий в коде ASDF: "Congratulations, you're the first ever user of FEATURE dependencies! Please contact the asdf-devel mailing-list." :)

Точкой входа в механизм поиска Лисп-систем в операционной системе является функция find-system, которая смоделированна на основе стандартной функции find-class. (Артефактом такого подобия является параметр errorp, необходимость в котором как здесь, так и в find-class и find-method, в которых также реализован этот подход, как по мне, по меньшей мере сомнительна). Find-system одновременно проверяет наличие системы среди уже загруженных (таблица таких систем со врменем последнего обновления находится в *defined-systems*), а также ищет на диске с помощью функции system-definition-pathname, которая в свою очередь раскручивает механиз поиска систем функциями, заданными в списке *system-definition-search-functions*. На данный момент в этом списке 2 основных функции: "классический" поиск в директориях, заданных в *central-registry*, и новый поиск в source-registry. Очень важной особенностью find-system, про которую не нужно забывать, это ее побочный эффект — загрузка ASD-файла системы в память и регистрация его в *defined-systems*.

Наконец, стоит еще упомянуть 2 обобщенные функции output-files и input-files, которые позволяют задавать способ точного определения полных имен файлов разных типов компонент по их короткому имени в описании системы.

В общем-то, это и всё ядро ASDF. Остальное — это ряд утилит для работы с операционной системой, среди которых несколько очень полезных функций, заслуживающих более широкой известности в Лисп-мире (например, run-shell-command, load-pathname, parse-windows-shortcut и другие), а также добавленные в ASDF 2 механизмы определения местонахождения FASL-файлов (в чем-то аналог ASDF-BINARY-LOCATIONS, для которого добавлен и compatibily mode) и работы с центральным реестром.



Разобравшись во внутренностях ASDF я пришел к довольно неожижанному для себя выводу: в ее основе лежит хорошо продуманный объектно-ориентированный дизайн, дающий воможность для ее эффективного применения не только непосредственно, но и как основы для других инструментов. Более того, используя ее как кейс, можно даже учить людей настоящему практическому объектно-ориентированному проектированию2. В то же время, этот дизайн, конечно, не идеален.
  • Во-первых, он сложен. И это действительно оправдано причинами, описанными вначале. Но все же временами наблюдается излишняя сложность. (Впрочем, эта проблема постепенно устраняется по мере развития кода). Сложность приводит к багам, некоторые из которых существуют до сих пор, на 10-м году жизни системы.
  • Во-вторых, он расширяемый, но тоже не до конца: ядро системы создано с оглядкой на последующую расширяемость, но в поддерживающем слое об этом иногда забывали.
  • В-третьих, он плохо описан. И это, пожалуй, самая большая проблема ASDF и хороший урок для любого проектировщика: ясная и полная документация имеет важнейшее значение для удачного использования сколь угодно хорошого дизайна.

И еще можно сказать, что ASDF намного ближе по своей философии к (качественным) продуктам "классических" системных языков, таких как С++ или Java, чем к распространенному в последнее время в Лиспе bottom-up стилю. В то же время, за счет использования полезных возможностей Лиспа: мультиметодов, функций высших порядков и т.п.,— он намного менее церемониален и многословен, так сказать, без перегрузки шаблонами проектирования.

В следующей статье — о некоторых шаблонах использования ASDF.


1 разница в том, что изменение "слабых" зависимостей не вызывает перекомпиляцию зависящих от них компонент
2 ... а не такому далекому на поверку от реальности, который можно увидеть, например, в знаменитой книге Гради Буча


И в придачу, непонятная визуализация того, как происходит поиск системы:)

2010-07-05

Уникальные технологии Common Lisp

Написано для: developers.org.ua
Время написания: октябрь 2008

Базовые подсистемы языка

В языке Common Lisp есть как минимум 3 инфраструктурных технологии, во многом формирующие подходы к его применению, которые в других языках либо отсутствуют вовсе, либо реализованы в очень ограниченном варианте. Для компенсации их отсутствия пользователи других языков часто вынуждены использовать Шаблоны проектирования, а порой и вообще не имеют возможности применять некоторые более эффективные подходы к решению типичных задач.

Что это за технологии и какие возможности дает их использование?

Макросистема

  • Это основная отличительная особенность Common Lisp, выделяющая его среди других языков. Ее реализация возможна благодаря использованию для записи Lisp-програм s-нотации (представления программы непосредственно в виде ее абстрактного синтаксического дерева). Позволяет программировать компилятор языка.

  • Позволяет полностью соблюдать один из основополагающих принципов хорошего стиля программирования DRY (не-повторяй-себя).

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

Примеры применения:
  1. Определение управляющих конструкций языка, которые могут использоваться на равне со стандартными (на самом деле практически все стандартные управляющие конструкции также являются макросами. Основу языка — “аксиомы”, которые невозможно определить через другие конструкции — составляют специальные операторы). В качестве примера можно привести анафорические управляющие конструкции (см. библиотеку Anaphora), которые, используя принцип “convention over configuration”, скрывают реализацию некоторых типичных шаблонов.

    Самый простой пример — макро AIF (или IF-IT), которое тестирует первый аргумент на истинность и одновременно привязывает его значение к переменной IT, которую, соответственно, можно использовать в THEN-clause:

    (defmacro aif (var then &optional else)
    `(let ((it ,var))
    (if it ,then ,else)))

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

    (defun determine-fit-xture-type (table-str)
    "Determine a type of Fit fixture, specified with TABLE-STR"
    (handler-case
    (aif (find (string-trim *spacers* (strip-tags (get-tag "td" (get-tag "tr" table-str 0) 1)))
    *fit-xture-rules* :test #'string-equal :key #'car)
    (cdr it)
    'row-fit-xture)
    (tag-not-found () 'column-fit-xture)))

    * В этой функции проверяется, есть ли во второй ячейке первой строки HTML таблицы какие-то данные и в соответствии с этим определяется тип привязки для Fit-теста. Переменной it присвоены найденные данные.

  2. Создание DSL‘ей для любой предметной области, которые могут иметь в распоряжении все возможности компилятора Common Lisp. Ярким примером такого DSL’я может служить библиотека Parenscript, которая реализует кодогенерацию JavaScript из Common Lisp. Используя ее, можно писать макросы для Javascript!
    (js:defpsmacro set-attr (id attr val)
    `(.attr ($ (+ "#" ,id)) ,attr ,val))

    * Простейший макрос-обертка для задания аттрибутов объекта, полученного с помощью селектора jQuery

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

  4. Наконец, создание инфраструктурных систем языка. Например, с помощью макросов можно реализовать продления (библиотека CL-CONT), ленивые вычисления (библиотека SERIES) и т.д.

  5. …ну и для многих других целей.
Больше по теме: Paul Graham, On Lisp

Мета-объектный протокол и CLOS

  • Основа объектной системы языка. Позволяет манипулировать представлением классов.

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

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

Примерами использования мета-объектного протокола также являются инфраструктурные системы языка, реализованные в виде библиотек:

  • object-persisance: Elephant, AllegroCache
  • работа с БД: CLSQL
  • интерфейс пользователя: Cells

Библиотека CLSQL создана для унификации работы с различными SQL базами данных. Кстати, на ее примере можно увидеть проявление мультипарадигменности Common Lisp: у библиотеки есть как объектно-ориентированный интерфейс (ORM), реализованный на основе CLOS, так и функциональный (на основе функций и макросов чтения).

С помощью мета-объектного протокола стандартный класс языка расширяется специальным параметром — ссылкой на таблицу БД, к которой он привязан, а описания его полей (в терминологии Lisp: слотов) — дополнительными опциональными параметрами, такими как: ограничение уникальности, ключа, функция-преобразователь при записи и извлечении значения из БД и т.д.

Больше по теме: Gregor Kiczales et al. The Art of Metaobject Protocol

Система обработки ошибок / сигнальный протокол

Система обработки ошибок есть в любом современном языке, однако в CL она все еще остается в определенном смысле уникальной (разве что в C# сейчас вводится нечто подобное). Преимущество этой системы заключается опять же в ее большей абстрактности: хотя основная ее задача — обработка ошибок, точнее исключительных ситуаций,— она построена на более общей концепции передачи управления потоком выполнения программы по стеку. …Как и системы в других языках. Но в других языках есть единственный предопределенный вариант передачи управления: после возникновения исключительной ситуации стек отматывается вплоть до уровня, где находится ее обработчик (или до верхнего уровня). В CL же стек не отматывается сразу, а сперва ищется соответствующий обработчик (причем это может делаться как в динамическом, так и в лексическом окружении), а затем обработчик выполняется на том уровне, где это определенно программистом. Таким образом, исключительные ситуации не несут безусловно катастрофических последствий для текущего состояния выполнения программы, т.е. с их помощью можно реализовать различные виды нелокальной передачи управления (а это приводит к сопроцедурам и т.п.) Хорошие примеры использования сигнального протокола приведены в книге Practical Common Lisp (см. ниже).

Больше по теме:

Вспомогательные технологии

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

Протокол множественных возвращаемых значений

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

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

Протокол обобщенных переменных

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

Больше по теме: Paul Graham, On Lisp, Ch.12 “Generalized Variables”

Макросы чтения

Это инструмент модификации синтаксиса языка за пределы s-выражений, который дает программисту возможность, используя компилятор Lisp, создать свой собственный синтаксис. Его работа основана на фундаментальном принципе Lisp-систем: разделении времени чтения, времени компиляции и времени выполнения — REPL (Read-Eval-Print Loop). Обычные макросы вычисляются (раскрываются, expand) во время компиляции, и полученный код компилируется вместе с написанным вручную. А вот макросы чтения выполняются еще на этапе обработки программы парсером при обнаружении специальных символов (dispatch characters). Механизм макросов чтения является возможностью получить прямой доступ к Reader’у и влиять на то, как он формирует абстрактное синтаксическое дерево из “сырого” программного кода. Таким образом, можно на поверхности Lisp использовать любой синтаксис, вплоть до, например, C-подобного. Впрочем, Lisp-программисты предпочитают все-таки префиксный унифицированный синтаксис со скобками, а Reader-макросы используют для специфических задач.

Пример такого использования — буквальный синтаксис для чтения hash-таблиц, который почему-то отсутствует в спецификации языка. Это, кстати, еще один пример того, каким образом CL дает возможность изменить себя и использовать новые базовые синтаксические конструкции наравне с определенными в стандарте. Основывается на буквальном синтаксисе для ассоциативных списков (ALIST):


;; a reader syntax for hash tables like alists: #h([:test (test 'eql)] (key . val)*)
(set-dispatch-macro-character #\# #\h
(lambda (stream subchar arg)
(declare (ignore subchar)
(ignore arg))
(let* ((sexp (read stream t nil t))
(test (when (eql (car sexp) :test) (cadr sexp)))
(kv-pairs (if test (cddr sexp) sexp))
(table (gensym)))
`(let ((,table (make-hash-table :test (or ,test 'eql))))
(mapcar #'(lambda (cons)
(setf (gethash (car cons) ,table)
(cdr cons)))
',kv-pairs)
,table)))))

Больше по теме: Doug Hoyte, Let Over Lambda, Ch.4 “Read Macros”


Послесловие

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

Интересная задачка: вытесняющий мультипроцессинг в userland

Нужно в рамках одной нити управления реализовать поочередно работающие 2 "процесса" (скажем, вычисление 2-х функций), переключение между которыми происходит по регулярному сигналу таймера. Естественно, что при переключении состояние вычисления должно сохраняться и восстанавливаться на следующем такте (а не начинаться заново каждый раз).

Интересно было бы увидеть, как это реализовывается в разных языках? (Я так понимаю, что в Smalltalk это должно быть тривиально за счет наличия объекта контекста. А где еще?)

2010-06-30

ASDF 2

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

ASDF является фактически единственным на данный момент сборщиком Common Lisp програм. Более того он играет свою роль в различных менеджерах дистрибутивов, хотя и не является самим по себе полноценным решением. И, я бы сказал, что со своей ролью инструмента сборки он справляется довольно неплохо, а вот в сфере описания дистрибутивов есть проблемы, которые пока что не решены на практике. Это заставляет многих людей, в том числе и меня, вообще не пользоваться подобными инструментами и произвоить установку дистрибутивов вручную (благо в Common Lisp среде, в том числе и благоаря ASDF это очень просто1), а других (в том числе и меня) задумыватся о создании собственного средства: примеры тому — Mudballs, Lispy, CL-Librarian, LibCL...

В недавней статье в журнале ПФП я написал, что
развитие средства управления пакетами должно идти именно с учетом децентрализованной структуры Лисп-среды, а не вопреки ей.

К такому же заключению мы пришли и в процессе обсуждения темы на форуме lisper.ru, которое также заставило меня немного заглянуть под капот ASDF, чтобы узнать, насколько реально и легко создать на его основе пакетный менеджер. Этот "быстрый взгляд" в итоге вылился в растянутое на целый месяц неспешное ковыряние кода и его доработку для полноценной поддержки версионирования. Свое решение я направил в список рассылки asdf-devel, однако оно вряд ли будет интегрированно. В любом случае, этой теме я собираюсь посвятить отдельную запись в этой серии.

А начнем мы с того, что нового нам несет ASDF 2, релиз которого состоялся 31 мая, и который уже скоро войдет в вашу любимую Лисп реализацию.

Целая новая версия системы претерпела существенный рефакторинг и доработку силами Фарэ и Роберта Голдмана. С точки зрения пользователей она включает в себя следующие улучшения (разумеется, накопленные и отлаженные за посление ряд релизов):
  • версионность самой библиотеки, возможность обновления ASDF

  • добавление более простого пользовательского интерфейса

  • добавление нового способа конфигурации

  • сохранение FASL-файлов отдельно (в других директориях) от исходного кода

  • улучшение работы c путями и соответствующее расширение классов component и system

  • исправление некоторых багов


Вот lightning talk Роберта на встрече TwinCity лисперов, посвященный ASDF 2. Нужно заметить, что ценность этого релиза не только практическая, но и символическая, знаменующая переход проекта на новую стадию разработки: более структурированную и прогнозируемую.

Итак, кратко о новых фичах.

1. Возможность обновления ASDF.
Сейчас любой пользователь может воспользоваться самой новой версией ASDF, просто загрузив ее через (asdf:oos 'asdf:load-op 'asdf) (но не через require!) Подробнее об этом в мануале, который, также улучшается.

2. Более простой пользовательский интерфейс — это функции load-system, compile-system и test-system. Вроде бы как, тривиальное изменение, но избавляющее новичков от необхоимости думать, почему это операции load-op и т.п. являются объектами, а не функциями, и других схожих волнений. Это важно в борьбе с кажущейся сложностью ASDF. При этом методы на test-op, как и раньше — и это понятно — нужно описывать разработчикам систем.

3. Системный реестр (source-registry) — новый способ конфигурации :)
Именно таким является новый, и по замыслу авторов основной способ задания места расположения исходников систем у пользователя. В то же время старый вариант через *central-registry* остается полностью функциональным и поддерживаемым. Более того, он был дополнен проверкой на самую неприятную и, наверно, частую ошибку при использовании ASDF — отсутствие слеша в конце пути к директориям — теперь этой неразберихи больше не будет.

Что же такое системный реестр? Это набор конфигурационных файлов в предопределенной структуре директорий для каждого пользователя, смоделированных по принципу *.conf.d директорий в Unix. А также собственно DSL для конфигурации. Простой пример того, как это работает из мануала:
В директории ~/.config/common-lisp/ находится файл source-registry.conf со следующей конфигурацией:

(:source-registry
(:tree "/home/fare/cl/")
:inherit-configuration)

В данном случае поиск установленных систем производится рекурсивно в поддиректориях в /home/fare/cl/.


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

Честно говоря, как по мне, то новый подход для индивидуального разработчика менее удобен, чем использование *central-registry*. Однако он лучше подойдет для средств автоматического конфигурирования (и, я думаю, что как раз опыт в рамках ITA, где используется много Lisp серверов приложений, послужил отправной точкой для разработки этого способа), а также для использования в пакетных менеджерах. И хорошо, что теперь есть альтернативы для каждого из случаев.

4. Cохранение FASL-файлов теперь происходит в компилятор-специфичных директориях, по умолчанию спрятанных в домашней диретории пользователя. Благодаря этому устраняются проблемы как конфликта прав в случае использования одних и тех же исходников библиотек разными пользователями, так и stale FASLs, которые возникают при апгрейде реализации (в частности для SBCL).

5. В общем, исправлены основные недочеты, которые присутствовали в ASDF при работе с путями в разных операционках, а в классы компонент и система добавлены слоты, указывающие абсолютное положение их в файловой системе. Также исправлены и некоторые другие баги, о чем можно почитать в Changelog'е.

Какие проблемы остались? Из существенных для меня — две: нечеткая семантика форсированных операций (параметр :force t), а также недостатки работы с версиями (этой теме будет посвящена отдельная запись, поэтому не буду касаться ее здесь).

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

В следующей записи — немного о внутренностях ASDF...




1 Мой алгоритм установки Lisp библиотеки:
- Загрузить tar.gz файл
- Развернуть в ~/lib/lisp/
- Создать символическую ссылку на ASD-файл в ~/.lisp/
- (И вариация для случая работы с разными версиями одного пакета): ссылка на ASD-файл основной версии в ~/.lisp/, а при необходимости использования альтернативной версии, скажем hunchentoot-0.15.7 вместо hunchentoot-1.1.0 (push "~/lib/lisp/hunchentoot-0.15.7/" asdf:*central-registry*) (аналог PATH).

2010-06-29

Что такое стартап?

Или точнее, что отличает настоящий стартап от других видов бизнеса? Еще один вопрос, не имеющий никакого практического значения, но всегда являющийся интересной темой для поговорить :)

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

В общем, стартап — это начинание, в котором сочетаются и сбаллансированны 3 составляющих: исследовательская, инженерная и бизнес. Исследовательская отвечает за сферу инноваций, инженерная — за их "доставку" до конечного потребителя, а бизнес — за извлечение из этого денег, позволяющих продолжать работу, развиваться и достигать личных целей основателям. При этом тут, на самом деле, не важна сфера: инженерия сейчас нужна не только в Интернете :), но и в биохимии, и во флористике. (Вот, кстати, хорошая цитата про то, почему же отдельно исследование, а отдельно инженерия, случайно встреченная мною в статье в Википедии: "The theory, while correct in as far as it goes, is not sufficient to allow one to do engineering").

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

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

2010-06-16

Mathematicians vs computer scientists

There is a considerable difference between a mathematician’s view of the world
and a computer-scientist’s. To a mathematician all structures are static: they have
always been and will always be; the only time dependence is that we just haven’t
discovered them all yet. The computer scientist is concerned with (and fascinated by)
the continuous creation, combination, separation and destruction of structures: time is
of the essence. In the hands of a mathematician, the Peano axioms create the integers
without reference to time, but if a computer scientist uses them to implement integer
addition, he finds they describe a very slow process, which is why he will be looking
for a more efficient approach. In this respect the computer scientist has more in com-
mon with the physicist and the chemist; like these, he cannot do without a solid basis in
several branches of applied mathematics, but, like these, he is willing (and often virtu-
ally obliged) to take on faith certain theorems handed to him by the mathematician.
Without the rigor of mathematics all science would collapse, but not all inhabitants of a
building need to know all the spars and girders that keep it upright. Factoring off cer-
tain detailed knowledge to specialists reduces the intellectual complexity of a task,
which is one of the things computer science is about.
--"Parsing Technics"

2010-05-31

Парадигмы программирования, followup

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

Например, функциональный язык — это такой, где вы будете задачи решать через функции. Т.е., когда вы беретесь за новую задачу, то напишите какой-нибудь defun или function. Следующим ключевым свойством функциональных языков является вот что: все есть выражение (expression), т.е. возвращает значение, а не утверждение (statement). Всё. На этом функциональность, строго говоря заканчивается. Но есть еще очень много других видов языков. Вот Haskell, например, язык, вообще говоря, не такой уже функциональный язык. Потому что решать вы все будете через типы и напишите сначала Data :: Type. А функции — лтшь один из частных случаев этих типов (один из типов, попросту говоря), но ведь гораздо интереснее такие типы, как монады или функторы. В общем, язык, на самом деле, алгебраический, т.е. для математиков (по складу ума). Вообще, все языки более-менее для кого-то там по складу ума.

Далее, вот, Common Lisp — наполовину функциональный, наполовину декларативный (потому что в половине случаев я начну не с defun, а с defmacro). А еще на треть объектно-ориентированный, потому что для defclass тоже часто найдется место (но без фанатизма). А Scheme — действительно чисто функциональный, так кроме функций больше ничего нет. Дальше посмотрим на Erlang — это сперва язык для конкурентного программирования. Всё закручено вокруг процессов и обмена сообщениями между ними. Это только на поверхности там пролог и функции, это всего лишь синтаксис. Т.е. нельзя отрицать функциональную ориентированность Erlang'а, но язык таки process-oriented/message-passing. А что же этот знаменитый функциональный язык JavaScript? Нет, нет и нет: "всё — выражение" не выполняется, и хотя функции — обычный синтаксис для записи кода, но ядро языка — это события и коллбеки, event-driven, как говорят американцы.

Возьмем теперь Ruby. Тут всё на классах и лямбдах, т.е. анонимных функциях. А обычные функции превращены в сообщения. Да еще и всё — выражение. Вот такая анархия или мультипарадигменность. И ко всему прочему полный контроль у программиста. Получается объектно-procедурно-немного декларативно-ориентированный. Еще есть Python. Тут все немного (или намного) регулярней. Есть классы и функции, но не всё выражение, анонимных функций, считай нет. В общем, glorified C (как хорошо показывет пример Cython), но, главное, что динамический и разумно простой. Современный объектно-ориентированный процедурный язык, в общем.

C Java, например, всё понятно. Тут чистый классовый подход. Класс на inner классе сидит и анонимным классом погоняет. Говорят, это называется объектно-ориентированный подход, хотя это, вообще-то, про Smalltalk или даже про Python. А тут у нас класс-ориентированный, чистой воды. На закуску остается C++, которому можно всё, особенно в версиях Boost и 0x. Только вот, забывают заметить, что это 3 отдельных языка: С, ++, а также еще Tempaltes. И это не говоря про препроцессор. Тут тоже, как бы, всё class-based, но всё — не объект, а указатель. Короче говоря, такой машинно-ориентированный язык с системами типов и классов, в придачу к возможности застрелить себя в ступню, как говорят опять же американцы...

Про brainfuck и все остальное более-менее без изменений.

2010-05-26

Лисп — философия разработки

Написано для: Практика функционального программирования №5
Соавтор: Александр Манзюк


Чтобы не делать перепечатку, помещаю тут только аннотацию. Основной текст — по ссылке выше.

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

2010-05-18

Простая модель налоговой реформы для малого И крупного бизнеса

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

Итак, первое, единый налог и проблемы, связанные с его использованием для "оптимизации" налогообложения.

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

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

В чем проблема для других форм. Простой пример: есть человек, который выполняет какую-то профессиональную деятельность, например, моет окна. Он может делать это в рамках компании и получать зарплату 4000 грн в месяц, при этом налоговое бремя, которое ложиться на такую компанию — еще 2400 грн, а сам человек на руки получает 3400 грн (а 15% платит налога на доход). Если он моет 20 окон в месяц, то за каждое окно компания должна брать с заказчика 320 грн (+ НДС :), чтобы хотя бы выйти в 0 (будем считать, что больше расходов нет). Теперь представим, что тот же человек моет окна как частный предприниматель: теперь, чтобы получить свои 3400 грн, ему нужно выручить всего то 3600, т.е. при цене 320 грн за окно, помыть около 11 с хвостиком окон. А если он помоет 20, то заработает 7800, что ровно в 2 раза больше. Спрашивается, как компания сможет конкурировать с таким фрилансером? Отсюда и зарплаты в конвертах, и "оптимизация" через СПД. Т.е. виновато не только высокое налоговое бремя (хотя это виновник №1), но и несбаллансированная налоговая система (хотя это все-таки только №2).

Второй момент: допустим человек моет окна самостоятельно, но не регистрируется как СПД, а платит обычный налог на доход (15%). Хотя такая ситуация немного противоречит закону, однако, по сути, это те условия, в которых находятся все наемные работники, если вообще не принимать в рассмотрение компании. Т.е. наемные работники тоже проигрывают СПДшникам на эти 15%.

Теперь рассмотрим вопрос пенсий. СПД-шник платит в пенсионный фонд 84 грн. В то же время любой наемный работник платит 33,2% своей зарплаты, что составляет минимум 300 грн. В результате оба будут получать одинаковую социальную пенсию. Но даже, если человек будет иметь сногсшибательную зарплату и платить огромные отчисления в ПФ, никто не гарантирует, что через 30 лет он будет получать какую-то громадную пенсию, как и сейчас не получают большой пенсии советские герои труда, проработавшие по 50 лет на каких-то вредных производствах и т.д. Потому что государство у нас такое, ответсвенное. Зато из этих отчислений будет платиться огромная пенсия сегодня депутатам, секретным агентам и академикам. Это, ясное дело, абсолютно не справедливо и неэффективно. Как эффективнее — ниже.

Итак, существование СПД-формы — это громадная несправедливость по отношению к: 1. компаниям, 2. простым работникам 3. будущим пенсионерам.
С другой стороны, проблема не столько в ней, сколько в гигантском налоговом бремени на компании, точнее собственников (почти 60% начисления на зарплату наемных рабочих + 25% налог на дивиденды, который, фактически, является двойным налогообложением). Это бремя приводит к тому, что блокируется рост зарплат и происходит тенезация экономики. А в пенсионном фонде все равно дырка, которая покрывается из бюджета.


Предложения по решению проблемы исходят из простейших идей:
1. СПД — это самозанятый профессионал, который делает работу самостоятельно.
2. Пенсии должны быть справедливыми (во всех отношениях :)

Соответственно:

1. СПД форма должна существовать и еще более упроститься.
- Во-первых, в области видов деятельности. Зачем искусственно ограничивать людей: есть специалисты широкого профиля, почему не дать им заниматься теми разными занятиями, которые им по душе? Т.е. КВЕД можно оставить только для статистики, но ни в коем случае не делать его ограничением, как сейчас.
- Во-вторых, уплата налога должна быть не такой частой (хотя бы раз в квартал), особенно в связи с п.2
- В-третьих, нужно убрать верхнее ограничение на размер выручки (если специалист мирового класса зарабатывает миллионы, он что, перестает быть предпринимателем?), но в то же время СПД не должен использовать наемный труд. Вообще. Если нужны помощники, пускай тоже оформляются как СПД и работают по подрядному принципу. Если у вас есть сотрудники, то это уже компания. Просто, у нас создание компании является нереально сложным делом.

2. Ставка налога на СПД должна быть приравнена к налогу на доход физлиц. Это, во-первых, справедливо по отношению к наемным работникам, во-вторых, выгодно для государства. Ну и, в-третьих, не вызовет принципиальных возражений у любого предпринимателя, который понимает, что налоги платить надо, но они должны быть подъемными. В то же время это будет справедливо к тем, кто по каким-то причинам не работает на данный момент как предприниматель: нет прибыли — нет налога. Вообще, предприниматель, который временно (от 1 квартала) сидит без работы, также должен иметь право стать на биржу труда, как и обычный уволенный сотрудник и какое-то время получать социальное пособие. О соцопсобии — ниже.

3. Сферу соцфондов можно радикально упростить и, при этом, сохранить существующие пенсии (потерять которые так боятся чиновники). Все трубят о втором уровне пенсионной системы, но никто не пытается доходчиво объяснить, что это такое. На самом деле все должно выглядеть очень просто: должна быть единая, не зависящая от зарплаты, социальная пенсия, которая выплачивается всем нетрудоспособным людям из ПФ, а также социальная помощь по безработице от государственной страховой компании. Обе эти величины должны равняться прожиточному минимуму. Помощь от несчастных случаев тоже должна быть стандартизована и зависить только от категории (тяжести) травмы, а не от зарплаты. Таким образом, имея статистику по количеству работающих, пенсионеров, безработных и несчастных случаев, можно рассчитать необходимую величину единого социального взноса с каждого работающего (не зависимо от того, сотрудника компании, или СПД, или даже рантье) на бюджетный год для выхода на нужные цифры компенсаций для неработающих. (Т.е. для СПД помимо налога на доход добавляется единый социальный взнос, меняющийся из года в год. Такой взнос нужно будет платить раз в месяц или же наперед).

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

А как покрывать дефицит ПФ для выплаты текущих обязательств государства сверх социальной пенсии (средства на которую, напомню, собираются через единый социальный взнос): пенсий, которые уже начисленны пенсионерам, которые, разумеется, не могут делать никаких отчислений? На самом деле, также как и сейчас — из госбюджета и это будут очень большие деньги :) Но хорошая новость в том, что с введением такой пенсионной системы обязательства бюджета будут с каждым годом уменьшаться, как и список тех пенсионеров, которые будут получать пенсию старого образца. Остается вопрос, как быть с теми, кому 54, 58, 50? Для них (как и для всех остальных работающих) можно ввести прогрессивную шкалу пенсии, которую уже должно государство: разделить количество роработанных лет на полный стаж до пенсии и получить процент от той пенсии, на которую человек мог рассчитывать по старой системе. Т.е. для человека, которому остался работать год, а он проработал 24, это будет 96%, а для того, кто проработал 2, а еще впереди 32 это будет 4%. Это обязательство государства, как и перед текущими пенсионерами. Остальную сумму можно будет набирать за счет добровольных взносов.

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

4. Наконец, последнее: нужно радикально упростить создание и, что немаловажно, ликвидацию компаний (ООО). Во-первых, администрирование этих процессов: тут можно очень долго говорить. Во-вторых, снизить ограничение по нижнему размеру уставного фонда (неподъемное для многих бизнесов вначале деятельности). Можно говорить, что есть всякие командитные общества, но это же не полноценная бизнес-форма...

2010-03-28

GUI в Common Lisp — еще один миф

Написано для: habrahabr.ru

Бытует расхожее мнение, что в Common Lisp нет или же плохая поддержка графики. Это еще один миф из серии, что Lisp — это язык только для подсчета факториалов. На самом деле, как и в большинстве других прикладных сфер общего назначения (например, веб, форматы передачи данных, взаимодействие с БД и т.д.) в Lisp-среде есть полный спектр библиотек и тулкитов для всех основных платформ с разными уровнями абстракции.

Linux/Unix


Базовой библиотекой для графики из Common Lisp под Unix является CLX. Это аналог xlib, т.е. низкоуровневый клиент, напрямую общающийся по X-протоколу.

Кроме того, есть обертки для основных графических фреймворков разной степени зрелости: LTK, CL-GTK2, CommonQt, CL-CAIRO2. Лично мне доводилось иметь дело с LTK, и работа с ним тривиальна. Хороший пример приложения, его использующего — простой и удобный Lisp-редактор/REPL ABLE.
ABLE screenshot

Windows


Кроме возможности использовать кросс-платформенные фреймворки из прошлого раздела, есть еще LispWorks CAPI, о котором только положительные отзывы. Единственная особенность заключается в том, что, как и большинство профессиональных сред разработки на любых языках под Windows, LispWorks стоит довольно дорого (ок. 1200 $), а CAPI доступна только в профессиональной версии. (Впрочем, попробовать его можно и в trial версии).

Также есть CL-OPENGL, которая, разумеется, кросс-платформенная.

MacOS X


Дополнительно к Unix-библиотекам для MacOS X есть хорошие Cocoa-биндинги в Clozure CL.

Специфические Lisp-решения


Библиотека McCLIM реализует Lisp Interface Manager спецификацию, определяющую весьма развитый протокол по оперированию с графическими примитивами. Хотя спецификация является платформо-независимой, сама библиотека на данный момент основанна на CLX с вытекающими отсюда последствиями пригодности только для Unix-среды. Если это не есть ограничением, то именно она нужна вам, если вы собираетесь писать что-то, сильно завязанное на графику: игру, графический или CAD-редактор, или же новый EmacsClimacs.
Climacs

Оригинальным подходом к GUI, зародившимся в Lisp-среде является проект Cells, который переносит spreadsheet-парадигму взаимозависимых "ячеек" на графический интерфейс. У него несколько Lisp-реализаций c тем или иным бэкендом: CELLS-GTK, CELLTK, CELLO,— а также есть и порты на другие языки.

Выводы


В общем, варианты есть на любой вкус и запросы. С чего начать? Для простого GUI я бы выбрал LTK или CL-GTK2. Оба они кросс-платформенные. Первый по причине максимальной простоты, даже примитивности. Соответственно, и подходит он для примитивных приложений. Второй — потому что это хорошая обертка для современной объектно-ориентированной графической библиотеки, активно развивающаяся, да еще и с русским автором :)

PS. Еще несколько более специфических графических библиотек, конечно, можно найти на Cliki.

2010-03-27

Пару мыслей об SCTest

Как по мне, ценность этого мероприятия даже не в том, что люди могут получить фидбек по своим проектам или потренировать презентации (это можно сделать и другим путем, подчас более эффективным), а в том, что оно создает определенную "школу" стартапов, т.е. происходит передача знаний, опыта, информации от старших коллег, чего-то добившихся, состоявшихся личностей и предпринимателей. На каждом мероприятии приходит кто-то интересный с собственным подходом и взглядом на вещи. Как и в любой школе, тут есть свои гуру, чьи слова постоянно цитируют и в которых находят ответы на все вопросы. В нашем случае это, конечно, Стив Бленк.

В этот раз панель была очень показательна, поскольку на ней присутствовали представители 3-х основных категорий участников test'а:
- состоявшийся, как правило, без сторонней помощи (венчурных инвесторов), а благодаря личным качествам и работе, предприниматель. Как раз пример того, у кого есть чему поучиться;
- tru стартапер, т.е. человек, хорошо изучивший стартап-"технологии", как правильно надувать пузырики, что нужно говорить инвесторам и т.п. (Эти качества, по моему мнению, на самом деле независимы от настоящих бизнес-качеств, которые присущи первой категории людей, так что "настоящий стартапер" может как ими обладать, так и не обладать);
- startup kiddie (по аналогии со script kiddie), т.е. молодой человек, увлеченный хайпом стартап-движения, и порой считающий, что сможет в этой игре переиграть "старших товарищей" (упреждиск: если относить меня к какой-то из этих категорий, то разве что к этой).

Интересна реакция на выступление представителя M$ — как раз синдром startup-kiddies, которые всё знают и всё могут. Я не говорю о том, что у большинства из пристуствующих на компьютере стоит ворованная копия той самой Windows (упреждиск: пишу это на Linux). Дело в другом: выступал отличный профессионал, с местами интересным докладом, содержащим много полезной информации (конечно, не без корпоративщины и прямой рекламы, а также самолюбования: оказывается, Vista была провалом, потому что Intel с чипсетами подкачал :) А реакция была не поискать интересное, а поерничать, когда же он будет кричать "Developers, developers...", и поприкалываться, что человек не умеет пользоваться PowerPoint'ом (что в данном случае было банальной глупостью). Элементарной культуры и самоуважения не хватает...

2010-03-15

What's on Coders' Minds?

Recently I've finished reading Coders at Work by Peter Siebel — another excelent book, that has given me a lot of food for though concerning programming. This weekend I've decided to perform a little analytics on it to see common patterns. I've collected statistics on the questions of programming language use, interrelations between them and topics, that might be relevant for the sequeals (there are so many other great programmers out there in the wild :)

1. Programming language preferences

First comes C, that was mentioned by all the coders. Only Peter Norvig didn't speak about it in some detail. It seems, that everyone had at least medium experience programming in it (except maybe for Norvig and Fran Allen), which proves, that the language truly is the Lingua Franca of programming community.

Next in terms of mentions comes C++, Java and Lisp, that were mentioned by more than 12 people.

C++ netted mostly negative or at least neutral reviews. Moreover, it seems, that only a couple of people had any big real-world coding experience in it: others (like Jamie Zawinski) tried to avoid using it at all cost.

On the contrary at least 4 of the people had a substantial hands-on involvement in Lisp programming, but mostly in the past decades. Others mentioned it more as an important alternative or a design influence.

The same amount of coders were really experienced in the Java world. Others just mentioned it as either the recent derivative of the Algol-family, or as a typical example of object-oriented approach, or for its automatic memory-management capability.

Next comes a pack of languages, that are mostly in wide use today, some gradually falling from grace and some getting more traction. These include: Fortran, Assembler, Python, JavaScript, BASIC, Perl and Pascal. All were mentioned by 6 to 10 people. Among them Assembler, BASIC and Pascal are obviously mostly out of use, but were the three most mentioned first languages in someone's carrers. And Fortran has it's distinct role as the default scientific language (and as the first high-level language).

The next group of languages were mentioned rarely, but they were supported by strong advocates. These are Haskell, Smalltalk, and to a lesser extent Scheme and the ML family. Also the same amount of mentions went to the now defunct previously important ones: Tcl, APL, PL/I, BCPL, and COBOL.

Interestingly enough some of the most wide-spread languages of today: PHP, C#, and Ruby,— were mentioned only once or twice and only in passing. The same concerns 3 other more or less important contemporary languages: Objective-C, Scala and Erlang,— that got only a single mention. The reasons for that seem to be different, but one of them can be, that bright representatives of those languages' communities (except for Erlang's creator Armstrong) were not interviewed.

Finally, a host of other languages, not in use today, got mentioned at least twice. They are: Ada, Algol, Eiffel, E, Prolog, Self, Simula, SNOBOL and Teco.

2. Social graph

Coders social graph

4 of the coders had some Berkley background, 3 — MIT and 2 — CMU. Among the most important organizations were Google (4 coders work there and 4 other mentioned its practices), Microsoft (1 employee and 7 other having some problem with them), IBM (1 employee, mostly everyone's first computer :), Xerox PARC and Netscape/Mozilla.

Also worth mentioning is that 13 of 15 interviewed programmers were from the USA, 2 from Europe.

3. Recommended books

* SICP (5 mentions)
* The Art of Computer programming (obviously) (5 mentions)
* Design Patterns (3), although all spoke of it as at least controversial
* The psychological books: "The Mythical Man-Month" and "Psychology of Computer Programming", each brought up both by Steele and Bloch (2 mentions)

And once were recommended these books:
* Beautiful Code: Leading Programmers Explain How They Think, Andy Oram, Greg Wilson (eds.) (O’Reilly, 2007)
* Code Complete, Steve McConnell (Microsoft Press, 1993)
* Compiling with Continuations, Andrew W. Appel (Cambridge University Press, 1992)
* The Design and Analysis of Computer Algorithms, Alfred V. Aho, John E. Hopcroft, and Jeffrey D. Ullman (Addison-Wesley, 1974)
* The Elements of Programming Style, Brian Kernighan and P.J. Plauger (Computing McGraw-Hill, 1978)
* Elements of Style, William Strunk and E.B. White (Longman, 1999)
* Hacker’s Delight, Hank Warren (Addison-Wesley, 2002)
* Higher-Order Perl, Mark Jason Dominus (Morgan Kaufmann, 2005)
* Java Concurrency in Practice, Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, and Doug Lea (Addison-Wesley, 2006)
* Java Puzzlers: Traps, Pitfalls, and Corner Cases, Joshua Bloch and Neil Gafter (Addison-Wesley, 2005)
* Programming Pearls, Jon Bentley (ACM Press, 1999)
* Purely Functional Data Structures, Chris Okasaki (Cambridge University Press, 2008)
* Zen and the Art of Motorcycle Maintenance: An Inquiry into Values, Robert Pirsig (Bantam, 1984)

The only unexpected omission seem to be "Goedel, Escher, Bach: an Eternal Golden Braid", which is the favorite programming book of many.

4. Other things

The tools in use include Emacs (most mentioned) and GDB. Others were: Java IDEs (Eclipse, NetBeans and IntelliJ), profilers, and correctness checkers, like JSLint, Valgrind and QuickCheck.

There were also a few topics, brought up by many of the coders, apart from the ones, explicitly triggered by the interview questions. Those are API and GUI design (often in connection with the ugliness of WinAPI), transactional memory (and other ways of tackling the massively multicore world), and means of inter-process communication and data-interchange. This may be a suggestion for questions of the future interviews.

Some of the coders came to programming through games or programmed some. 2 times were mentioned Pac-Man and the Game of Life. Also variants of tic-tac-toe, tetris, and Adventure were named. Still game programming is one of the most important parts of the programming industry and no prominent person from it was present (although John Carmack was planned).

5. Conclusions (who to interview next?)

The next candidates can be derived from the mentions' graph, but this can lead to confinement inside of the subset of all programming communities due to the cluster nature of "schools" of influence. So it's also worth considering the missing languages and georgaphical diversity.

So, among the important pears of our coders were:
* Richard Stallman, founder of the FSF and GNU (3 mentions)
* Guido van Rossum, creator of Python (3)
* Bjarne Stroustrup, creator of C++ (3)
* Danny Bobrow, a prominent lisper (3)
* Larry Wall, creator of Perl (2)
* Adele Goldberg, who was at the root of object-oriented movement (2)
* Bill Gosper, another prominent lisper (2)

Also were mentioned the famous computer scientists: Edsger Dijkstra, Tony Hoare and Noahm Chomsky.

It can be seen, that the mentioned persons were mostly programming language designers. But this book showed, that it is equally important to present the opinion of language users, because in many areas it can diverge. In search of such people we can direct ourselves to the "missing languages" side. And the first one of those is actually C++ for its wide spread is not reflected by the interviewed, who were important representatives of that school of thought. Apart from language's creator Stroustrup, 2 people come to mind: John Carmack of idSoftware fame and Raymond Chen from Microsoft. The Ruby camp has another interesting feature: it's main proponents are very loudly heard in the programming world. David Hanson, Dave Thomas, Reginald Braithwhite, Tim Bray and Chris Wanstrath (Github) all have influential blogs. The other one famous Ruby programmer, whom it could be really interesting to interview, is Why the Poignant Stiff. It would also be interesting to hear the voice of those "unknown" heroes of the PHP and C# world, because it can resonate with lots and lots of programmers in the world. From other (well represented in the previous book) communities the prominent figures are Ian Bicking (Python), Edi Weitz (Lisp), Ward Cunningham (Java) and Luke Gorrie (Erlang, Smalltalk, Lisp).

This book also featured a number of interviews with computer scientists: Knuth and Allen, as well as Norvig, Steele and Peyton Jones (although those three are more on the practical side). Among their pears in the CS world, obviously, the most mentioned were Abelson and Sussman. Next come Danny Bobrow, Aho and Ulman (among the living).

Talking about geographical diversity, Edi Weitz and Luke Gorrie represent Europe. Other interesting European coders are the creators of PHP Rasmus Lerdorf, Prolog Alain Colmerauer and OCaml Xavier Leroy. And in every programming community there can easily be named prominent European hackers (like Ola Bini from JRuby team). It is also worth "visiting" Japan, the former USSR (that had it's own tradition in CS and software), and, possibly, India and China. From Russia such people as: Eugene Roshal (creator of the Rar compression format), Stepan Pachikov (founder of Evernote), and Viktor Shchepin (author of ejabberd Erlang-based jabber server),— can be named.

Finally, the last diversity question, that was raised by Peter Siebel himself, is women participation. The first woman candidate, that can be derived from the analysis, is Adele Goldberg. One other woman, that comes to mind, is Allison Randal, working on the Parrot virtual machine and Perl 6 programming language.

(Actually most of the mentioned names were in the list of possibil candidates for the first book).