среда, 30 июня 2010 г.

Common Lisp на миллиард

Самой известной организацией, использующей Common Lisp для разработки коммерческих систем, является, вероятно, ITA Software и кажется это единственная организация, озабоченная проблемами разработки крупномасштабного ПО c помощью Common Lisp (XCVB родилась, как раз, ради нужд этой организации). До не давнего времени я был совершенно не в курсе о размере этой организации, но тут с некоторого времени стали курсировать слухи, что Google пытается её купить и речь идёт о сумме, близкой к одному миллиарду долларов. Мне кажется, что это достаточно крупная сумма для того, что бы навсегда закрыть вопрос о возможности/целесообразности коммерческого применения Common Lisp...

понедельник, 28 июня 2010 г.

Секс, Java и рок-н-ролл

http://www.youtube.com/watch?v=A1zySeNpW20

archimag-lisp-overlay

Сегодня с моим форком gentoo-lisp-overlay, который базировался на github.com, случилось несчастье - он отказался push-ится, мотивируя это тем, что мол нет доступа к ./objects, как будто это и не мой репозиторий вовсе. В поддержке gitnub есть зарегистрированное подобное сообщение, но там человека сразу послали, мол это не проблемы github. Интересно, а чьи это проблемы, если проекты пушился-пушился и вдруг перестал? Потыкавшись, решил попробовать удалить его и создать заново - но создаваться заново с таким же именем он не захотел, мотивируя это уже совсем чем-то странным. Так что, пришлось окончательно его грохнуть и создать новый archimag-lisp-overlay, куда всё и залил. Вроде поломанные при этом ссылки (таких было несколько) починил и теперь всё должно быть как прежде, но только в немного другом месте.

пятница, 11 июня 2010 г.

Продолжение дискуссии: дизайн

Собственно, продолжаем обсуждение проблем разработки программного обеспечения. Тут придётся немного апеллировать к утверждениям из поста thesz, так что прощу прощения за перепечатки.
Если программа может быть написана двумя или более способами, то необходимо произвести выбор одного из них. Archimag предлагает выбрать дизайн наугад и потом оценить, подошёл ли он.
Во-первых, любая программа может быть написана бесконечным количеством способом. Т.е. ситуация, когда имеется только один вариант дизайна не бывает в принципе. Во-вторых, я никогда не действую наугад и, естественно, не рекомендую это делать другим.
Желательно бы делать лучше, например, уметь сравнивать дизайны между собой.
В том самом обсуждении я предложил следующий вариант: сперва сравниваем дизайны по тому, как много сценариев программы они покрывают, потом сравниваем по количеству кода, потребному для реализации дизайна. Такая несложная иерархия.

Если включить в рассмотрение важность сценариев и сложность их реализации и отсортировать их, как это рекомендует gaperton, то получится хорошая система оценки дизайна программы, ещё более иерархичная, быстрее отсеивающая относительно плохие дизайны.
Слава роботам! Так действуют машины, когда им надо решить какую-нибудь более-менее сложную задачу. Т.е. в идеале нужно перебрать все варианты и из них выбрать самый подходящий. Когда вариантов бесконечно много (как в случае с дизайном) надо действовать чуть хитрее. Поиск в пространстве состояний сейчас достаточно хорошо проработанная область, но она содержит алгоритмы для машин, не для людей. Точнее, мы используем эти алгоритмы для того, что бы научить машину делать нечто, для чего ранее требовался человеческий интеллект. Возможно, когда-нибудь программы будут писать роботы, который действительно будут действовать схожим образом. Хотя тоже сомнительно, ибо простой перебор вариантов не работает даже в шахматах, которые хоть и являются довольно сложной проблемой, но довольно ограниченной и, мало того, конечной. В целом, я считаю предложенный способ сравнения, по крайней мере, наивным.

Человек действует не так. Мы не может позволить себе писать программу несколько раз и выбирать лучший вариант (впрочем, подобный процесс происходит на более высоком уровне: разные команды пишут схожие продукты, среди которых практика использование отбирает лучшие.).
типы языка Хаскель основываются на логике, по-моему, на классическом её варианте (могу быть неправ, но в прилагательном;). Выражая типы компонент и пробуя прикинуть приблизительную их реализацию Хаскеле мы проверяем наш дизайн с помощью системы автоматического доказательства теорем. Это помогает отсевать те варианты дизайна, что не могут быть логически стройно сформулированы.
Слава роботам! Или не слава... Любое доказательство теорем основывается на других теоремах и аксиомах. Если нижележащие теоремы/аксиомы неверны, то любое основанное на них доказательство таковым не является и вообще ничего не значит, т.е. не имеет никакой ценности. При разработке программного обеспечения, в большинстве случаев (за исключением некоторых, достаточно редких в повседневной практике областей) мы не обладаем полнотой информации о создаваемой системе, а значит просто не можем физически положить в основу корректный набор аксиом (и теорем).

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

Цели

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

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

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

Инструмент

Основным инструментов для работы над дизайном является декомпозиция. Если говорить в целом, то моя позиция заключается в том, что цель декомпозиции найти "хорошие простые ответы на правильные вопросы", при чём, как обычно "правильные вопросы" это больше половины дела. Применительно к языкам программирования имеет смысл выделять какие средства предоставляет язык для отображения выбранного способа декомпозиции в коде. Для Common Lisp я выделяю следующие возможности, которые определяют дизайн системы:
  • Функции (включая замыкания)
  • Структуры данных (структуры, классы, различные виды списков и т.д.)
  • Generic-фукнции (которые следует рассматривать отдельно и от функций, и от классов)
  • Макросы
  • Динамические переменные
  • Рестарты
  • Пакеты
Это довольно много (по сравнению с большинством других языков) и с одной стороны предоставляет весьма мощный набор инструментов, которые могу облегчить процесс создания качественного дизайна, а с другой такое богатство может завести разработчика в дебри. Кстати, проблема многообразия средств также характерна и для С++, который заслужил благодаря этому довольно дурную славу, но это скорей не проблема языка, а проблема уровня самодисциплины у разработчиков. В Common Lisp же эта проблема выражена ещё сильнее и разработчик должен постоянно контролировать свое желание "заюзать ещё пару крутых фич".

Кстати, насчёт "хороших простых ответов на правильные вопросы" хочу привести однин, как мне кажется, вполне уместный исторический пример: Коперник vs Птолемей. С точки зрения математики, особой разницы между подходами Коперника и Птолемея нет, они оба вполне корректны, мало того, опять же с точки зрения математики последователи Птолемея делали совершенно потрясающую вещь - раскладывали орбиты планет в ряды Фурье и это за 1000 лет до появления математического анализа! Коперник же задал правильный вопрос (что вокруг чего вращается?) и дал на него простой ответ, в результате смог объяснить наблюдаемое движение небесных тел с помощь значительно более простого математического аппарата.

Процесс
Определившись с целями и доступным инструментом можно попробовать разобраться в процессе создания дизайна. Если рассматривать вопросы декомпозиции, то думаю все согласятся, что "правильная декомпозиция с первого раза" для достаточно сложных проектов может является замечательным примером человеческого гения, периодически освещающего наш мир. В большинстве же случаев необходим достаточно длительный поиск подходящего решения, основанный на интуиции, способности предвидеть и понимании создаваемой системы. Ключевым фактором является склонность человека к ошибкам, а также замечательная способность мозга создавать иллюзию того, что мы обладаем полнотой информации. Единственным известным мне действенным способ проверки правильности принятых решений является непосредственное написание кода (который можно реально запустить), во время которого выявляются все не-стыковки и противоречия в дизайне. Одним из, уже можно считать, традиционных и устоявшихся способов проверки дизайна является написание unit-тестов: хотя разработка программы может находиться ещё только в самой ранней стадии, благодаря им мы получаем реальный код, который можно запустить и проверить принятые решения. Другим, ещё более заслуженным способом является создание прототипов. Common Lisp предоставляет ещё одну блестящую возможность, которая может быть эффективно использована для работы над дизайном - REPL. Тут я хочу отдельно отметить, что под REPL я понимаю не только командную строку (которая есть сейчас для многих динамических языков: Python, Ruby, JavaScript и т.п.), но целый комплекс средств, предоставляемых SLIME или IDE коммерческих реализаций, которые значительно упрощают интерактивную разработку (например, простая перекомпиляция отдельной функции или класса непосредственно во время работы над исходным кодом, наглядное раскрытие макросов и т.п.). Очень простая возможность проверки решений буквально подталкивает разработчика к большому количеству экспериментов с исходным кодом, что создаёт все предпосылки для создания качественного дизайна. При этом, весьма важную роль играет динамическая типизация, которая позволяет нарушать общую корректность программы ради проверки какой-либо идеи. В языках со статической проверкой типов внесение изменений ради эксперимента может оказаться болезненным, что препятствует эффективному поиску (обычно люди избегают боли).

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

четверг, 10 июня 2010 г.

cl-json и plists

Сначала несколько слов о JSON и Common Lisp.

Есть достаточно распространённое мнение, что CL хорошо подходит для генерации всяких XML, HTML и т.п., и для решения подобных задач предлагается множество решений на базе s-выражений. Из своего опыта могу заключить, что подобное мнения однозначно является заблуждением и для генерации XML/HTML лучше всего подходят шаблоны, но в этом случае CL не имеет специфичных преимуществ над другими языками (ну кроме тех, что CL просто лучший язык).

А вот JSON действительно очень хорошо подходит для использования с Common Lisp, ибо может непосредственно отображаться на списки, которые являются основной структурой данных в CL. Поскольку JSON также непосредственно отображается и на хэш-таблицы, которые являются основным типов данных для JavaScript (можно утверждать, что хэш-таблицы в JavaScript имеют такое же ключевое значение, как списки в CL), то связка CL<=>JavaScript с взаимодействием на основе JSON вообще смотрится очень выигрышной: и там и там без каких-либо дополнительных усилий можно непосредственно работать с наиболее естественной формой представления данных.

В какой-то момент для работы с JSON в Common Lisp я выбрал библиотеку cl-json, уже точно не помню, что мной двигало, но наверное то, что это простое наиболее известное решение. Сейчас я подумываю о том, что бы изменить это решение, ибо авторы cl-json кажется уж очень увлеклись своими концепциями. Но не суть. Так вот, в этой библиотеке для представления объектов/хэшов используются alists, которые я считаю не удобными для использования человеком и предпочитаю строить свои решения на базе plists. Соответственно, имеющаяся у меня инфраструктура гораздо более дружественна к plists и я захотел переоренитровать cl-json на работу с plists. Добился я этого следующим кодом, который поясняет моё отношение к данной библиотеке - он хотя и довольно краток, но его нельзя написать без изучения исходников библиотеки:
(defun encode-json (obj)
(flet ((encode-json-list (list stream)
(if (keywordp (car list))
(json:encode-json-plist list stream)
(json::encode-json-list-guessing-encoder list stream))))
(let ((json::*json-list-encoder-fn* #'encode-json-list))
(json:encode-json-to-string obj))))

(defun decode-json (str)
(flet ((accumulator-add-pkey (key)
(json::accumulator-add (funcall json:*identifier-name-to-key*
(funcall json:*json-identifier-name-to-lisp* key))))
(accumulator-add-pvalue (value)
(json::accumulator-add value)))
(let ((json:*object-key-handler* #'accumulator-add-pkey)
(json:*object-value-handler* #'accumulator-add-pvalue))
(json:decode-json-from-string str))))


CL-USER> (encode-json '(:a 1 :b (:c 2 :d 3) :e 4))
"{\"a\":1,\"b\":{\"c\":2,\"d\":3},\"e\":4}"
CL-USER> (decode-json (encode-json '(:a 1 :b (:c 2 :d 3) :e 4)))
(:A 1 :B (:C 2 :D 3) :E 4)
Да, я использую darcs-версию библиотеки, которая содержит важное изменение, почему-то не оформленное в виде какого-либо релиза.

среда, 9 июня 2010 г.

Дискуссия

В комментариях к недавнему посту thesz о том "Зачем нужны выкрутасы с типами" завязалось обсуждение и в результате автор предложил мне расширить формат дискуссии и ответить в своём блоге на следующие вопросы:
- почему вы считаете развитием явление, которому более подходит название "расширение использования",
- почему вы считаете, что дизайн не имеет критериев сравнения,
- в моём посте приведён пример "дизайна системы" на основе типов, причём присутствует не менее двух итераций, попроще и посложнее; и до этого писал похожие посты; почему вы считаете, что никто и никогда не показывает, как пользоваться типами при дизайне систем,
- почему вы считаете, что для программирования не нужен ум (и это особенно интересно),
- почему вы считаете не зафиксированное в документе ТЗ отсутствующим ТЗ,
- известна ли вам теория тестирования,
- знаете ли вы об использовании типов для автоматической генерации тестов в библиотеке QuackCheck,
- можно ли протестировать корректность параллельной программы,
- пожалуй, всё.
Отказаться от данного предложения я, конечно, не могу.


Здесь речь идёт о повышении в течении последних 20 лет популярности языков с динамической типизацией, что вряд ли является следствием развития CS. Ответ заключается в том, что развитие "практического программирования" это прежде всего не появление новых фич в языках и теоретических подходов, а прежде всего накопление и критическое переосмысление опыта разработки реальных систем. Результатом данного процесса, в первую очередь, является появление методик разработки, непосредственно не привязанных к используемым инструментам. Такие практики, как "парное программирование", "code review" или "тесты вперёд" вряд ли даже с натяжкой могут быть отнесены к области CS, но именно они вносят наиболее серьёзные изменения в процесс разработки ПО. Поэтому, процесс популяризации динамических языков я называю именно развитие поскольку в результате этого произошло (и продолжает происходить) существенное изменение методологии, а инструмент нельзя рассматривать отдельно от методик его использования. Возможно, Python и не блещет глубиной идей, но на развитие методологии разработки он оказал гораздо большее влияние, чем все наследники ML вместе взятые.

> почему вы считаете, что дизайн не имеет критериев сравнения

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

> в моём посте приведён пример "дизайна системы" на основе типов, причём присутствует не менее двух итераций, попроще и посложнее; и до этого писал похожие посты; почему вы считаете, что никто и никогда не показывает, как пользоваться типами при дизайне систем,

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

> почему вы считаете, что для программирования не нужен ум (и это особенно интересно),

Вообще-то, ничего такого я не говорил. А говорил, что количество ошибок в коде не связано с умом, а скорей со свойствами характера. Собственно, такое наблюдение я сделал как в результате непосредственной работы с другими программистами, так и в результате наблюдения за некоторым open-source проектами.

Впрочем, у меня есть кое-какое мнение и о затронутой теме. Как мерить ум я не знаю, мне известна только методика измерения IQ, которая вызывает много нареканий. Но, за неимением другой, можно попробовать осторожно опереться на неё. Так вот, большинство программистов, с которыми я общался, имели уровень интеллекта выше среднего и думаю, что вполне можно считать, что большинство программистов имеют уровень IQ в районе 120-140. Где-то мне попадалась информация, что большинство Нобелевских лауреатов имеют уровень IQ в районе 130, а 120 встречается чаще, чем 140. Поскольку вряд ли деятельность, за которую вручают Нобелевские премии, является менее сложной, чем программирование, то можно утверждать, что для достижения выдающихся результатов после превышения определённого уровня IQ наиболее значимым являются личностные качества. Т.е. уровень IQ в 120 позволяет эффективно работать даже над весьма сложными задачами, а определяющим фактором для успеха являются свойства характера. Я не вижу каких-либо оснований утверждать, что программист с уровнем IQ в 140 будет более эффективен, чем программист с уровнем IQ в 120.

> почему вы считаете не зафиксированное в документе ТЗ отсутствующим ТЗ

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

> известна ли вам теория тестирования

В своё время прочитал довольно нудную книгу "Введение в тестирование программного обеспечения" (Луиза Тамре), но там излагается скорей практическое руководство, чем какая-либо теория.

> знаете ли вы об использовании типов для автоматической генерации тестов в библиотеке QuackCheck

Нет.

> можно ли протестировать корректность параллельной программы

Протестировать можно всё, что угодно, а вот гарантировать обычно ничего нельзя. Впрочем, для веб-приложений используются системы, которые генерируют нагрузку на веб-сервер аналогичную реальному трафику. Повышая интенсивность нагрузки можно в достаточно сжатые сроки провести весьма качественное тестирование, которое, хотя ничего и не гарантирует, но обычно позволяет выявить практически значимые проблемы. Наверняка, схожие подходы можно использовать для тестирования большинства параллельных программ. Кстати, для тестирования потоков в SBCL под Windows, реализацией которых сейчас занимается dmitry_vk, как раз в том числе использовалось тестирование веб-сервера и тривиального генератора нагрузки.

Кажется всё. Хотя пост и не посвящен обсуждение Common Lisp, но тем не менее я использую тэг lisp для того, что бы данный пост попал в Russian Lambda Planet.

вторник, 8 июня 2010 г.

Made with Common Lisp 2

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


Скринкаста пока не будет, ибо всё таки функционал ещё готов не полностью.

В отличие от предыдущей версии, эта работает не только с Firefox, но также с Chrome и Opera. И вообще, ориентируюсь на HTML5.

Ключевое значение в данном проекте приобрела cl-closure-template, ибо позволила сделать интерфейс более сложным и одновременно заметно сократить размер и сложность JavaScript кода (а также оказывает заметное влияние на дизайн серверного кода), так что теперь я могу смело утверждать, что Common Lisp используется не только на серверной стороне, но так же имеет ключевое значение и на стороне клиента.

пятница, 4 июня 2010 г.

iolib.process

Давно хотел, но то времени не было, то мотивация подводила, и вот вчера звёзды совпали и я написал iolib.process - очень простую библиотеку для создания дочерних процессов и взаимодействия с ними через стандартные потоки ввода/вывода. Её можно рассматривать как не зависимую от реализации альтернативу sb-ext:run-programm - реализация полностью выполнена на основе возможностей библиотеки iolib. По дизайну она существенно отличается от sb-ext:run-programm, которая представляется мне излишне запутанной: sb-ext:run-programm это, конечно, крутая вещь, но мне не очень понятны потребности человека, которые её разрабатывал. iolib.process требует git-версию iolib и должна работать, в принципе, на любых UNIX-системах. Можно было бы сделать и windows-версию (я писал когда-то соответствующий код на C++), но iolib не содержит необходимый инфраструктуры (да и windows у меня уже давно нигде нет). Для запуска дочерних процессов используется sh (которая, кажется есть на всех юниксах), так что нет потребности указывать полный путь или там каталоги поиска и можно даже вызывать конвейеры, например:
CL-USER> (iproc:with-child-process (conveyer "cat | grep good" :stdin t :stdout t)
(let ((*standard-output* (iproc:process-input conveyer)))
(write-line "Haskell is bad")
(write-line "Python is bad")
(write-line "Common Lisp is good")
(write-line "IMHO"))
(iproc:process-input-close conveyer)
(read-line (iproc:process-output conveyer)))
"Common Lisp is good"

среда, 2 июня 2010 г.

GPL 3 - кусается

Эх, первый раз испытал отрицательные эмоции от того, что какой-либо проект использует GPL 3. Речь о библиотеке cl-data-format-validation. Я связался с автором на предмет возможного распространения этой библиотеки на условиях Lisp LGPL и получил следующий ответ:
Given Common-Lisps dynamic nature and the shortage of libraries (compared to more recent toy languages) I consider it appropriate to just use GPL3 - not LGPL. In the unlikely event someone wants to use my library code in a proprietary derivative they can contact me for a different license however I am not going to support them for free.
Вот ну очень жаль :( И дело не в том, что данная библиотека делает что-то фантастическое. На самом деле это довольно просто решение. Но, при этом, это очень точно решение, предоставляющее общий подход для упрощения сразу целого ряда типовых для web-разработки проблем. И это очень соответствует моему пониманию разработки, что даже сложные проблемы имеют достаточно простые решения если найти "критические точки", проблема лишь в том, что бы правильно определить эти самые "точки приложения силы" (а это всегда не просто, хоть потом решение и кажется очевидным). И с этой позиции cl-data-format-validation представляется мне очень точным и грамотным решением.

Отказаться от данной идеи выше моих сил, так что, видимо, придётся писать свою библиотеку на данную тему, но под Lisp LGPL, а также с блэкджеком и развратными женщинами, ну т.е. с тестами и поддержкой локализации. Где вот только время на это найти...