tag:blogger.com,1999:blog-60316479615060054242024-02-19T05:15:59.160+02:00Lisp, the Universe and EverythingVsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.comBlogger42125tag:blogger.com,1999:blog-6031647961506005424.post-20105554836243714662016-12-31T14:21:00.000+02:002016-12-31T14:23:28.554+02:00Уроки курса «Алгоритмика»<img src="https://prjctr.com.ua/assets/static/v1.4.2/images/logo.png" style="float:right; margin-left: 30px;"/>
<p>
28 декабря состоялся второй выпуск слушателей курса по алгоритмам в Projector. Пока что именно "слушателей", а не полноценных выпускников, потому что до 100% того, чем может и должен стать этот курс, еще очень далеко. Но чтобы достичь этих 100%, в том числе, нужно подвести некоторые промежуточные итоги. Первая попытка, прошедшая весной и в начале лета, официально позиционировалась как бета-версия, но и вторая, по факту, тоже оказалось пробной, т.к. курс был разделен на 2 уровня сложности и существенно переработан.
<p>
Что можно сказать точно — что курс нужен. Эти знания и навыки дают возможность программисту перейти из категории кодера в полноценного разработчика, способного решать задачи любой сложности. Именно такие люди нужны как гуглам и фейсбукам (и поэтому они уделяют такое внимание теме алгоритмов на собеседованиях), так и небольшим амбициозным продуктовым командам. Да и самому себе: это один из аспектов, котороый дает возможность программисту "расправить крылья" и полюбить свою работу, получив возможность сделать ее разнообразной и приносящей гораздо большую отдачу. И то количество людей, которые этим активно интересуются, показывает, что на рынке тоже есть такое понимание. Хотя эти знания относятся к базовым для университетского курса Компьютерных наук, далеко не у всех была возможность их освоить: кто-то пришел в профессию не классическим путем, кому-то не повезло с вузом и преподавателями, кто-то, просто, прогулял и теперь, набравшись ума-разумуа, хотел бы наверстать упущенное.
<p>
Другой вопрос, что разным категориям из этой группы людей подходят разные форматы обучения. Для кого-то это онлайн-обучение, для кого-то — классический университет, и только немногие, объективно, готовы к формату интенсивных трехмесячных курсов. Практика показывает, что где-то треть людей, записавшихся и начавших ходить на такие курсы, быстро осознает это и отпадает. В итоге, до конца двух первых курсов у меня доходило чуть меньше половины участников, причем далеко не все из них успешно проходили весь материал. Т.е. помимо тех, кто просто попал не туда и ушел сразу, есть еще примерно такое же количество, которые могли бы получить хороший результат, но этого не произошло. Почему так? В первую очередь, конечно, причина в недоработках с моей стороны.
<p>
Наконец, если в востребованности направления алгоритмов в целом у меня нет сомнений, то вот потребность именно в продвинутом курсе пока что не доказана. Да, первый набор был закрыт, но мы сделали ключевую ошибку: не отфильтровали его должным образом, т.к. надеялись, что те, кто в себе не уверен, пойдут на базовый курс. Это сработало лишь отчасти. Второй момент: люди ждут от продвинутых алгоритмов нечто большого, чем только алгоритмов — конкретно, они хотят машинного обучения. И это можно понять: все хотят машинного обучения :) Оно было заключительной частью программы последней итерации курса (и прошло, как по мне, на ура), но из-за этого пострадал весь остальной материал, который объективно не вмещался в формат. Поэтому машинного обучения больше не будет в рамках Алгоритмики Про. Впрочем, ее сомневаюсь, что очень скоро оно появится в виде отдельного курса, т.к. спрос на это направление сейчас зашкаливает.
<p>
В итоге, что же такое Алгоритмика Про и есть ли в достаточном количестве те, кому она нужна? Это сложный курс и сложный вопрос. Его основная идея — это погрузить практикующих программистов в проблематику решения реальных алгоритмических задач: работу с данными размером как минимумом в гигабайты, или реальными графами с миллионами узлов, разобраться в том, как функционируют современные базы данных, редакторы и системы контроля версий, изучить различные алгоритмы оптимизации и применить их для задач из окружающей действительности, залезть под капот сервера и уменьшить время ожидания запросов в его очереди. К сожалению, пока что я только нащупываю это направление. С одной стороны, не все практические проблемы, до которых можно дотянутся, лежали на поверхности (хотя сейчас по опыту двух курсов эта тема для меня довольно неплохо прояснилась). С другой — для этого не было достаточно готовности, вовлеченности и отдачи со стороны участников. Много ли у нас программистов, которые хотят прокачаться в алгоритмической разработке, находятся на должном уровне и готовы выделить для этого большой кусок времени в течение трех месяцев — вот это главный вопрос, ответа на который мы пока не знаем. Такие люди, одназначно, были на первых двух моих курсах. Но, к сожалению, их можно было сосчитать на пальцах одной руки, когда нужно было бы задействовать хотя бы четыре...
<h2>Основные вызовы курса для меня</h2>
<p>
Первый из них я, фактически, описал выше. С самого первого дня обсуждения мы говорили о том, что этот курс должен быть максимально практичным. И я честно старался добиться этого. Но практичность имеет разные аспекты: один аспект — это практическая работа, т.е., в данном контексте, банальное программирование. К сожалению, мне не удалось привлечь к этому каждого участника, хотя по моими прикидкам и прошлому опыту казалось, что это произойдет естественно. Второй аспект практичности — это обсуждение (и реализация) примеров из реального мира, где это все используется. Этому я также старался уделять должное внимание: как рассказывать кейсы на лекциях, так и давать подобные задания (хотя их было меньше, чем могло быть), так и в аспекте курсовой работы. К сожалению, эта часть, как по мне, основывается на первой, т.е. активном программировании, а без него она тоже сильно буксовала. Это проблема номер один, которую я намерен активно решать в рамках следующего курса.
<p>
Она также упирается в наличии удобной среды для такой работы у каждого участника курса, а также ее единства для всех участников, чтобы можно было где-то подталкивать продвижение вперед. Я начал первый курс с демонстрацией примеров кода на Лиспе. Это не всем нравилось, хотя все был об этом честно предупрежденны. В итоге, второй вариант был более абстрактным: описание алгоритмов на доске без привязки к тому или иному языку. Этот AB-тест показал, что так не работает: нужно иметь под рукой код, который можно пощупать и покрутить, и который можно подкинуть человек, который застрял, чтоб он мог двигаться дальше. Учитывая мою собственную привязку к Лиспу, а также то, что язык хорошо подходит для реализации алгоритмов, я планирую продолжать настаивать на его использовании. Почему не Python или что-то другое? Во-первых, многие языки не очень пригодны для изучения алгоритмов вообще: яркий пример — это JavaScript, который слишком не четок, не имеет полноценной поддержки арифметики и нужных структур данных, другая крайность — это статические языки, особенно низкоуровневые, которые, с одной стороны, дают много возможностей для оптимизации, но, с другой, вносят слишком много ограничений (в частности, более сложный процесс разработки) и избыточной сложности. Что до Питона, то он более-менее подходит, но я его, по-просту, не люблю, тем более, что курсов по алгоритмам на Питоне хватает. Что ж до конкретно Лиспа и его особенностей: я считаб, что это хороший фильтр, которых нам не хватало при наборе на предыдущие курсы. На самом деле, разобраться в Лиспе на базовом уровне, необходимом для этого курса, не сложно. И если у человека не хватает мотивации и доверия, чтобы это сделать, это многое говорит о его дальнейшей мотивации преодолевать трудности во время самого курса. А, как показала практика, цитируя одного из студентов курса, "Сам факт, що лекція коштує 550 грн, ще не має достатньо стимулючого ефекту," щоб виконати домашнє завдання :(
<h2>А зачем, вообще, платить?</h2>
<p>
Очевидный и резонный вопрос, на который должен ответить для себя каждый желающий пройти этот курс — это вопрос, стоит ли он того и зачем, вообще, платить? Ведь есть интернет, википедия и прекрасные онлайн-курсы, на которых можно изучить то же самое. И это, действительно, так! В отличие от онлайн-курсов, оффлайн-курсы не могут быть бесплатными, поскольку они должны окупать аренду помещения и другие расходы, достойную оплату преподаваталя и персонала, и давать какую-то прибыль организаторам. И к ним не применима фримиум-модель, которую используют Курсера и другие. Да и, вообще, за все в жизни нужно платить.
<p>
Но если взглянуть с практической стороны, ROI любого обучения — это отношение полученного результата к затратам денег и времени на его достижение. По-идее, оффлайн-курсы могут выигрывать за счет более высокого среднего результата и меньших затрат времени. Что может войти в этот лучший результат?
<ul><li>во-первых, как ни банально это звучит, "волшебный пендель", т.е. внешняя мотивация пробежать этот забег от начала и до конца. И вложенные деньги тоже являются частью этой мотивации, хотя, как показывает практика, не достаточной. В этих курсах пока не было соревновательного момента, который присущ классическому обучению, и это еще одно направление, над которым нужно немного поработать (наметки есть)
<li>во-вторых, возможность личного общения с преподавателем и другими учениками. Для меня это, на самом деле, одна из главных мотиваций делать этот курс: возможность взаимодействия с программистами, которые ищут и хотят развиваться в профессии. Парадокс в том, что даже не смотря на то, что я получаю неплохие деньги за этот курс, я все равно зарабатываю больше за основную свою работу. Т.е. меньший заработок должен компенсироваться чем-то другим. Для меня это другое — это возможность со-творчества с участниками курса. А это значит, что мы должны быть на одной волне и идти, в первую очередь, иметь желание попасть на занятие и провести его полноценно. В идеале, завязавшиеся во время обучения связи должны быть одним из главных долгосрочных активов после окончания курса
<li>комфортная среда обучения и общения, причастность к сообществу. Projector делает очень важное дело, создавая на основе своей площадки сообщества профессионалов в сфере дизайна, продуктовой разработки и программирования (а также, в будущем, я думаю и других областях)</ul>
<h2>Кому стоит и не стоит идти на курсы по алгоритмам в Projector</h2>
<p>
Для меня, на самом деле, это ключевой вопрос всей этой темы. Ни я, ни Projector не ставим себе цели массовости и сверхприбылей. Во-первых, это не устойчиво и закончится пшиком, во-вторых, никакого внутреннего удовлетворения от такой работы не получишь. Между группой из 8-10 мотивированных людей, которые знают, куда и зачем пришли, и 20 вольно интересующимися я выбираю первый вариант, хотя второй, на самом деле, проще. Первые две итерации курсов были поиском: поиском правильного формата и адекватной ему аудитории.
<p>
Мой вывод следующий: эти курсы подходят тем, кто
<ul><li>уже имеют некоторый опыт программирования (в идеале, хотя бы пару лет)
<li>осознал для себя ценность алгоритмов, и не будет мучаться вечным вопросом украинского студента: "где же это все применяется в реальной жизни?" Ответ на него, с моей точки зрения: кто хочет, тот найдет. Спрос на алгоритмических программистов есть, и хотя он не более нишевый, но в нишах всегда больше и интерес, и доходы
<li>готов (как психологически, так и организационно) 3 месяца стабильно уделять минимум 10 часов в неделю этим занятиям, а также, что еще более важно, уделять им основной ресурс своего мозга. Практически, это означает, что в это время не удастся полноценно интенсивно работать. Как показали эти 2 курса, самое лучший период для участия в этой авантюре — это либо перерыв между работами, либо последние курсы вуза. Те, кто пытаются одновременно интенсивно работать в разработке и учиться, либо забивают на курс, либо жалуются, что работа начинает страдать, либо берут отпуск, чтобы подтянуть хвосты. Также это может прокатить для тех, у кого работа сейчас не предполагает активное написание кода. Если же вы только что поменяли работу, как раз должны заканчивать важный проект, ожидаете рождения ребенка (да, и такие случаи уже бывали :) или же собираетесь уехать в середине в отпуск или коммандировку, то этот формат точно не для вас</ul>
Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com0tag:blogger.com,1999:blog-6031647961506005424.post-23106816001302025892016-01-03T21:42:00.000+02:002016-01-03T21:45:31.452+02:00КПИ no more<div dir="ltr" style="text-align: left;" trbidi="on">
Это последний год, который я читаю в КПИ курс <a href="https://vseloved.github.io/spos.html">"Операционные системы"</a>. Пролистав архив переписки, я сделал вывод, что начал это делать в 2009 году, т.е 6 лет назад, и за это время через меня прошло более 500 студентов. Выходит, я проработал преподавателем уже больше, чем проучился в институте, иными словами мой долг перед альма матер можно считать исполненным :)<br />
<br />
В этом тексте я хочу описать свой опыт и ответить на вопрос, почему решил уйти. Начнем с конца: потому что, кроме меня, это, по большому счету, никому не нужно. А у меня с каждым годом все меньше и меньше времени делать никому не нужные вещи.<br />
<br />
А теперь по порядку.<br />
<br />
Шесть лет назад я начинал создавать этот курс с чистого листа. Что мне нравилось, это то, что был полный карт-бланш для экспериментов и выбора методик преподавания, и это дало возможность попробовать много всего и понять, что работает (для меня), а что нет. Для студентов это не всегда было благоприятно, т.к. далеко не все эксперименты были успешными, но, все же, я считаю, что это лучше, чем то, что было у нас, когда этот курс просто спускался на тормозах (это, собственно, и была моя изначальная мотивация прийти читать его). Каждый год я ставил себе условную оценку за проделанную работу, и если вначале это была твердая двойка, то сейчас, я считаю, что это минимум 4 или 4+ (в прошлом году я также попросил студентов поставить мне оценку, и у них тоже в среднем вышла четверка).<br />
<br />
В целом, курсом я преследовал такие цели:<br />
<br />
<ul style="text-align: left;"><li>ввести студентов в парадигму системного программирования, показать ее интересные стороны и задачи</li>
<li>дать им альтернативный взгляд на разработку ПО (а не только ООП и C#, которые являются основными на нашей кафедре)</li>
<li>научить работать с Unix-средой и консолью</li>
<li>научить не боятся ассемблера и, вообще, низкоуровневых вещей</li>
<li>чуть-чуть познакомить с новыми языками прогарммирования, которые актуальны для системной разработки</li>
<li>познакомить с опен-сорсом</li></ul>
Не все из этих целей я достигал в рамках курса в тот или иной год: иногда получалось лучше что-то одно, иногда другое, но, думаю, что все они акутальны. Кроме того, если задаться целью, то на основе этого курса легко можно сделать продвинутое продолжение — "Разработка ОС" (аналог <a href="http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-828-operating-system-engineering-fall-2012/">MIT Operating System Engineering</a>). За 6 лет удалось собрать достаточно материала, который доступен в виде <a href="https://vseloved.github.io/spos.html">конспекта и заданий</a> (а также <a href="https://github.com/vseloved/kpi-spos">исходников к ним</a>). Более того, в качестве побочного продукта я разработал небольшую систему публикации материалов (свой leanpub), а также тестирования теории. Единственное, до чего так и не дошли руки — это система автоматического тестирования лабораторных. Если придумать, как эффективно масштабировать такую штуку, то это был бы, действительно, золотой грааль, но, к сожалению, основная работа тут, кажется, все равно будет заключаться в написании ручных тестов. А главный закон преподавателя: ручная проверка практических работ — самый большой убийца мотивации...<br />
<h4 style="text-align: left;">
Почему это никому не нужно в КПИ?</h4>
Четыре года назад я написал колонку на ДОУ <a href="http://dou.ua/columns/ukrainian-stanford/">"Украинский Стенфорд"</a>, в которой изложил свой ограниченный взгляд на проблемы, вызовы и возможности нашего высшего технического образования. С тех пор ситуация конкретно в КПИ скорее ухудшилась: ушли многие из старых профессоров, многие из хороших молодых преподавателей также по разным причинам не задержались, так что еще лет 5 и на ФИВТе преподавать, похоже, останутся только люди, не имеющие особого отношения ни к индустрии, ни к науке. Впрочем, это не значит, что такая ситуация везде: мне кажется, сейчас достаточно 1-2 ориентиованных на модернизацию людей в руководстве какого-то факультета или кафедры, чтобы организовать там вполне качественное обучение (все остальное, в принципе, есть). Иными словами, с моей точки зрения основным тормозом изменений университета сейчас является его руководство (на всех уровнях). В связи с этим интересно посмотреть, как будет развиваться ситуация в ХНУРЕ, куда ректором пришел Эдуард Рубин...<br />
<br />
Что также интересно, внешне КПИ меняется в лучшую сторону. Хорошо развивается кампус, причем в этот процесс активно включились студенты с такими проектами, как <a href="https://vk.com/belka_space_kpi">Белка</a>, <a href="http://vezha.kpi.ua/">Вежа</a> или <a href="http://rkpi.me/">Радио КПИ</a>. Много движения в культурной и социальной жизни (вопреки инерции системы). Отличная инициатива — <a href="http://summerschool.ssa.org.ua/">Летняя школа</a>, которая проходит уже 10 лет и в которой я участвовал последние 2 года. В прошом году мы даже провели <a href="http://tedx.kpi.ua/">TEDxKPI</a>! Обычно, такие позитивные внешние проявления следуют за изменением внутренних процессов, но тут имеет место несколько иная динамика: университет — это по определению открытая система, которая постоянна испытывает приток новой крови, и среди этой новой крови действуют те же тренды, что и по всей стране ("бери и делай"). Но, в то же время, с такой же легкостью, с которой новая энергия прибывает сюда, она здесь и надолго не задерживается: активным студентам почти нет стимула оставаться в аспирантуре, а для многих преподавателей это, фактически, волонтерство (например, моя зарплата в КПИ более чем в десять раз меньше тех денег, которые я могу заработать в индустрии), которое почти всегда не стабильно. Именно поэтому я говорю о том, что при ситуацию можно было бы поменять при правильной ориентации руководства: на улучшение качества студентов (за счет их количества), поддержку новых инициатив, уменьшение бюрократии.<br />
<h4 style="text-align: left;">
Однако, к сожалению, стратегического видения развития университета нет.</h4>
С моей точки зрения, есть 2 рабочих модели высшего образования:<br />
<br />
<ul style="text-align: left;"><li>элитарная, когда отбирается небольшое количество самых талантливых и мотивированных студентов и преподаватель работает с ними индивидуально (к ней есть хороший "анекдот" про астрофизика, который читал свой курс в какой-то далекой обсерватории только для двух студентов, оба из которых потом стали Нобелевскими лауреатами)</li>
<li>массовая, когда набирается большая группа студентов и их всех подтягивают до какого-то базового уровня</li></ul>
<br />
Очевидно, что КПИ реализовывает второй подход: на последнем потоке второго курса, которому я читал, учится около 100 человек. И таких потоков по направлению компьютерных наук в университете несколько. Но чтобы массовая система работала, она должна быть действительно системой (в которой нет слабых звеньев). В первую очередь, студенты должны быть мотивированы. По принципу кнута и пряника.<br />
<br />
Позитивной мотивацией должно быть желание получить современную и перспективную специальность, а в процессе делать интересные и прикольные штуки, а негативной — реальная возможность вылететь. К сожалению, в КПИ обе эти мотивации недоразвиты. По большому счету, в наших реалиях на первом и втором курсе нужно выгонять минимум 20% студентов (примерно столько людей у меня на курсе вообще ничего не делает по ходу семестра и начинают пытатся что-то сдать в лучшем случае к его концу, если не к сессии). Но систематической политики выгонять нет, скорее, наоборот, есть вялое сопротивление тем, кто пытается это делать.<br />
<br />
Что до позитивной мотивации, то в КПИ ее перебивает передающаяся из поколения в поколения совковая традиция этого университета, который можно выразить несколькими популярными у студентов мемами:<br />
<br />
<ul style="text-align: left;"><li>главное — это сдать зачет/сессию/получить диплом (и для этого хороши все способы, в том числе обман)</li>
<li>то, чему нас учат, никогда не понадобиться в реальной жизни</li>
<li>лучший друг студента — это шара</li></ul>
Но хорошая новость в том, что спрос на качественное техническое образование есть и он никуда не денется, и чем больше будут деградировать существующие институции, тем больше места будет открываться для новых начинаний и форм. Я думаю, что в течение ближайших 5 лет у нас появится минимум 1 технический "университет" нового образца, в котором можно будет получить образование на мировом уровне (как я написал, для этого, в принципе, есть всё, кроме некоторой доли лидерства). Я также надеюсь, что сам смогу чем-то помочь в этом. Но, чтобы начать что-то новое, нужно сначала завершить старое. Жаль только, буду скучать за парком КПИ, тополями и каштанами...<br />
<br/>
<img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOluL-dvBvJ3T8z8uUU6hx9a9PzVzbZxpjRI1is4QHpSNEGDPpo7aHpf7VCWdXG0z-Lz6iVoIfXFcMviI8b6NtD6r73iLeskHrA5SnX_e08snl6ogn8aPNxXmzJPSyGUkXQg0HKMrncOc/s400/12291176_10153812288673552_5476963373799342068_o.jpg" /></div>
Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com0tag:blogger.com,1999:blog-6031647961506005424.post-85910702369089177262015-04-07T13:04:00.000+03:002015-04-07T16:20:43.609+03:00Креш-курс по Лиспу - кому он будет интересен<div dir="ltr" style="text-align: left;" trbidi="on">
<img src="http://www.lisperati.com/lisplogo_warning_128.png" style="float: right; margin: 30px;" /><br />
В июле должен состояться мой мастер-класс <a href="http://smartme.com.ua/course/kresh-kurs-po-lisp/">введение в практическую разработку на Common Lisp</a>. По этому случаю меня попросили написать статью в блог компании SmartMe, которая проводит это мероприятие. В ней я попытался ответить на вопрос, кому и зачем сейчас может быть интересно разобраться с Лиспом.<br />
<br />
Лисп — один из самых старых и, пожалуй, самый загадочный из современных языков программирования. Также бытует мнение, что он не просто стар, а устарел. Почему это не так и где его ниша, я попробую ответить в этой статье.<br />
<br />
Лисп — пожалуй единственный динамический <b>системный</b> язык программирования. И среди динамических языков он остается непревзойденным выбором благодаря следующими свойствам:<br />
<ul>
<li>реальной мультипарадигменности, дающей возможность элегантно совмещать процедурный, объектно-ориентированный, функциональный и другие стили</li>
<li>уникальной поддержке метапрограммирования</li>
<li>легендарной интерактивной среде разработки</li>
<li>железобетонному стандарту языка, за которым стоит многолетняя работа мегаумов
МИТа, Xerox PARC, CMU и других подобных мест, оплаченная DARPA</li>
<li>обширному набору реализаций (компилятор и среда исполнения), коих разработано за
его историю около 25, до 10 из которых активно поддерживаются и развиваются</li>
</ul>
На самом деле, современное положение Лиспа как языка, который обычно не рассматривают для серьезной разработки, обуcловленно отнюдь не техническими причинами, а лишь исторической случайностью — язык сильно опередил свое время,— и человеческим фактором: он и не выбор по-умолчанию (как С++, Java или JavaScript), и не модная новая технология (как Scala, Go или Ruby), и не имеет за собой какую-либо серьезную организацию или сообщество, которые бы продвигали его использование (как C#, Swift или Rust). Тем не менее, миф о непрактичности Лиспа опровергает как мой опыт использования его в ядре Grammarly и предыдущих моих коммерческих проектах (уже более 7 лет), так и опыт поисковика авиабилетов ITA Software, купленной Гуглом за миллиард долларов, или же португальской Siscog, разработчика решений для железных дорог, в которой работает более полусотни Лисп-программистов. А адепты теории о необходимости его модернизации могут почитать <a href="http://www.sbcl.org/news.html">Changelog SBCL</a> (лидирующей open source реализации) :)<br />
<br />
Конечно, у Лиспа есть и недостатки — помимо небольшого сообщества, представленного в основном энтузиастами, это:
<br />
<ul>
<li>непривичный синтаксис</li>
<li>часто непривичные подходы и способы разработки</li>
<li><s>отсутствие библиотек для взаимодействия с остальной средой</s> (проект <a href="http://www.quicklisp.org/">Quicklisp</a> давно доказал обратное :)</li>
</ul>
Таким образом, еще раз можно повторить, что язык и экосистема Common Lisp не имеет серьезных <b>технических</b> недостатков при ряде бесспорных преимуществ, но он слишком непривычен и нетипичен, поэтому страдает от проблемы курицы и яйца: отсутствие Лисп-программистов не позволяет начинать на нем серьезные проекты, а отсутсвие импульса в сообществе не приводит в него новых программистов. Поэтому, Лисп вряд ли будет в ближайшее время серьезно использоваться в индустрии разработки. В чем же тогда его ниша сегодня? Если оставить за скобками тренды, то я бы сказал, что в первую очередь, это системы, которые пишутся на годы и должны постоянно эволюционировать: в этом плане он находится в точке золотой середины между классическими системными языками, типа C++ и Java, и их динамическими конкурентами, предоставляя невероятно гибкую и, в то же время, достаточну производительную (как в отношении скорости исполнения, так и скорости разработки) среду. Особенно, если такие системы должны иметь средства представления и обработки большого количества знаний. Как раз про них 10-е правило Гринспена:
<blockquote>Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.</blockquote>
<div style="margin-bottom:10px;">Однако, очень мало кто решится писать сейчас такие проекты на Лиспе. Более актуально другое его применение — быстрое прототипирование и среда для экспериментов. В этой нише у Лиспа много конкурентов, таких как Python или же специализированные языки, типа R и MatLab, но преимущество Лиспа в том, что удачный протип можно со временем довести до продакшн системы. Однако, самое важное значение Лиспа для меня — это полная свобода творчества, которую он предоставляет. Кроме шуток, доступ к такой среде дает возможность программисту развиваться не просто набором опыта использования каких-либо инструментов и фреймворков, а через решение нестандартных задач пытаясь найти для этого наиболее удачный способ независимо от случайных ограничений, налагаемых текущими обстоятельствами и принятыми нормами.</div>
</div>Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com0tag:blogger.com,1999:blog-6031647961506005424.post-8620338033347895562013-09-04T13:40:00.003+03:002013-09-04T13:40:41.924+03:00Ищем Лиспера с горящими глазами<div dir="ltr" style="text-align: left;" trbidi="on">
<div style="text-align: left;">
Уже 3 года я работаю в Grammarly, где мы строим (и довольно успешно) самый точный сервис исправления и улучшения английских текстов. В экстремуме эта задача упирается в полноценное понимание языка, но пока мы не достигли этого уровня, приходится искать обходные пути. :) Понятно, что работы у нашей команды, которую мы называем "core team", выше крыши. Но эта работа довольно сильно отличается от обычной программной инженерии, точнее, инженерия — это ее конечный этап, которому предшествует огромное количество экспериментов и исследования.</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
Я как-то назвал нашу систему Grammar OS, поскольку она чем-то похожа на ОС: в ней есть низкий уровень языковых сервисов, есть промежуточные слои проверщиков, есть и пользовательские приложения. Одним из ключевых компонентов является написанная на Лиспе и постоянно развивающаяся система проверка грамматики. Этот проект помимо собственно сложной задачи, на которую отлично легли возможности Лиспа, high-load'а, также интересен и тем, что над ним работают около 5 компьютерных лингвистов, которые все время дописывают в него какой-то код...</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
К чему я это веду, это к тому, что мы уже больше полугода ищем Лисп-разработчика, желающего подключиться к развитию этой системы, а в перспеткиве и к работе над новыми Лисп-проектами, о которых мы думаем. Помимо Лиспа в ядре у нас используется много Джавы (даже больше), и мы, в общем-то, ее тоже любим. И немного Питона. А основной фокус сейчас все больше и больше сдвигается в сторону различных алгоритмов машинного обучения.</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
Наконец, у нас в Grammarly отличная атмосфера и куча людей, у которых есть чему поучиться.</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
<iframe allowfullscreen="" frameborder="0" height="281" mozallowfullscreen="" src="//player.vimeo.com/video/55076086" webkitallowfullscreen="" width="500"></iframe></div>
<div style="text-align: left;">
<a href="http://vimeo.com/55076086">Grammarly. Хэппи-офис</a> from <a href="http://vimeo.com/simonova">Alena Simonova</a> on <a href="https://vimeo.com/">Vimeo</a>.</div>
<div style="text-align: left;">
<br /></div>
<div style="text-align: left;">
В общем, как по мне, практически работа мечты для программиста, которому нравится обработка текстов, R&D или Лисп.
Логичный вопрос: почему же мы так долго не можем найти подходящего человека? Ответов несколько:
</div>
<ul style="text-align: left;">
<li>Во-первых, мы всех нанимаем очень долго (почти полгода искали даже Java-разработчика). Почему — смотри выше — качество команды для нас важнее скорости.</li>
<li>Во-вторых, лисперов с индустриальным опытом у нас не так и много: в Киеве их от силы пару десятков, в России — больше, но мало кто из них хочет переезжать.</li>
<li>В-третьих, даже у технически сильных кандидатов часто не горят глаза :(</li>
</ul>
<div style="text-align: left;">
Короче говоря, ситуация такова:
</div>
<ul style="text-align: left;">
<li>Есть сложная и интересная работа, с хорошей зарплатой, отличной командой, компанией и перспективами.</li>
<li>Нужен увлеченный своей работой программист, который хорошо понимает алгоритмы, математику и статистику, и хочет писать на Лиспе.</li>
<li>Опыт на Лиспе нужен, но не обязательно из реального мира: open-source или хобби-проекты тоже покатят. Но тогда важно желание и способность быстро развиваться в этом направлении.</li>
</ul>
Если что, пишите на vseloved@gmail.com...</div>
Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com0tag:blogger.com,1999:blog-6031647961506005424.post-22331052551451894092012-12-14T10:34:00.000+02:002012-12-14T10:45:32.966+02:00Ansi Common Lisp на русском<img src="http://files.books.ru/pic/3127001-3128000/3127808/259054451ct.jpg" style="float: right; margin: 20px;">
<p>
Недавно вышел русский перевод книги Пола Грема "<a href="http://www.books.ru/books/ansi-common-lisp-3127808/">Ansi Common Lisp</a>", к которому я немного приложил руку в качестве "научного" редактора. На форуме <a href="http://lisper.ru/forum/common-lisp">lisper.ru</a> уже были сообщения от счастливых обладателей бумажной версии книги, а на сайте издательства даже доступен ее электронный вариант по свободной цене.
<p>
Хотя изначально я скептически отнесся к выбору именно этой книги для перевода, сейчас я рад, что так вышло. Работая над переводом, хочешь-не хочешь, а пришлось прочитать книгу практически от корки до корки, и могу сказать, что это, пожалуй, самое краткое, простое и доступное введение в язык. <a href="http://gigamonkeys.com/book/">Practical Common Lisp</a> лучше открывает глаза, и все-таки остается самой лучшей книгой по Lisp'у в целом, но он существен больше. В общем, ANSI CL — очень хороший вариант для начинающих. И хотя стиль Пола Грема часто критикуют в современном Lisp-сообществе, эта книга достаточно сбаллансированна и не содержит каких-то апокрифических мыслей :)
<p>
Книга состоит из двух частей, меньшая из которых — справочник — фактически бесполезна из-за наличия <a href="http://www.lispworks.com/documentation/HyperSpec/Front/">Hyperspec</a>'и. Но это хорошо, поскольку остается меньше текста для прочтения :) Первая же часть состоит из 13 глав, описывающих разные аспекты языка, и 3 глав с решением практических задач. Главы про язык содержат множество примеров использования различных структур данных и реализации с их помощью нетривиальных алгоритмов, что может позволить неплохо прокачать это направления тем, кто не занимается постоянным решением алгоритмических задачек на Codeforces. Особенно, учитывая красоту и ясность реализации этих алгоритмов на Lisp'е. Несколько глав были весьма полезны и мне с моим пятилетним практическим опытом использования языка: например, я смог по достоинству оценить элегентность <code>structs</code> и стал намного больше пользоваться ими, интересными также были главы про оптимизацию и структурирование программ. В последних 3 главах разобраны классические для Lisp'а задачи: логический вывод, создание своей объектной системы (фактически, реализация внутренностей JavaScript'а) и генерация HTML из мета-языка — это те вещи, на которых видны некоторые из самых сильных сторон языка.
<p>
Из-за проблем издательства работа над переводом велась очень долго — что-то около двух лет. Точнее, сама работа длилась намного меньше, но ее отдельные части были разделены большими временными промежутками. Переводил <a href="https://github.com/allchemist">allchemist</a>, и сделал это задорно и весело. Своей задачей я видел прежде всего исправление отступлений от оригинала и работу с терминологией. Что касается второго пункта то тут я хотел напоследок рассказать занимательную историю про стог и пул.
<h2>Стог и пул</h2>
<p>
Пару лет назад <a href="http://softwaremaniacs.org/blog/2011/04/02/coder-at-work-in-russian/">Иван Сагалаев</a>, который выступал в той же роли научного редактора для книги "<a href="http://lisp-univ-etc.blogspot.com/2010/03/whats-on-coders-minds.html">Coders at Work</a>", написал следующее по поводу роли научного редактора:
<blockquote>
Кто не знает, научный редактор — это человек, совершенно необходимый для специальной литературы. Он берёт сырой перевод и приводит специфичную терминологию в соответствии с принятой в реальном мире. В результате вы читаете книжку, в которой написано не "процесс синтаксического разбора", а просто "парсинг", и не "интерфейс прикладной программы", а "API".
</blockquote>
<p>
Применительно к Кодерам, которые должны читаться как приключенческий роман, я согласен с подходом Ивана. Но вот что касается таких книг, как ANSI CL, предназначеных прежде всего для (относительных) новичков, я считаю, что выбор должен делаться в сторону максимальной понятности терминов, а не привычности их для людей, которые уже в теме. Т.е., конечно, не "процесс синтаксического разбора", а просто "синтаксический разбор" и местами "разбор" — но не "парсинг". Почему? Да хоть потому, что "парсинг" для новичка создает некий магический ореол вокруг этого термина и выделяет его из ряда других, названных на родном языке, хотя ничего выделяющегося в нем нет. Да, часто подобрать адекватный термин на родном языке очень трудно, порой их даже приходится изобретать, но именно так и происходит развитие терминологии.
<p>
По этому поводу в этой книге было 2 очень интересных примера, за первый из которых меня можно смело закидывать помидорами, но я все же буду продолжать настаивать на нем. Давайте перечислим абстрактные структуры данных, с которыми мы чаще всего встречаемя — это, конечно же, <b>лист</b>, <b>три</b>, <b>кью</b>, <b>стек</b>, <b>хип</b>, <b>дек</b>. Ой... Т.е., я хотел сказать: <b>список</b>, <b>дерево</b>, <b>очередь</b>, <b>куча</b>, <b>колода</b> и... <b>стек</b>. Как-то так вышло, что у всех этих структур имена как имена, а вот стек какой-то особенный. Почему? Наверно, из-за лени, но не важно. Если заглянуть в словарь, то для английского слова "stack" можно найти 2 вполне подходящих перевода. Первый из них — <b>стог</b> :) По-моему, удивительный случай созвучности, и, по-своему, очень забавный вариант. Именно его я предложил использовать в качестве термина, когда речь идет об этой структуре данных, и он продержался практически до последней ревизии, однако, в последний момент все-таки был заменен на менее одиозный вариант <b>стопки</b>. Это тоже хороший перевод и с точки зрения соответствия реальности даже более адекватный, так что я остался доволен. Удивительно, почему он так редко встречается в литературе!
<p>
Но тут есть еще одна трудность: а как быть со стеком вызовов функций программы, который уже не абстрактная структура данных, а конкретное технологическое решение, вокруг которого есть еще и другие термины, типа "<b>stacktrace</b>"? Вот тут, конечно, намного труднее, и я остановился на том, что в данном случае, чтобы не создавать путаницы, лучше использовать устоявшийся термин, т.е. стек. Возможно, с прочным вхождением в обиход стопки, можно будет перенести этот термин и сюда: стопка вызовов — звучит банально. Зато никакой дополнительной случайной сложности :)
<p>
Вторым термином, которым я остался недоволен, был <b>пул</b>. Тут случай хуже, т.к. адекватного перевода его на русский и вовсе нет. Ну не бассейн же. Я так ничего и не придумал. Но, если у вас будут мысли на эту тему, делитесь...Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com1tag:blogger.com,1999:blog-6031647961506005424.post-57997673863367191972012-12-12T07:21:00.000+02:002012-12-12T07:21:42.260+02:00Утилитарный LispВот как выглядит "клиент" (если для такого простого кусочка кода уместно столь громкое название) для набирающего популярность лог-сервера Graylog2 на современном Lisp'е:
<script src="https://gist.github.com/4262486.js"></script>
По-моему, этот кусочек кода неплохо развеивает миф о проблемах с библиотеками в Lisp-среде: в нашем пайплайне сначала сообщение сериализуется в JSON библиотекой <code>cl-json</code>, затем кодируется в байтовый поток <code>babel</code>, затем зипуется <code>salza2</code>, а затем отправляется через UDP-шный сокет <code>usocket</code>. А еще есть повод использовать прекрасную библиотеку для работу со временем <code>local-time</code>, основанную на статье Эрика Наггума. Ну и чуть-чуть синтаксического сахара из <code>rutils</code>, в том числе и буквальный синтаксис для хеш-таблиц (как в Clojure), модульно подключаемый с помощью <code>named-readtables</code>. Ничего лишнего.Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com0tag:blogger.com,1999:blog-6031647961506005424.post-78490048726071875232012-08-20T11:25:00.004+03:002012-08-23T16:33:31.281+03:00Как сочетать функциональные языки и мейнстримГде-то с месяц назад меня позвали на киевскую тусовку функциональных программистов рассказать про практический опыт использования Clojure. В итоге я немного покритиковал Clojure, но в основном хотел сказать несколько о другом: о том, где ниша функциональных и других немейнстримных языков (например, Lisp'а), и как их можно использовать в сочетании с мейнстримными. Получилось, как всегда довольно сбивчиво, поэтому попробую изложить здесь яснее и структурированнее.<br /><br />Вот видео:<br /><iframe width="420" height="315" src="http://www.youtube.com/embed/VjZaiWEzadY" frame border="0" allowfullscreen></iframe><br /><br />А хотел сказать я всего-то 3 простые вещи, в которых нет ничего особенно нового, но от этого они, как по мне, не теряют своей ценности.<br /><br /><h2>1. Философия Unix для веба</h2><br />Если говорить о серверной разработке, то самым эффективным подходом к построению масштабируемых систем (как в смысле нагрузки, так и трудоемкости их развития) был и остается Unix way:<br /><blockquote>small pieces, loosely joined, that do one thing, but do it well</blockquote><br />Только в отличие от классики Unix, теперь эти кусочки живут в рамках отдельных узлов сети и взаимодействуют не через pipe, а через <b>сетевые</b> текстовые интерфейсы. Для того, чтобы организовать такое взаимодействие существует ряд простых, надежных, хорошо масштабируемых и, что очень важно, де-факто стандартных и языконезависимых средств. Под разные задачи эти средства разные, и они включают:<ul><li>низкоуровневые механизмы взаимодействия: сокеты и ZeroMQ</li><br /><li>высокоуровневые протоколы взаимодействия: HTTP, SMTP, etc.</li><br /><li>форматы сериализации: JSON и еще десяток других</li><br /><li>точки обмена данными: Redis, разные MQs</li></ul>Это не REST и не SOA в чистом виде, скорее перечисленные схемы являются несколько специализированными, а иногда и ушедшими сильно в сторону ппримерами воплощения этого подхода. Это не более и не менее, чем инструментарий, поверх которого можно построить любую сетевую архитектуру — от централизованной до P2P.<br /><br /><h2>2. Требования к языкам</h2><br />Когда программисты сравнивают между собой разные языки, они, как правило, подходят к задаче не с той стороны: от возможностей, а не от требований. Хотя работа в индустрии должна бы была их научить обратному. :) Если же посмотреть на требования, то их можно разделить на несколько групп: требования к языкам для решения <b>неизвестных</b> (исследовательских) задач существенно отличаются от требований к языкам для реализации задач давно отработанных и понятных. Это классическая дихотомия R&D — research vs development — исследования vs инженерия. Большинство задач, с которыми мы сталкиваемся — инженерные, но как раз самые интересные задачи, как с точки зрения профессиональной, так и экономической — исследовательские. Также отдельно я выделяю группу требований для скриптовых языков.<br /><h4>Требования к языкам для исследований</h4><ul><li>Интерактивность (минимальное время цикла итерации)</li><br /><li>Поддатливость и гибкость (решать задачу, а не бороться с системой)</li><br /><li>Хорошая поддержка предметной области исследований (если это математика — то хотя бы Numeric Tower, если статистика — то хорошая поддержка матриц, если деревья — то инструменты работы с деревьями и первоклассная рекурсия, и т.д.)</li><br /><li>Возможность решать задачу на языке предметной области (заметьте, что специфические исследовательские языми — всегда DSL'и)</li></ul><br /><h4>Требования к языкам для решения стандартных задач</h4><ul><li>Поддерживаемость (возможность легко передать код от одного разработчика другому)</li><br /><li>Развитая экосистемы инструментов и хорошая поддержка платформы</li><br /><li>Стабильность и предсказуемость (как правило, мало кто любит истекать кровью на bleeding edge, поэтому выбирают то, что работает просто, но без особых проблем, проверенное)</li><br /><li>И, порой самое важное — возможность получить быстроработающий результат (подчас скорость работы отличает решение, которое пойдет в продакшн, от того, которое не пойдет)</li></ul><br /><h4>Требования к скриптовым языкам</h4><ul><li>Первое и основное — хорошая интеграция с хост-системой (оптимизация языка под наиболее часто выполняемые операции в хост-системе)</li><br /><li>Простота и гибкость (я еще не слышал ни об одном статически типизированном скриптовом языке :)</li><br /><li>Минимальный footprint (с одной стороны вся тяжелая работа может делаться на стороне хост-системы, с другой стороны — обычно ресурсы ограниченны и очень мало смысла тратить их на ненужное)</li></ul><br />Очень много можно рассуждать о том, какие требования стояли во главе угла при создании тех или иных языков, как языки эволюционируют и т.д. Ограничусь лишь тем, что выделю группу языков, которые однозначно создавались чисто для исследовательской работы — это Matlab, Octave, Mathematica, R, Prolog и разные вариации на тему. Слабость таких языков обычно в том, что в них не были заложены общеинженерные механизмы (прежде всего, первоклассная поддержка взаимодействия с другими системами, которые живут за пределами их "внутреннего" мира).<br /><br />Один из классических подходов к решению исследовательских задач — двухэтапный метод: разработать прототип системы на исследовательском языке, а затем реализовать полноценную систему уже на инженерном языке, оптимизировав при этом скорость и другие показатели решения, но не меняя его сути. Самым существенным недостатком у него, как по мне, является то, что это может неплохо работать в случае одноразового решения, но если система должна эволюционировать во времени, то все становится существенно сложнее. Ну и, зачем делать дурную работу: хорошо, если язык может поддерживать как исследовательскую, так и инженерную парадигму.<br /><br />Если предаставить эти требования в виде декартовых координат, и расположить на них языки, и обевсти самую интересная область, в нее попадет совсем немного языков — раз-два и обчелся. Прежде всего, это Lisp, также Python, и, может быть, еще пара-тройка. <br /><br /><img src="http://img.photobucket.com/albums/v473/pufpuf/rd.jpg"><br /><br /><h2>3. Выбор языка под задачу, а не под платформу</h2><br />Возвращаясь к нашему Unix'у в облаках, отдельные компоненты этой системы могут быть написаны на любом языке (также, как и на обычном Unix'е) и даже работать на любой платформе. Это дает возможность решать специфические задачи тем инструментом, который подходит лучше всего. И среди всего спектра задач преобладают в основном такие, которые, по большому счету все равно, на каком языке делать. Для этих задач обычно самыми главными критериями выбора являются экосистема инструментов и в половине случаев скорость работы результата (во всяком случае в долгосрочном плане).<br /><br />Но не все задачи такие: есть много разных областей, в которых мейнстримные языки работают плохо или же фактически не работают вообще. Известный афоризм на этот счет — <a href="http://en.wikipedia.org/wiki/Greenspun's_tenth_rule">10е правило Гринспена</a>. Соответственно, если вы хотите использовать Lisp, Haskell или Factor — <b>решайте на них те задачи, на которых они дают очевидное преимущество</b>, и делайте их полноценными гражданами в облачной экосистеме. Так вы на практике, а не в теории сможете доказать их полезность скептикам, и в то же время будут развиваться как знания остальных программистов о них, так и инструментарий этих языков (по которому они часто проигрывают мейнстримным аналогам). Таким образом, в будущем они получат возможность рассматриваться как кандидаты для решения и других задач, для которых их преимущества не столько очевидны (в 2-3 раза, а не на порядок).<br /><br /><h2>P.S. Пару слов о Clojure</h2><br />В выступлении я много сравнивал Erlang и Clojure. Оба эти языка делают упор на конкурентную парадигму разработки. Но Erlang в этом смысле стоит особняком от других функциональных языков, поскольку конкурирует с императивными языками не столько на уровне качества языка для решения конкретных задач, сколько как язык-платформа, создающий новую парадигму решения задач за рамками отдельного процесса и отдельной машины. Таким образом, его главная ценность — это роль системного языка для распределенных систем.<br /><br />Что касается Clojure, то я в шутку разделил все языки на фундаментальные и хипстерские. Фундаментальные языки (такие как C, Lisp, Erlang, Haskell, Smalltalk) появляются, когда группы умных людей долго работают над сложными проблемами и в процессе создают не просто язык, а целую парадигму. В то же время хипстерские языки являются продуктом обычно одного человека, который хочет здесь и сейчас получить самое лучшее из нескольких языков сразу, гибрид. Я перечислял такие примеры, как C++ (C + классы + еще куча всего, понадерганного с разных сторон) — это, пожалуй, архетипный хипстерский язык,— Ruby (Perl + Smalltalk + Lisp), JavaScript (C + Scheme), который со времени пояления V8 и node.js перешел из категории скриптовых языков в общесистемные. Кстати, в большинстве случаев языки второго типа более успешны в краткосрочном плане и завоевывают мир. Точнее, их мировое господство чередуется с господством языков, которые стоят в этом спектре где-то посередине: когда люди устают от подобных гибридов, их в конце концов "побеждают" более здравые варианты. Java вместо C++, Python вместо Ruby (для веба), посмотрим, что будет вместо JS. К сожалению, Clojure — яркий пример такого гибрида: это попытка скрестить Lisp с Haskell'ем, да еще и на Java-основе...Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com0tag:blogger.com,1999:blog-6031647961506005424.post-37338872505197379512011-12-21T09:47:00.004+02:002011-12-21T11:20:53.066+02:00Реальная норма часов работы программиста в деньВ 2009-10 годах у меня был некоторый опыт удаленной работы программистом на основе почасовой оплаты. С тех пор я периодически задумывался над тем, сколько же <b>реальных часов работы над кодом в день</b> должен в среднем выдавать программист. На самом деле, этот вопрос, а точнее его последствия, актуален для любого формата работы: как офисной, так и удаленной, так и всего, что между. В "офисном" варианте оплачивается фиксированное время работы и задача в оптимизации реального времени в рамках этого интервала (гибкий график, уменьшение количества встреч и т.п.) Проблемой фриланс-варианта является то, что большинство заказчиков не понимают, что они должны платить не только за время реальной работы над кодом, но и за сопутствующие активности, типа ковыряния в носу, и/или не знают, какое должно быть соотношение этих активностей к собственно программированию.<br /><br />И вот, на днях на хакерньюз я наткнулся на отличную статью профессиональной писательницы <a href="http://thisblogisaploy.blogspot.com/2011/06/how-i-went-from-writing-2000-words-day.html">о повышении эффективности ее труда в 5 раз</a> (с 2 до 10 тысяч слов в день). Фактически, ей удалось это сделать за счет выработки метода систематического вхождения в поток. А как все мы творческие работники знаем, поток — это залог эффективной работы*. Но, что в этой статье для меня было действительно важным — это то, что человек, добившийся такой эффективности, констатировал факт: собственно непрерывной работы у нее в среднем 4 часа в день — в самых удачных случаях до 7 — плюс 2 часа с утра, которые расходуются на подготовительную работу. А если вырабатывать больше, то эффективность начинает падать. Не говоря о том, что можно перегореть. <br /><br />Это наблюдение совпадает и с моим опытом: у меня такое же количество (4-6) часов эффективного писания кода в день. В этом нет ничего удивительного, поскольку труд программиста ничем не выделяется из ряда других творческих профессий: писателя, дизайнера или же композитора. Что же тогда делать с 8-ми часовым рабочим днем? На самом деле, ничего плохого в том, что только 50-75% рабочего времени тратится собственно на работу нет: остальное время тоже не пропадает зря, посколько тратится на комуникацию (без которой любая компания и проект обречены), а также какие-то нерабочие активности, которые формируют ту самую мифическую "корпоративную культуру". И даже если этих активностей нет (фриланс) — это не значит, что: а). программист может это время потратить на работу над кодом б). программисту они не нужны (нужны, возможно, только в другой форме: социализация нужна всем).<br /><br />Т.е. выходит 2 числа, характеризующие нормального программиста: максимум 6 часов (а в среднем: 4 часа) программирования в день и коэфициент 1.5 соотношения часов программирования к часам работы. И эти числа нужно принять как исходные данные, на основании которых менеджеры могут стоить какие-то предположения, оценки и методики.<br /><br />Кроме того, такой взгляд разрешает для меня <b>диллему эстимейтов</b>: должен и может ли, вообще, их делать программист?<br /><br />Функция time профилировщиков программ выдает обычно 2 числа: total time и real/cpu time. Первое значение — это фактическое время, которое прошло от старта программы до завершения. На него могут влиять такие факторы, как кеширование, ожидание ввода-вывода и т.п. А сpu time — это время, которое программа действительно выполнялась на процессоре. По-хорошему, именно это, второе время, может научиться оценивать программист: т.е. время, которое ему понадобиться для написания и отладки кода, когда он будет находится в "разогретом" режиме работы. А вот общее время — которое, на самом деле, интересует бизнес — может научиться оценивать только менеджер, беря во внимание оценки программистов (и их историческое качество), а также 100500 других факторов, которые могут повлиять на режим его работы: кеширование, своевременное заполнение пайплайнов, переключене контекста и т.д.<br /><br />* Те, кто не знает, может очень быстро узнать из уст человека, который сформулировал эту концепцию (или из его классической книги Flow):<br /><object width="526" height="374"><param name="movie" value="http://video.ted.com/assets/player/swf/EmbedPlayer.swf"></param><param name="allowFullScreen" value="true" /><param name="allowScriptAccess" value="always"/><param name="wmode" value="transparent"></param><param name="bgColor" value="#ffffff"></param><param name="flashvars" value="vu=http://video.ted.com/talk/stream/2004/Blank/MihalyCsikszentmihalyi_2004-320k.mp4&su=http://images.ted.com/images/ted/tedindex/embed-posters/MihalyCsikszentmihalyi-2004.embed_thumbnail.jpg&vw=512&vh=288&ap=0&ti=366&lang=&introDuration=15330&adDuration=4000&postAdDuration=830&adKeys=talk=mihaly_csikszentmihalyi_on_flow;year=2004;theme=unconventional_explanations;theme=the_creative_spark;theme=how_the_mind_works;event=TED2004;tag=Culture;tag=Global+Issues;tag=happiness;tag=music;tag=psychology;tag=work;&preAdTag=tconf.ted/embed;tile=1;sz=512x288;" /><embed src="http://video.ted.com/assets/player/swf/EmbedPlayer.swf" pluginspace="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" wmode="transparent" bgColor="#ffffff" width="526" height="374" allowFullScreen="true" allowScriptAccess="always" flashvars="vu=http://video.ted.com/talk/stream/2004/Blank/MihalyCsikszentmihalyi_2004-320k.mp4&su=http://images.ted.com/images/ted/tedindex/embed-posters/MihalyCsikszentmihalyi-2004.embed_thumbnail.jpg&vw=512&vh=288&ap=0&ti=366&lang=&introDuration=15330&adDuration=4000&postAdDuration=830&adKeys=talk=mihaly_csikszentmihalyi_on_flow;year=2004;theme=unconventional_explanations;theme=the_creative_spark;theme=how_the_mind_works;event=TED2004;tag=Culture;tag=Global+Issues;tag=happiness;tag=music;tag=psychology;tag=work;&preAdTag=tconf.ted/embed;tile=1;sz=512x288;"></embed><br /></object>Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com2tag:blogger.com,1999:blog-6031647961506005424.post-51096368863001827972010-10-17T15:16:00.002+03:002010-10-17T15:33:35.921+03:00Роль CTO в технологической компанииЕсть известная максима, давно проповедуемая Полом Гремом и взятая на вооружение большей частью американского стартап-сообщества, о том, что технологические стартапы должны иметь минимум двух основателей. Можно много дискутировать на тему, с чем это связанно: например, есть версия, что "если предприниматель не способен убедить в своей идее хотя бы еще одного человека, то он тем более не сможет убедить в этом рынок". Я же пришел к выводу, что всё упирается в ответ на вопрос, что можно аутсорсить, а что нет? Как известно, нельзя этого делать с ключевыми функциями компании. И первой ключевой функцией является формирование ее идентичности на рынке. Этим занимается человек, называемый американцами CEO, хотя более правильно было бы назвать его просто лидером проекта. По своей базе он может быть как инженером, так и маркетологом, финансистом или продажником. Это не столь важно, поскольку его главная роль, которая заключается в том, чтобы вести компанию в правильном направлении и рассказывать об этом рынку через маркетинг, биздев, продажи, PR и т.д. и т.п., не требует особой специализации. Очевидно, что при этом он должен стоять у истоков идеи, которая реализуется, и сам крепко верить в нее, иначе он не сможет убедить в ней других. И эта роль настолько обширна, что она должна быть единственной сферой ответственности такого человека. Да, он может быть крутым программистом и вносить свой вклад, а, воможно, и наибольшее ноу-хау в технологии своей компании, но он не может быть ответственным за эту сферу в целом. Потому что, если отвечать за два дела одновременно, то ни одно из них не выйдет сделать хорошо. И это показывает опыт, в том числе и мой собственный.<br /><br />Вот здесь и выходит на арену CTO, роль которого в компании заключается в технологическом лидерстве. Часто считается, что СТО должен написать основную часть кода, если это касается софтверного стартапа. Это не обязательно. Код могут написать хоть 10 фрилансеров или 100 миллионов обезьян, поскольку это довольно стандартная работа, и если правильно поставить задачу, с ней прекрасно справится наемный сотрудник. Но вот, что сложно — так это поставить задачу и сформулировать рамки, в которых должен развиваться проект, а потом следить за тем, чтобы они соблюдались. В технологическом бизнесе, как ни крути, а основные риски все-таки технологические. И все остальные участники процесса (наемные сотрудники, клиенты и т.д.) будут их только увеличивать. Единственный, кто может и должен с ними бороться — это СТО.<br /><br />СТО — это роль того, кто принимает окончательное решение, выбрать технологию Х, за которую выступает разработчик, или Y, которая сейчас на волне хайпа. Это тот, кто не спит ночами из-за того, что не настроен бекап базы данных. Это тот, кто вводит практику ревью кода и добивается ее реального воплощения. Тот, кто отвечает программистам на вопросы: "зачем?", "почему?" и "когда?" Тот, кто отбирает подходящих инженеров. И принимает еще 100500 разных решений, а потом добивается их исполнения. В общем, это тот, кто создает технологическую культуру компании и не дает появляться технологическуму долгу, который в будущем (и, скорее, близком, чем отдаленном) потянет компанию ко дну или же не даст ей развиваться необходимыми темпами.<br /><br />Так что правило про двух основателей я переформулировал бы так.<br /><blockquote>На этапе становления у технологического бизнеса есть 2 ключевые роли, которые должны выполнять 2 разных основателя компании: бизнес-лидер и технолонический лидер.</blockquote>Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com4tag:blogger.com,1999:blog-6031647961506005424.post-30372376168914744922010-08-13T17:57:00.009+03:002010-08-14T09:47:45.516+03:00ASDF как язык описания системЭто третья статья в серии про ASDF. Первая часть — <a href="http://lisp-univ-etc.blogspot.com/2010/06/asdf-2.html">об ASDF 2</a>, вторая — <a href="http://lisp-univ-etc.blogspot.com/2010/07/asdf.html">об архитектуре ASDF</a>.<br /><br />Как было замечено <a href="http://tream.dreamhosters.com/tream/musings/49-lisp/76-analysis-of-existing-asdf-files">ранее</a>, ASDF выполняет 2 связанные, но все же довольно разные по требованиям со стороны пользователей функции: описание систем и управление их сборкой. В этой статье я постараюсь перечислить распространенные (и не очень) шаблоны его применения для этих задач, собранные мной из более 70 open-source Lisp библиотек, с которыми приходилось работать. Я думаю, что систематизация этих знаний сослужит хорошую службу Lisp-сообществу и будет полезна как начинающим, так и экспертам, которые смогут усовешенствовать свои техники.<br /><br />Изначально в этой статье я планировал разобрать все основные шаблоны использования ASDF, но материала оказалось слишком много, поэтому более сложным темам его применения как build-инструмента будет посвящена следующая серия.<br /><br />Прямые ссылки на полезные вещи:<ul><li><a href="#tutorial">новый проект c помощью ASDF за 2 минуты</a></li><li><a href="#defsystem-package">пакет для defsystem-формы</a></li><li><a href="#proper-symbols">правильные символы в именах систем и пакетов</a></li><li><a href="#system-name-path">имена и пути компонент</a></li><li><a href="#platform-dep">платформо-зависимые описания систем</a></li></ul><br /><h2>Начало работы с ASDF</h2><br />ASDF нельзя назвать инструментом, который можно освоить за 5 минут. Я сам, например, очень долго вникал в идеологию и особенности его работы, и делал это в основном методом проб и ошибок (мануал мне плохо помогал :). Однако, сейчас мне кажется, что эти трудности связанны не с особенностями ASDF (какими-то неудачными архитектурными решениями и т.п.) или Lisp'а, а, в первую очередь, с отсутствием должной документации и best-practices. Именно этот пробел я и хочу заполнить. Да, всё, что будет показано ниже, почерпнуто из широкодоступного кода Lisp-библиотек, однако часто нужны скорее tutorial'ы, которые позволят быстро начать работу и сразу получить ожидаемый результат. Поэтому начнем именно с такого самого простого примера.<br /><br /><a name="tutorial"></a>Итак, чтобы создать новый проект с помощью ASDF — нужно создать директорию проекта (пусть будет <code>testprj</code>), в которой поместить имеющиеся lisp-исходники (пусть это будет файл <code>app.lisp</code>) и создать в этой директории файл <code>testprj.asd</code>, в который поместить следующую форму:<code><pre>(asdf:defsystem #:testprj<br /> :components ((:file "app")))</pre></code>Для загрузки нового проекта в Lisp-среду нужно, чтобы наш <code>testprj.asd</code> файл был в известных ASDF местах. На данный момент поддерживается 2 способа задания таких мест: аналог PATH (<code>*central-registry*</code>) и <code>source-registry</code> (использующий конфигурационные файлы). Самый распространенный подход (в POSIX-окружении) — это создать ссылку на asd-файл в какой-то специальной директории (например, <code>~/.lisp</code>), которая добавлена в <code>*central-registry*</code>.<br /><br />В общем полная настройка такого варианта выглядит так:<code><pre>$ ln -s <path-to-testprj.asd> ~/.lisp/testprj.asd<br /><br />;; обычно это делается в rc-файле lisp'а (например, .sbclrc)<br />CL-USER> (push "~/.lisp/" asdf:*central-registry*)<br /><br />;; далее можно выполнять (для ASDF 2):<br />CL-USER> (asdf:load-system :testprj)<br />;; или для любой версии ASDF:<br />CL-USER> (asdf:oos 'asdf:load-op :testprj)<br />;; или в большинстве окружений (например, SBCL), даже:<br />CL-USER> (require :testprj)</pre></code>Вот и всё.<br /><br /><h3>Другие формы в ASD-файле</h3><br />На самом деле, ASD-файл — это обычный lisp-исходник, просто с другим расширением, что помогает найти его ASDF'у, поэтому в нем могут находится и другие lisp-формы. Впрочем, прежде, чем помещать туда произвольные формы, стоит очень хорошо подумать. Во всяком случае, ASD-файл следует сохранить полностью декларативным, поскольку большинство инcтрументов, созданных вокруг ASDF, должны иметь возможность просто читать этот файл, не исполняя.<br /><h3><a name="defsystem-package"></a>Пакет для <code>defsystem</code></h3><br />Единственный класс форм, размещение которых в ASD-файле необходимо (помимо <code>defsystem</code> и некоторых других ASDF-специфичных форм, о которых будет сказано далее) — это формы работы с пакетом.<br /><br />Сейчас в Lisp-сообществе есть 2 конкурирующих взгляда на то, как это делать:<ul><li>простой — определять системы в пакете ASDF <code>(in-package :asdf)</code></li><li>скурпулезный — определять отдельный пакет только для ASDF-описания системы:<code><pre>;; Пример из описания системы ARCHIVE:<br />(defpackage :archive-system (:use :cl :asdf))<br />(in-package :archive-system)</pre></code></li></ul>Лично я являюсь сторонником первого подхода, поскольку он не добавляет избыточной сложности. Его единственный недостаток — в возможном конфликте символов, однако он решается использованием особых символов (<code>#:testprj</code> или <code>:testprj</code>). Единственным случаем, когда вариант отдельного пакета может оказаться препочтительнее — это какие-то очень сложные описания систем с зависимостями от дополнительных пакетов. Впрочем, это верный признак того, что вы делаете что-то не так и, просто, не пользуетесь всеми возможностями ASDF.<br /><br /><h3><a name="proper-symbols"></a>Использование символов</h3><br />Имена ASDF-систем в форме <code>defsystem</code>, как и CL пакетов в <code>defpackage</code>, можно задавать несколькими способами:<ul><li>символом: <code>(defsystem testprj ...)</code></li><li>кивордом: <code>(defsystem :testprj ...)</code></li><li>неинтернированным символом: <code>(defsystem #:testprj ...)</code></li><li>строкой: <code>(defsystem "TESTPRJ" ...)</code></li></ul>Мне не сразу стал понятен принцип выбора, но он прост: нужно использовать неинтернированные символы (а все остальные встречающиеся варианты связанны с тем, что авторы просто не знали или не понимали этот вариант :).<br /><br />Вариант строки, в принципе, эквивалентен, но не эстетичен, киворда — приводит к "засорению" пакета keywords,— а просто символа — подвержен риску конфликта имен (его точно не стоит использовать, если описывать систему внутри ASDF-пакета).<br /><h2>Задание зависимостей</h2><br />Я бы сказал, что ключевой функцией ASDF является разрешени зависимостей между исходными файлами в рамках одной системы и между разными системами. Все зависимости в ASDF задаются ключевым словом <code>:depends-on</code> в описании соответствующего компонента (файла, модуля, системы и т.д.)<br /><br /><h3>Зависимости от других систем</h3><br />Разумеется, любая серьезная система существует не в вакууме, а использует множество других библиотек. Несмотря на миф об их отсутствии в lisp-экосистеме :), большие проекты, над которыми мне приходилось работать, как правило, использовали порядка 20-30 сторонних библиотек (включая рекурсивные зависимости).<br /><br />Предположим, что наш проект использует библиотеку утилит <code>RUTILS</code>. В таком случае нам нужно немного расширить его описание:<code><pre>(asdf:defsystem #:testprj<br /> :depends-on (#:rutils)<br /> :components ((:file "app")))</pre></code>Опять же для задания имени зависимости мы используем неинтернированный символ. ASDF позволяет дать и более точное описание такой зависимости (которое пока используется крайне редко, и о котором в отдельной статье, посвященной версиям).<br /><br /><h3>Зависимости между файлами</h3><br />Если в проекте больше одного lisp-файла, то стандартной практикой является добавления файла <code>packages.lisp</code>, в котором описываются 1 или несколько пакетов, которые будут использоваться (создавать любой неигрушечный проект в рамках <code>cl-user</code> пакета строго не рекомендуется :).<br /><br />Предположим, что помимо <code>app.lisp</code>, мы также используем файл <code>support.lisp</code>. В таком случае наше описание приобритет следующую форму:<code><pre>(asdf:defsystem #:testprj<br /> :depends-on (#:rutils)<br /> :components ((:file "packages")<br /> (:file "support")<br /> (:file "app")))</pre></code>Однако мы не задалт порядок загрузки отдельных файлов, что может привести к неожиданным результатам (скажем, Lisp будет ругаться на форму <code>(in-package #:testprj)</code> в файле <code>support.lisp</code>, поскольку файл <code>packages.lisp</code> еще не загружен. Поэтому эту форму нужно доработать:<code><pre>(asdf:defsystem #:testprj<br /> :depends-on (#:rutils)<br /> :components ((:file "packages")<br /> (:file "support" :depends-on "packages")<br /> (:file "app" :depends-on "support")))</pre></code>Тут мы не пишем, что <code>app</code> зависит от <code>packages</code>, поскольку зависимости транзитивны.<br /><br />Такой последовательный (serial) вариант зависимостей характерен для доброй половины проектов, поэтому для него есть специальная декларация для <code>defsystem</code>: <code>:serial t</code>. С ней наше описание снова упростится:<code><pre>(asdf:defsystem #:testprj<br /> :depends-on (#:rutils)<br /> :serial t<br /> :components ((:file "packages")<br /> (:file "support")<br /> (:file "app")))</pre></code>В основе ASDF лежит расширяемая объектная модель компонентов и операций над ними. Некоторые разработчики используют в описании систем прямое имя класса <code>:cl-source-file</code>, а не <code>:file</code>. А вот класса <code>file</code> в ASDF как раз нет: конкретный класс определяется из слота <code>default-component-class</code> модуля (система — потомок модуля) и по умолчанию, конечно, является как раз lisp-исходником.<br /><br />Помимо этого описан ряд других вариантов, компонент, а также всегда можно описать свой собственный (об этом — в следующей статье).<br /><br />Например, интересной практикой является подобная декларация компонента:<code><pre>:components ((:static-file "cl-oauth.asd")<br /> ...</pre></code>(<code>static-file</code> — это любой статичный файл, который не обрабатывается компилятором, например файл лицензии или, как в данном случае, собственно файл описания системы — ведь он обрабатывается только ASDF. Зачем его добавлять? Например, если мы расчитываем, что будем реализовывать какую-нибудь операцию, типа <code>publish-op</code>, для создания дистрибутива из исходных файлов).<br /><br /><h3>Модули</h3><br />Модули ASDF — это логические компоненты системы, которые объединяют несколько других компонент. Использование модулей позволяет решить 2 задачи:<ul><li>аггрегированно управлять зависимостями<br/><br />Скажем, у нас есть 2 части системы: бэкенд и фронтенд, которые зависят от общего файла утилит. И при изменении каждой из них мы не хотим перекомпилировать другую. В таком случае логично будет описать каждую часть в виде отдельного модуля</li><li>распределить исходники по разным директориям (в какой-то степени это аналог модулей в Python, но без управления видимостью — об этом в следующей статье)<code><pre>(defsystem :arnesi<br /> ...<br /> :components<br /> ((:module :src<br /> :components ((:file "accumulation"<br /> :depends-on ("packages" "one-liners"))<br /> (:file "asdf" :depends-on ("packages" "io"))<br /> (:file "csv" :depends-on ("packages" "string"))<br /> (:file "compat" :depends-on ("packages"))<br /> (:module :call-cc<br /> :components ((:file "interpreter")<br /> (:file "handlers")<br /> (:file "apply")<br /> (:file "generic-functions")<br /> (:file "common-lisp-cc"))<br /> :serial t<br /> :depends-on ("packages" "walk"<br /> "flow-control" "lambda-list"<br /> "list" "string"<br /> "defclass-struct"))))<br /> ...))</pre></code></li></ul>А можно ли разложить файлы по разным директориям не привязываясь к модели зависимостей, которая накладывается модулями? Да. У любого компонента есть слот <code>:pathname</code>, который позволяет явно задать путь к нему. Однако его использование имеет свои особенности — об этом дальше.<br /><h2><a name="system-name-path"></a>Имена компонент и путь к ним</h2><br />У любого ASDF-компонента есть обязательный аттрибут имя, который используется при его поиске и разрешении зависимотстей. Однако этот аттрибут *не задается* декларацией <code>:name</code> в описании компонента.<code><pre>(defsystem :ch-image<br /> :name "ch-image"<br /> ...)</pre></code>В этом коде <code>:name "ch-image"</code> несет чисто эстетический смысл (<a href="#foot-1">не верите?</a> :)<br /><br />Имя задается первым символом в декларации компонента: в данном случае <code>ch-image</code>, или же в случае модуля выше — <code>:src</code>, или же <code>"accumulation"</code> для файла там же. Все внутренние функции ASDF умеют работать как с символьным, так и со строковым представлением имен, описанных <a href="#proper-symbols">выше</a>.<br /><br />Кроме того, у каждого компонента есть аттрибут <code>pathname</code>, который определяет его положение в файловой системе. Однако, в отличие от имени, его как раз можно задать соответствующей декларацией. Например, этот пример задает относительный собственно ASD-файла путь к модулю <code>io.multiplex</code> библиотеки <code>IOLIB</code>: <code>:pathname (merge-pathnames #p"io.multiplex/" *load-truename*)</code>.<br /><br />Если же путь не задавать явно, то он вычисляется из имени, расширения (которое определяется типом компонента) и положения в иерархии модулей. Таким образом для примера задания модуля в <code>arnesi</code> (выше) для компонента <code>:file "interpreter"</code> будет вычислен такой путь: <code>src/call-cc/interpreter.lisp</code>.<br /><br />Ну и ответ на вопрос, как разбросать файлы по директориям, не используя модули: задать для всех файлов явный <code>:pathname</code>.<br /><h2>Мета-информация</h2><br /><code>Defsystem</code>-форма позволяет задать большое количество полезных метаданных для системы. Очень важно указать как минимум следующие:<ul><li><code>:version</code>, например <code>:version "0.0.1"</code> (подробнее о версиях — в отдельной статье)</li><li><code>:author</code> или <code>:maintainer</code> (с указанием e-mail'а, чтобы к вам впоследствии смогли обратиться и предложить миллионы за доработку и поддержку вашей прекрасной библиотеки :)</li><li><code>:licence</code> — чтобы люди знали, как они могут пользоваться вашими поделками</li></ul><br /><h2><a name="platform-dep"></a>Платформо-зависимое описание систем</h2><br />CL предоставляет исключительно удобный механизм условной компиляции и выполнения кода (<code>#+/#-</code>). И как раз в описаниях систем он, разумеется, находит широкое применение:<br /><ul><li>предотвращение загрузки системы в целом — тут интересны примеры из двух альтернативных библиотек для FFI:<code><pre>#+(or allegro lispworks cmu openmcl digitool cormanlisp sbcl scl)<br />(defsystem uffi ...)<br /><br />;; CFFI: этот вариант, безусловно, правильнее, чем просто тихо ничего не сделать<br />#-(or openmcl sbcl cmu scl clisp lispworks ecl allegro cormanlisp)<br />(error "Sorry, this Lisp is not yet supported. Patches welcome!")</pre></code></li><li>вынесение функций, зависящих от конкретной lisp-среды в отдельные файлы — пример из все того же CFFI:<code><pre>:components (#+openmcl (:file "cffi-openmcl")<br /> #+sbcl (:file "cffi-sbcl")<br /> #+cmu (:file "cffi-cmucl")<br /> #+scl (:file "cffi-scl")<br /> #+clisp (:file "cffi-clisp")<br /> #+lispworks (:file "cffi-lispworks")<br /> #+ecl (:file "cffi-ecl")<br /> #+allegro (:file "cffi-allegro")<br /> #+cormanlisp (:file "cffi-corman")</pre></code></li><li>закладка нескольких вариантов построения библиотеки, в зависимсоти от каких-то условий. Тут проще всего привести примеры:<code><pre>;; использовать ли acl-regexp2-engine?<br />(defsystem :cl-ppcre<br /> :version "2.0.3"<br /> :serial t<br /> :components ((:file "packages")<br /> (:file "specials")<br /> (:file "util")<br /> (:file "errors")<br /> (:file "charset")<br /> (:file "charmap")<br /> (:file "chartest")<br /> #-:use-acl-regexp2-engine<br /> (:file "lexer")<br /> #-:use-acl-regexp2-engine<br /> (:file "parser")<br /> #-:use-acl-regexp2-engine<br /> (:file "regex-class")<br /> #-:use-acl-regexp2-engine<br /> (:file "regex-class-util")<br /> #-:use-acl-regexp2-engine<br /> (:file "convert")<br /> #-:use-acl-regexp2-engine<br /> (:file "optimize")<br /> #-:use-acl-regexp2-engine<br /> (:file "closures")<br /> #-:use-acl-regexp2-engine<br /> (:file "repetition-closures")<br /> #-:use-acl-regexp2-engine<br /> (:file "scanner")<br /> (:file "api")))</pre></code><code><pre>;; какие бэкенды генерации графических файлов доступны?<br />(defsystem :ch-image<br /> ...<br /> (:module<br /> :io<br /> :components<br /> (#+ch-image-has-tiff-ffi<br /> (:cl-source-file "tiffimage")<br /> #+ch-image-has-cl-jpeg<br /> (:cl-source-file "jpegimage")<br /> #+(and ch-image-has-zpng)<br /> (:cl-source-file "pngimage")<br /> (:cl-source-file "imageio"<br /> :depends-on (#+ch-image-has-tiff-ffi<br /> "tiffimage"<br /> #+ch-image-has-cl-jpeg<br /> "jpegimage")))<br /> :depends-on (:src))</pre></code></li></ul><br /><hr><br /><a name="foot-1"></a><code><pre>;; :name не имеет значения<br />CL-USER> (defsystem :ch-image<br /> :name "ch-image1")<br />#<SYSTEM "ch-image" {AA7A911}><br />CL-USER> (describe *)<br />#<SYSTEM "ch-image" {AA7A911}><br /> [standard-object]<br /><br />Slots with :INSTANCE allocation:<br /> NAME = "ch-image"<br /> VERSION = #<unbound slot><br /> IN-ORDER-TO = NIL<br /> DO-FIRST = ((COMPILE-OP (LOAD-OP)))<br /> INLINE-METHODS = NIL<br /> PARENT = NIL<br /> RELATIVE-PATHNAME = #P"/home/vs/lib/lisp/ch-image_0.4.1/"<br /> OPERATION-TIMES = #<HASH-TABLE :TEST EQL :COUNT 0 {AA7AB61}><br /> PROPERTIES = NIL<br /> COMPONENTS = NIL<br /> IF-COMPONENT-DEP-FAILS = :FAIL<br /> DEFAULT-COMPONENT-CLASS = NIL<br /> DESCRIPTION = #<unbound slot><br /> LONG-DESCRIPTION = #<unbound slot><br /> AUTHOR = #<unbound slot><br /> MAINTAINER = #<unbound slot><br /> LICENCE = #<unbound slot></pre></code>Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com1tag:blogger.com,1999:blog-6031647961506005424.post-25705991258548657922010-07-09T12:18:00.005+03:002011-04-07T20:00:43.936+03:00Новости Redis и cl-redisНаконец, дошли руки обновить наш <a href="http://github.com/vseloved/cl-redis">клиент для Redis</a> и доработать его для поддержки новых режимов, которые добавились в последних версиях БД. Заодно и проверить утверждения из <a href="http://fprog.ru/2010/issue5/vsevolod-dyomkin-lisp-philosophy/#htoc2">статьи</a> о том, что клиент тривиально расширяемый и легко адаптируется к изменениям.<br /><br />На самом деле, пришлось немного повозиться. Но не потому, что утверждения ошибочны, а из-за стандартной проблемы подавляющего большинства софтверных проектов — неадекватной документации. Впрочем, в случае с Redis'ом все не однозначно. С одной стороны, в общем, документация хорошая и правильная: во-первых, не перегруженная, во-вторых, описан протокол взаимодействия, и для каждой команды более-менее указано, как она использует протокол (а еще и другие полезные вещи: иногда примеры использования, а также, что мне понравилось, временные характеристики). Но, в том то и дело, что "более-менее" указано, и некоторые важные моменты упущены, так что в этот раз пришлось лезть в код родного клиента <code>redis-cli</code> и даже прослушивать его взаимодействие с сервером <code>tcpdump</code>'ом (жалко, что я не додумался с этого начать :)<br /><br />Короче говоря, у Redis появилась поддержка хеш-таблиц, но обнаружился <a href="http://code.google.com/p/redis/issues/detail?id=275">баг</a> у всех команд, которые передают ключи (в терминах Redis — "поля") в таблице (самый простой пример: <code><nobr>HGET table field</nobr></code>). Почему этого не заметили разработчики? Видимо, потому, что родной клиент использует для общения с сервером не <a href="http://code.google.com/p/redis/wiki/ProtocolSpecification">описанный протокол</a> с разными вариантами передачи команд: <code>inline</code>, <code>bulk</code> и т.п.,— а простой унифицированный способ в форме <a href="http://code.google.com/p/redis/wiki/ProtocolSpecification#Multi_bulk_commands"><code>multi-bulk</code></a>, т.е. вместо строки <code><nobr>"HGET table field"</nobr></code>, которую посылал наш клиент, вот что:<br /><pre><code>"*3<br />$4<br />HGET<br />$5<br />table<br />$5<br />field"</code></pre><br />Соответственно, наверно, и не заметили особенность при обработке полей в случае передачи команды в <code>inline</code>-форме.<br /><br />Справедливости ради, нужно сказать, что если порыться в документации, то можно найти замечание, что <code>"A good client library may implement unknown commands using this command format in order to support new commands out of the box without modifications."</code> (т.е. самый продуктивный путь был — снова перечитать спецификацию протокола :) Теперь вот, задумался, может стоит перевести все команды на эту <code>multi-bulk</code> форму. И API упростится, будет не:<br /><pre><code>(def-cmd HGET (key field)<br />"Retrieve the value of the specified hash FIELD."<br />:generic :bulk)</code></pre>а<br /><pre><code>(def-cmd HGET (key field) :bulk<br />"Retrieve the value of the specified hash FIELD.")</code></pre>С другой стороны, немного уменьшиться производительность.<br /><br />Вообще, Redis все в большем количестве мест использует <code>multi-bulk</code> форму (поскольку она наиболее общая). Поддержка PubSub сделана также на ней, хотя и с небольшим отклонением.<br /><br />PubSub — это сейчас такой горячий пирожок, который все хотят съесть. Как это реализовано здесь? Есть команды <code>SUBSCRIBE</code> и <code>PSUBSCRIBE</code>, позволяющие подписаться на каналы соответственно по имени и по шаблону (типа <code>"news.*"</code>). И есть <code>PUBLISH</code>, которая посылает сообщения. Сообщения доставляются условно мгновенно и приходят в рамках тех <b>активных</b> соединений, в которых произведена подписка. Таким образом, типичный подход к применению этого, насколько я понимаю — это когда у нас есть отдельная нить-слушатель, которая обрабатывает приходящие сообщения и иницирует какую-то реакцию в программе. Где-то так:<br /><pre><code>(with-connection ()<br />(red-subscribe "chan")<br />(loop<br /> (let ((msg (expect :multi)))<br /> (bt:make-thread (lambda ()<br /> (process (elt msg 2))))<br /> ;; или же, например<br /> (bt:with-lock-held (*lock*)<br /> (push (elt msg 2) *internal-queue*)))))</code></pre>Сообщения имеют вид <code>'("message" <канал> <сообщение>)</code> для простой подписки и <code>'(<шаблон> <канал> <сообщение>)</code> для <code>PSUBSCRIBE</code>. Вот, собственно, и всё.<br /><br />Как видно из примера, получение сообщений из канала блокирующее и выполняется просто за счет вызова функции <code>expect</code>, которая во всех обычных командах присутствует в паре <code>tell-expect</code>. Так что, возвращаясь к моим утверждениям про легкую расширяемость и адаптацию — они подтвердились (и один из примеров — вот он).<br /><br />Redis действительно быстро развивается и много чего меняется, добавляются радикально новые варианты использования: в данном случае транзакции и PubSub. Но для поддержки всего этого в клиенте хватает точечных изменений на уровне реализации протокола, никакого рефакторинга. В этот раз нужно было добавить еще несколько вариантов ожидания ответа: <code>:queued</code> (для транзакций), при котором считывается сразу несколько разнородных ответов подряд; <code>:float</code>; а также <code>:pubsub</code>,— поменять несколько определений команд, потому что поменялась сама их спецификация. Ну и добавилась обработка особого случая транзакций, когда любая команда возвращает <code>"QUEUED"</code> вместо своего стандартного ответа.<br /><br />PS. Да, и еще про транзакции: теперь, как видите, Redis и их <a href="http://code.google.com/p/redis/wiki/MultiExecCommand">поддерживает</a>. Что меня заинтересовало — это обсуждение гарантий целостности (на этой же странице внизу), которые, на первый взгляд, недостаточны: нет условия успешного завершения всех команд в рамках транзакции, чтобы транзакция была признана успешной. Т.е. <code>ROLLBACK</code> не предусмотрен. Но вот, что пишет на этот счет Сальваторе Санфилиппо:<br /><blockquote>I think you are missing the point about <code>MULTI/EXEC</code>, 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.</blockquote><br />Так что транзакции в Redis кислотные по-своему, и нужно хорошо уяснить для себя их семантику, прежде чем браться применять. (И опять, возвращаясь к тому, с чего я начинал: встает проблема адекватности документации. А идеальная документация — исполняемая... ;)Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com9tag:blogger.com,1999:blog-6031647961506005424.post-75663680129368014552010-07-09T07:58:00.006+03:002010-07-09T12:45:25.249+03:00Внутреннее устройство ASDFЭто вторая статья в серии про ASDF. Первая рассказывала про нововведения в <a href="http://lisp-univ-etc.blogspot.com/2010/06/asdf-2.html">ASDF 2</a>.<br /><br />Итак, рассказ о внутренностях ASDF начнем с того, что меня самого испугала бы задача создать с нуля подобную систему. В данном случае в идеале нужно единомоментно получить программу, обладающую одновременно такими довольно противоречивыми характеристиками:<br /><ul><li>хорошо покрывающую основные варианты использования (в случае ASDF — это и средство описания систем (для их последующего распространения), и менеджер сборки)</li><li>простую и удобную для непосвященных в детали пользователей</li><li>хорошо расширяемую для того, чтобы позволить развивать сопутствующую инфраструктуру (например, такие средства как ASDF-INSTALL)</li><li>ну и, разумеется, сразу корректно работающую</li></ul>ASDF была впервые написана Деном Барлоу в 2001 году. Как я понимаю, подспорьем при ее создании был фундаментальный труд Кента Питмана, обобщающий опыт в этой сфере, <a href="http://www.nhplace.com/kent/Papers/Large-Systems.html">"Описание больших систем"</a> (1984 года), а также опыт эксплуатации ее предшественника MK:DEFSYSTEM. Т.е., по сути, ASDF была "второй системой", но в данном случае, к счастью, удалось избежать реализации соответствующего синдрома.<br /><br />Что же представляет из себя эта сама по себе довольно большая система изнутри? Хребтом ASDF является иерархия классов <code>component</code> → <code>module</code> → <code>system</code>, которые содержат информацию об именах, местоположении и зависимостях систем и их компонент, метаинформацию, а также служебную информацию самой ASDF.<br /><br />Кроме того описаны классы операций, которые могут выполняться над системами, как то: <code>compile-op</code>, <code>load-op</code>, <code>test-op</code> и т.д. Почему операции являются объектами, а не просто ключами, что напрашивается на первый взгляд? Во-первых, это позволяет наследовать от них и при этом точечно менять поведение связанных обобщенных функций. Но даже более важно то, что объект операции имеет определенные свойства: операцию-родитель, является ли операция форсированной (хотя это свойство на данный момент не работает корректно), таблицы отработанных и еще не отработанных узлов и т.д. Это имеет и свой недостаток, поскольку кажется несколько избыточным для обычного пользователя. В версии ASDF 2 для его устранения введены функции-обертки <code>load-system</code>, <code>compile-system</code> и <code>test-system</code>.<br /><br />На уровне пользовательского API на основе всех этих классов функционирует макро <code>defsystem</code>, а также обобщенная функция <code>operate</code>.<br /><br /><code>Defsystem</code> — это хороший инструмент описания систем, знакомый и понятный, я думаю, каждому. Он ведет свою историю еще от Lisp Machine Lisp <code>DEFSYSTEM</code>, хотя с тех пор и существенно эволюционировал в сторону упрощения интерфейса.<br /><br />Параметры <code>defsystem</code>-формы не передаются, как можно было бы предположить, напрямую в <code>(make-instance 'system ...)</code>, а сперва обрабатываются функцией <code>parse-component-form</code>. При этом часть параметров передается как есть, а часть транслируется или используется в качестве мета-параметров. Остановлюсь на двух из них:<br /><ul><li><code>defsystem</code> можно применять не только для стандартных систем, но и их потомков за счет параметра <code>:class</code></li><li>параметр <code>:depends-on</code> (и <code>:weakly-depends-on</code><a href="http://www.blogger.com/post-create.g?blogID=6031647961506005424#foot1"><sup>1</sup></a>), на самом деле, не присутствует в качестве слота в классе <code>component</code>. Его содержимое транслируется в содержимое слота <code>in-order-to</code>, которое описывает зависимости более гранулярно отдельно для каждой операции. Кстати, этот слот можно задать напрямую в <code>defsystem</code>-описании, чем иногда пользуются при необходимости указания нестандартных сценариев поведения. Впрочем, среди установленных у меня порядка 70 библиотек я нашел только несколько примеров такого использования, самый интересный из которых — в описании системы weblocks-prevalance (в остальных случаях это применяется для указания зависимостей систем-тестовых наборов). В данном случае устанавливается зависимость от дополнительно определенной операции <code>prepare-prevalance-op</code>:</li></ul><pre><code>(defsystem weblocks-prevalence<br /> :name "weblocks-prevalence"<br /> ;; кстати, нет смысла задавать параметр `name' для системы,<br /> ;; поскольку имя берется из символа, передаваемого в <code>defsystem</code><br /> :description "A weblocks backend for cl-prevalence."<br /> :depends-on (:metatilities :cl-ppcre :cl-prevalence :bordeaux-threads)<br /> :components ((:file "prevalence"))<br /> :in-order-to ((compile-op (prepare-prevalence-op :weblocks-prevalence))<br /> (load-op (prepare-prevalence-op :weblocks-prevalence))))</code></pre>А сама операция <code>prepare-prevalence-op</code> характеризуется всего одним дополнительным методом, отвечающим за подгрузку дополнительной системы, находящейся по внутреннему пути, не известному в <code>*central-registry*</code>:<br /><pre><code><br />(defmethod perform ((op prepare-prevalence-op)<br /> (c (eql (find-system :weblocks-prevalence))))<br /> (unless (find-package :weblocks-memory)<br /> ;; load weblocks if necessary<br /> (unless (find-package :weblocks)<br /> (asdf:oos 'asdf:load-op :weblocks))<br /> ;; load weblocks-memory.asd<br /> (load (merge-pathnames<br /> (make-pathname :directory '(:relative "src" "store" "memory")<br /> :name "weblocks-memory" :type "asd")<br /> (funcall (symbol-function<br /> (find-symbol (symbol-name '#:asdf-system-directory)<br /> (find-package :weblocks)))<br /> :weblocks)))<br /> ;; load weblocks-memory<br /> (asdf:oos 'asdf:load-op :weblocks-memory)))</code></pre>Впрочем, это можно было бы сделать и иначе :)<br /><br />Проблемой использования <code>defsystem</code>, которой я коснусь в следующей статье на тему шаблонов применения ASDF, является то, что есть соблазн отступить о чисто декларативного описания системы и добавить в него некоторые исполняемые элементы, например, чтение и подстановку версии системы из отдельного файла. Как по мне, было бы разумно обрабатывать эту форму в рамках <code>(let ((*read-evel* nil) ...)</code>, чтобы исключить такие варианты. Причина тут в том, что ASDF-описание может обрабатыватся более, чем 1 раз при поиске систем и разрешении зависимостей, и работа с ним в таком случае выполняется в режиме просто чтения. Возможно, это ограничение будет со временем установлено: во всяком случае это уже обсуждалось в рассылке.<br /><br />Теперь рассмотрим функцию <code>operate</code>, которая является точкой входа в область собственно ядра ASDF, которое отвечает за поиск и выполнение операций над зависимыми компонентами. Она опирается на обобщенные функции <code>traverse</code>, роль которой — в построении плана выполнения той или иной операции, и <code>perform</code>, которая собственно выполняет конечные действия, будь то компиляция, загрузка файлов и т.д, а также на функцию <code>find-system</code>. Кроме того, интересным дополнением (неким альтер-его) <code>perform</code> является <code>explain</code>, которая только указывает, какое действие должно быть выполнено. Хорошая иллюстрация возможностей применения <code>explain</code> дана в статье Питмана:<br /><pre><code>(DOLIST (STEP (SYSDEF:GENERATE-PLAN system :UPDATE))<br /> (SYSDEF:EXPLAIN-ACTION system STEP)<br /> (UNLESS (NOT (Y-OR-N-P "OK? "))<br /> (SYSDEF:EXECUTE-ACTION system STEP)))</code></pre>Этот код на Lisp Machine Lisp позволяет пошагово выполнять обновление системы при условии согласия пользователя на каждом шаге.<br /><br />Операция <code>traverse</code> реализует алгоритм поиска и разрешения зависимостей ASDF. Сам по себе он не стоит отдельного рассмотрения, но что интересно, это то, что алгоритм может обрабатывать 3 типа зависимостей:<br /><ul><li>привычную простую форму (<code>:depends-on (:cl-ppcre ...)</code>)</li><li>версионированную форму (<code>:depends-on ((:version :cl-ppcre "1.2.3") ...)</code>)</li><li>зависимость от фичи (<code>:depends-on ((:feature :x86) ...)</code>)</li></ul>Ко второй форме мы еще вернемся в теме о поддержке версионности. А на счет третьей, то к ней относится интересный комментарий в коде ASDF: "Congratulations, you're the first ever user of FEATURE dependencies! Please contact the asdf-devel mailing-list." :)<br /><br />Точкой входа в механизм поиска Лисп-систем в операционной системе является функция <code>find-system</code>, которая смоделированна на основе стандартной функции <code>find-class</code>. (Артефактом такого подобия является параметр <code>errorp</code>, необходимость в котором как здесь, так и в <code>find-class</code> и <code>find-method</code>, в которых также реализован этот подход, как по мне, по меньшей мере сомнительна). <code>Find-system</code> одновременно проверяет наличие системы среди уже загруженных (таблица таких систем со врменем последнего обновления находится в <code>*defined-systems*</code>), а также ищет на диске с помощью функции <code>system-definition-pathname</code>, которая в свою очередь раскручивает механиз поиска систем функциями, заданными в списке <code>*system-definition-search-functions*</code>. На данный момент в этом списке 2 основных функции: "классический" поиск в директориях, заданных в <code>*central-registry*</code>, и новый поиск в <code>source-registry</code>. Очень важной особенностью <code>find-system</code>, про которую не нужно забывать, это ее побочный эффект — загрузка ASD-файла системы в память и регистрация его в <code>*defined-systems*</code>.<br /><br />Наконец, стоит еще упомянуть 2 обобщенные функции <code>output-files</code> и <code>input-files</code>, которые позволяют задавать способ точного определения полных имен файлов разных типов компонент по их короткому имени в описании системы.<br /><br />В общем-то, это и всё ядро ASDF. Остальное — это ряд утилит для работы с операционной системой, среди которых несколько очень полезных функций, заслуживающих более широкой известности в Лисп-мире (например, <code>run-shell-command</code>, <code>load-pathname</code>, <code>parse-windows-shortcut</code> и другие), а также добавленные в ASDF 2 механизмы определения местонахождения FASL-файлов (в чем-то аналог ASDF-BINARY-LOCATIONS, для которого добавлен и compatibily mode) и работы с центральным реестром.<br /><br /><hr /><br />Разобравшись во внутренностях ASDF я пришел к довольно неожижанному для себя выводу: в ее основе лежит хорошо продуманный объектно-ориентированный дизайн, дающий воможность для ее эффективного применения не только непосредственно, но и как основы для других инструментов. Более того, используя ее как кейс, можно даже учить людей настоящему практическому объектно-ориентированному проектированию<a href="http://www.blogger.com/post-create.g?blogID=6031647961506005424#foot2"><sup>2</sup></a>. В то же время, этот дизайн, конечно, не идеален.<br /><ul><li>Во-первых, он сложен. И это действительно оправдано причинами, описанными вначале. Но все же временами наблюдается излишняя сложность. (Впрочем, эта проблема постепенно устраняется по мере развития кода). Сложность приводит к багам, некоторые из которых существуют до сих пор, на 10-м году жизни системы.</li><li>Во-вторых, он расширяемый, но тоже не до конца: ядро системы создано с оглядкой на последующую расширяемость, но в поддерживающем слое об этом иногда забывали.</li><li>В-третьих, он плохо описан. И это, пожалуй, самая большая проблема ASDF и хороший урок для любого проектировщика: ясная и полная документация имеет важнейшее значение для удачного использования сколь угодно хорошого дизайна.</li></ul><br />И еще можно сказать, что ASDF намного ближе по своей философии к (качественным) продуктам "классических" системных языков, таких как С++ или Java, чем к распространенному в последнее время в Лиспе bottom-up стилю. В то же время, за счет использования полезных возможностей Лиспа: мультиметодов, функций высших порядков и т.п.,— он намного менее церемониален и многословен, так сказать, без перегрузки шаблонами проектирования.<br /><br />В следующей статье — о некоторых шаблонах использования ASDF.<br /><br /><hr /><a name="foot1"><sup>1</sup></a> разница в том, что изменение "слабых" зависимостей не вызывает перекомпиляцию зависящих от них компонент<br /><a name="foot1"><sup>2</sup></a> ... а не такому далекому на поверку от реальности, который можно увидеть, например, в знаменитой книге Гради Буча<br /><br /><hr/>И в придачу, непонятная визуализация того, как происходит поиск системы:)<br /><img src="http://img.photobucket.com/albums/v473/pufpuf/sysdef-search.jpg" />Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com0tag:blogger.com,1999:blog-6031647961506005424.post-68521664184518803672010-07-05T08:43:00.005+03:002010-07-05T08:55:33.664+03:00Уникальные технологии Common Lisp<span id="for-and-date"><strong>Написано для:</strong> <a href="http://www.developers.org.ua/archives/vseloved/2008/10/22/common-lisp-technologies/">developers.org.ua</a><br /><strong>Время написания:</strong> октябрь 2008</span><br /><h3>Базовые подсистемы языка</h3><p>В языке Common Lisp есть как минимум 3 инфраструктурных технологии, во многом формирующие подходы к его применению, которые в других языках либо отсутствуют вовсе, либо реализованы в очень ограниченном варианте. Для компенсации их отсутствия пользователи других языков часто вынуждены использовать Шаблоны проектирования, а порой и вообще не имеют возможности применять некоторые более эффективные подходы к решению типичных задач.</p><p>Что это за технологии и какие возможности дает их использование?</p><h4>Макросистема<br /></h4><ul><li>Это основная отличительная особенность Common Lisp, выделяющая его среди других языков. Ее реализация возможна благодаря использованию для записи Lisp-програм <a href="http://en.wikipedia.org/wiki/S-expression">s-нотации</a> (представления программы непосредственно в виде ее абстрактного синтаксического дерева). Позволяет программировать компилятор языка.</li><br /><li>Позволяет полностью соблюдать один из основополагающих принципов хорошего стиля программирования DRY (не-повторяй-себя).</li><br /><li>В отличие от обычных функций, аргументы, передаваемые макросам, не вычисляются, поэтому с их помощью можно создавать любые управляющие конструкции языка.</li><br /></ul>Примеры применения:<br /><ol><li>Определение управляющих конструкций языка, которые могут использоваться на равне со стандартными (на самом деле практически все стандартные управляющие конструкции также являются макросами. Основу языка — “аксиомы”, которые невозможно определить через другие конструкции — составляют <a href="http://gigamonkeys.com/book/the-special-operators.html">специальные операторы</a>). В качестве примера можно привести <a href="http://en.wikipedia.org/wiki/Anaphora_%28linguistics%29">анафорические</a> управляющие конструкции (см. библиотеку <a href="http://common-lisp.net/project/anaphora/">Anaphora</a>), которые, используя принцип “convention over configuration”, скрывают реализацию некоторых типичных шаблонов.<br /><p>Самый простой пример — макро AIF (или IF-IT), которое тестирует первый аргумент на истинность и одновременно привязывает его значение к переменной IT, которую, соответственно, можно использовать в THEN-clause:</p><pre><code>(defmacro aif (var then &optional else)<br />`(let ((it ,var))<br /> (if it ,then ,else)))</code></pre><p>Учитывая то, что в CL ложность представляется константой NIL, которая также соответствует пустому списку, такая конструкция, например, часто применяется в коде, где сначала какие-то данные аккумулируются в список, а потом, если список не пуст, над ними производятся какие-то действия. Другой вариант, это проверить, заданно ли какое-то значение и потом использовать его:</p><pre><code>(defun determine-fit-xture-type (table-str)<br />"Determine a type of Fit fixture, specified with TABLE-STR"<br />(handler-case<br /> (aif (find (string-trim *spacers* (strip-tags (get-tag "td" (get-tag "tr" table-str 0) 1)))<br /> *fit-xture-rules* :test #'string-equal :key #'car)<br /> (cdr it)<br /> 'row-fit-xture)<br /> (tag-not-found () 'column-fit-xture)))</code></pre><p><small>* В этой функции проверяется, есть ли во второй ячейке первой строки HTML таблицы какие-то данные и в соответствии с этим определяется тип привязки для Fit-теста. Переменной it присвоены найденные данные.</small></p></li><li>Создание <a href="http://en.wikipedia.org/wiki/Domain-specific_programming_language">DSL</a>‘ей для любой предметной области, которые могут иметь в распоряжении все возможности компилятора Common Lisp. Ярким примером такого DSL’я может служить библиотека <a href="http://common-lisp.net/project/parenscript/">Parenscript</a>, которая реализует кодогенерацию JavaScript из Common Lisp. Используя ее, можно писать макросы для Javascript!<br /><pre><code>(js:defpsmacro set-attr (id attr val)<br />`(.attr ($ (+ "#" ,id)) ,attr ,val))</code></pre><p><small>* Простейший макрос-обертка для задания аттрибутов объекта, полученного с помощью селектора jQuery</small></p></li><li>В форме локальных макросов (MACROLET) для модуляризации и разделения потоков вычислений внутри сложных функций, а также для соблюдения принципа DRY при написании лишь слегка отличающегося кода в различных местах одной функции.</li><br /><li>Наконец, создание инфраструктурных систем языка. Например, с помощью макросов можно реализовать продления (библиотека <a href="http://common-lisp.net/project/cl-cont/">CL-CONT</a>), ленивые вычисления (библиотека <a href="http://series.sourceforge.net/">SERIES</a>) и т.д.</li><br /><li>…ну и для многих других целей.</li></ol>Больше по теме: <a href="http://paulgraham.com/onlisp.html">Paul Graham, <em>On Lisp</em></a><br /><br /><h4>Мета-объектный протокол и CLOS</h4><ul><li>Основа объектной системы языка. Позволяет манипулировать представлением классов.</li><br /><li>Методы не принадлежат классам, а специализируются на них, что дает возможность элегантной реализации множественной диспетчиризации. Также возможна специализация не по классу, а по ключу.</li><br /><li>Уникальной является технология комбинации методов, позволяющая использовать стандартные способы комбинации: перед, после, вокруг,— а также определенные пользователем.</li></ul><p>Примерами использования мета-объектного протокола также являются инфраструктурные системы языка, реализованные в виде библиотек:<br /></p><ul><li>object-persisance: Elephant, AllegroCache</li><li>работа с БД: CLSQL</li><li>интерфейс пользователя: Cells</li></ul><p>Библиотека <a href="http://clsql.b9.com/">CLSQL</a> создана для унификации работы с различными SQL базами данных. Кстати, на ее примере можно увидеть проявление мультипарадигменности Common Lisp: у библиотеки есть как объектно-ориентированный интерфейс (ORM), реализованный на основе CLOS, так и функциональный (на основе функций и макросов чтения).</p><p>С помощью мета-объектного протокола стандартный класс языка расширяется специальным параметром — ссылкой на таблицу БД, к которой он привязан, а описания его полей (в терминологии Lisp: слотов) — дополнительными опциональными параметрами, такими как: ограничение уникальности, ключа, функция-преобразователь при записи и извлечении значения из БД и т.д.</p>Больше по теме: <a href="http://books.google.com/books?id=3X5Gnudn3k0C">Gregor Kiczales et al. <em>The Art of Metaobject Protocol</em></a><br /><br /><h4>Система обработки ошибок / сигнальный протокол</h4><p>Система обработки ошибок есть в любом современном языке, однако в CL она все еще остается в определенном смысле уникальной (разве что в C# сейчас вводится нечто подобное). Преимущество этой системы заключается опять же в ее большей абстрактности: хотя основная ее задача — обработка ошибок, точнее исключительных ситуаций,— она построена на более общей концепции передачи управления потоком выполнения программы по стеку. …Как и системы в других языках. Но в других языках есть единственный предопределенный вариант передачи управления: после возникновения исключительной ситуации стек отматывается вплоть до уровня, где находится ее обработчик (или до верхнего уровня). В CL же стек не отматывается сразу, а сперва ищется соответствующий обработчик (причем это может делаться как в динамическом, так и в лексическом окружении), а затем обработчик выполняется на том уровне, где это определенно программистом. Таким образом, исключительные ситуации не несут безусловно катастрофических последствий для текущего состояния выполнения программы, т.е. с их помощью можно реализовать различные виды нелокальной передачи управления (а это приводит к сопроцедурам и т.п.) Хорошие примеры использования сигнального протокола приведены в книге Practical Common Lisp (см. ниже).</p>Больше по теме:<br /><ul><li><a href="http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html">Kent Pitman, <em>Condition Handling in the Lisp Language Family</em></a></li><li><a href="http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html">Peter Siebel, <em>Practical Common Lisp, Ch.19 “Beyond Exception Handling: Conditions and Restarts”</em></a></li></ul><br /><h3>Вспомогательные технологии</h3><p>Кроме того в CL есть ряд технологий менее значительных, которые нельзя назвать в полной мере уникальными, но которые существенно упрощают его применение и делают программы более ясными, а также дают дополнительные возможности для расширения языка:</p><h4>Протокол множественных возвращаемых значений</h4><p>Дает возможность возвращать из функции несколько значений и по желанию принимать все их (и привязывать к каким-то переменным) или только часть. По-умолчанию для кода, не использующего эту функциональность, передается только 1-е значение.</p><p>Казалось бы, это простая возможность, однако, на поверку, она требует обширной поддержки на языковом уровне (учитывая необходимость поддержки возврата из блоков и т.п.).</p><h4>Протокол обобщенных переменных</h4><p>Это аналог свойств в некоторых ОО-языках. Концептуально, оперирует понятием места (place) — по сути дела ячейки памяти, однако не физической (без манипуляции указателями) — это может быть просто объект или же элемент какой-то структуры (будь-то опять же объект, список, массив и т.д.) Таким образом, имеются намного большие возможности, чем при использовании обычных свойств, поскольку для любой функции, которая читает значения какого-либо места, можно указать функцию которая его значение задает.</p>Больше по теме: <a href="http://www.bookshelf.jp/texi/onlisp/onlisp_13.html">Paul Graham, <em>On Lisp, Ch.12 “Generalized Variables”</em></a><br /><h4>Макросы чтения</h4><p>Это инструмент модификации синтаксиса языка за пределы s-выражений, который дает программисту возможность, используя компилятор Lisp, создать свой собственный синтаксис. Его работа основана на фундаментальном принципе Lisp-систем: разделении времени чтения, времени компиляции и времени выполнения — REPL (Read-Eval-Print Loop). Обычные макросы вычисляются (раскрываются, expand) во время компиляции, и полученный код компилируется вместе с написанным вручную. А вот макросы чтения выполняются еще на этапе обработки программы парсером при обнаружении специальных символов (dispatch characters). Механизм макросов чтения является возможностью получить прямой доступ к Reader’у и влиять на то, как он формирует абстрактное синтаксическое дерево из “сырого” программного кода. Таким образом, можно на поверхности Lisp использовать любой синтаксис, вплоть до, например, C-подобного. Впрочем, Lisp-программисты предпочитают все-таки префиксный унифицированный синтаксис со скобками, а Reader-макросы используют для специфических задач.</p><p>Пример такого использования — буквальный синтаксис для чтения hash-таблиц, который почему-то отсутствует в спецификации языка. Это, кстати, еще один пример того, каким образом CL дает возможность изменить себя и использовать новые базовые синтаксические конструкции наравне с определенными в стандарте. Основывается на буквальном синтаксисе для ассоциативных списков (ALIST):</p><br /><pre><code class="lisp">;; a reader syntax for hash tables like alists: #h([:test (test 'eql)] (key . val)*)<br />(set-dispatch-macro-character #\# #\h<br /> (lambda (stream subchar arg)<br /> (declare (ignore subchar)<br /> (ignore arg))<br /> (let* ((sexp (read stream t nil t))<br /> (test (when (eql (car sexp) :test) (cadr sexp)))<br /> (kv-pairs (if test (cddr sexp) sexp))<br /> (table (gensym)))<br /> `(let ((,table (make-hash-table :test (or ,test 'eql))))<br /> (mapcar #'(lambda (cons)<br /> (setf (gethash (car cons) ,table)<br /> (cdr cons)))<br /> ',kv-pairs)<br /> ,table)))))</code></pre><p>Больше по теме: <a href="http://letoverlambda.com/textmode.cl/guest/chap4.html">Doug Hoyte, <em>Let Over Lambda, Ch.4 “Read Macros”</em></a></p><p><a href="http://letoverlambda.com/textmode.cl/guest/chap4.html"><em><br /></em></a></p><h3>Послесловие</h3><p>В заключение хотелось бы коснуться понятия <strong>высокоуровневого языка программирования</strong>. Оно, конечно, является философским, поэтому выскажу свое мнение на этот счет: по-настоящему высокоуровневый язык должен давать программисту возможность выражать свои мысли, концепции и модели в программном коде напрямую, а не через другие концепции, если только те не являются более общими. Это значит, например, что высокоуровневый язык должен позволять напрямую оперировать такой сущностью, как функция, а не требовать для этого задействовать другие сущности такого же уровня абстракции, скажем, классы. Подход к созданию высокоуровневого языка можно увидеть на примере Common Lisp, в котором для каждой задачи выбирается подходящая концепция, будь то объект, сигнал или место. А что дает нам использование по-настоящему высокоуровневых языков? Большую расширяемость, краткость и адаптируемость программы к изменениям, и, в конце концов, настоящую свободу при программировании!</p>Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com3tag:blogger.com,1999:blog-6031647961506005424.post-88438275807848431472010-07-05T08:26:00.003+03:002010-07-05T08:34:47.919+03:00Интересная задачка: вытесняющий мультипроцессинг в userlandНужно в рамках одной нити управления реализовать поочередно работающие 2 "процесса" (скажем, вычисление 2-х функций), переключение между которыми происходит по регулярному сигналу таймера. Естественно, что при переключении состояние вычисления должно сохраняться и восстанавливаться на следующем такте (а не начинаться заново каждый раз).<br /><br />Интересно было бы увидеть, как это реализовывается в разных языках? (Я так понимаю, что в Smalltalk это должно быть тривиально за счет наличия объекта контекста. А где еще?)Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com7tag:blogger.com,1999:blog-6031647961506005424.post-81186096821433921932010-06-30T12:56:00.003+03:002010-06-30T13:05:37.115+03:00ASDF 2Собираюсь написать серию постов про ASDF: его текущее развитие в связи с выходом ASDF 2, внутреннее устройство, шаблоны и некоторые идеи по поводу его использования. Вообще, в последние пару лет в Lisp-сообществе к этой теме (не только собственно ASDF, но и в целом управление сборкой и дистрибутивами), очень живой интерес, поскольку есть вопросы, которые требуют решения. Недавняя статья, дающая пищу для размышлений: <a href="http://tream.dreamhosters.com/tream/musings/49-lisp/76-analysis-of-existing-asdf-files">Анализ использования ASDF в разных проектах</a>.<br /><br />ASDF является фактически единственным на данный момент сборщиком Common Lisp програм. Более того он играет свою роль в различных менеджерах дистрибутивов, хотя и не является самим по себе полноценным решением. И, я бы сказал, что со своей ролью инструмента сборки он справляется довольно неплохо, а вот в сфере описания дистрибутивов есть проблемы, которые пока что не решены на практике. Это заставляет многих людей, в том числе и меня, вообще не пользоваться подобными инструментами и произвоить установку дистрибутивов вручную (благо в Common Lisp среде, в том числе и благоаря ASDF это очень просто<sup>1</sup>), а других (в том числе и меня) задумыватся о создании собственного средства: примеры тому — Mudballs, Lispy, CL-Librarian, LibCL...<br /><br />В <a href="http://fprog.ru/2010/issue5/vsevolod-dyomkin-lisp-philosophy/">недавней статье в журнале ПФП</a> я написал, что<br /><blockquote>развитие средства управления пакетами должно идти именно с учетом децентрализованной структуры Лисп-среды, а не вопреки ей.</blockquote><br />К такому же заключению мы пришли и в процессе <a href="http://lisper.ru/forum/thread/343">обсуждения темы на форуме lisper.ru</a>, которое также заставило меня немного заглянуть под капот ASDF, чтобы узнать, насколько реально и легко создать на его основе пакетный менеджер. Этот "быстрый взгляд" в итоге вылился в растянутое на целый месяц неспешное ковыряние кода и его доработку для полноценной поддержки версионирования. Свое решение я направил в список рассылки <a href="http://common-lisp.net/pipermail/asdf-devel/2010-June/001514.html">asdf-devel</a>, однако оно вряд ли будет интегрированно. В любом случае, этой теме я собираюсь посвятить отдельную запись в этой серии.<br /><br />А начнем мы с того, что нового нам несет ASDF 2, релиз которого состоялся 31 мая, и который уже скоро войдет в вашу любимую Лисп реализацию.<br /><br />Целая новая версия системы претерпела существенный рефакторинг и доработку силами Фарэ и Роберта Голдмана. С точки зрения пользователей она включает в себя следующие улучшения (разумеется, накопленные и отлаженные за посление ряд релизов):<br /><ul><li>версионность самой библиотеки, возможность обновления ASDF</li><br /><li>добавление более простого пользовательского интерфейса</li><br /><li>добавление нового способа конфигурации</li><br /><li>сохранение FASL-файлов отдельно (в других директориях) от исходного кода</li><br /><li>улучшение работы c путями и соответствующее расширение классов <code>component</code> и <code>system</code></li><br /><li>исправление некоторых багов</li></ul><br /><br />Вот <a href="http://www.blogger.com/vimeo.com/12072117">lightning talk Роберта на встрече TwinCity лисперов</a>, посвященный ASDF 2. Нужно заметить, что ценность этого релиза не только практическая, но и символическая, знаменующая переход проекта на новую стадию разработки: более структурированную и прогнозируемую.<br /><br />Итак, кратко о новых фичах.<br /><br />1. Возможность обновления ASDF.<br />Сейчас любой пользователь может воспользоваться самой новой версией ASDF, просто загрузив ее через <code>(asdf:oos 'asdf:load-op 'asdf)</code> (но не через <code>require</code>!) Подробнее об этом в <a href="http://common-lisp.net/project/asdf/asdf.html">мануале</a>, который, также улучшается.<br /><br />2. Более простой пользовательский интерфейс — это функции <code>load-system</code>, <code>compile-system</code> и <code>test-system</code>. Вроде бы как, тривиальное изменение, но избавляющее новичков от необхоимости думать, почему это операции <code>load-op</code> и т.п. являются объектами, а не функциями, и других схожих волнений. Это важно в борьбе с <b>кажущейся</b> сложностью ASDF. При этом методы на <code>test-op</code>, как и раньше — и это понятно — нужно описывать разработчикам систем.<br /><br />3. Системный реестр (<code>source-registry</code>) — новый способ конфигурации :)<br />Именно таким является новый, и по замыслу авторов основной способ задания места расположения исходников систем у пользователя. В то же время старый вариант через <code>*central-registry*</code> остается полностью функциональным и поддерживаемым. Более того, он был дополнен проверкой на самую неприятную и, наверно, частую ошибку при использовании ASDF — отсутствие слеша в конце пути к директориям — теперь этой неразберихи больше не будет.<br /><br />Что же такое системный реестр? Это набор конфигурационных файлов в предопределенной структуре директорий для каждого пользователя, смоделированных по принципу *.conf.d директорий в Unix. А также собственно DSL для конфигурации. Простой пример того, как это работает из мануала:<br /><blockquote>В директории <code>~/.config/common-lisp/</code> находится файл <code>source-registry.conf</code> со следующей конфигурацией:<br /><pre><code><br />(:source-registry<br /> (:tree "/home/fare/cl/")<br /> :inherit-configuration)</code></pre><br />В данном случае поиск установленных систем производится рекурсивно в поддиректориях в <code>/home/fare/cl/</code>.</blockquote><br /><br />Однако это объяснение и пример далеко не исчерпывающи, поэтому лучше читать <a href="http://common-lisp.net/project/asdf/asdf.html#Controlling-where-ASDF-searches-for-systems">соответствующий раздел руководства</a> и проверить все собственноручно.<br /><br />Честно говоря, как по мне, то новый подход для индивидуального разработчика менее удобен, чем использование <code>*central-registry*</code>. Однако он лучше подойдет для средств автоматического конфигурирования (и, я думаю, что как раз опыт в рамках ITA, где используется много Lisp серверов приложений, послужил отправной точкой для разработки этого способа), а также для использования в пакетных менеджерах. И хорошо, что теперь есть альтернативы для каждого из случаев.<br /><br />4. Cохранение FASL-файлов теперь происходит в компилятор-специфичных директориях, по умолчанию спрятанных в домашней диретории пользователя. Благодаря этому устраняются проблемы как конфликта прав в случае использования одних и тех же исходников библиотек разными пользователями, так и stale FASLs, которые возникают при апгрейде реализации (в частности для SBCL).<br /><br />5. В общем, исправлены основные недочеты, которые присутствовали в ASDF при работе с путями в разных операционках, а в классы компонент и система добавлены слоты, указывающие абсолютное положение их в файловой системе. Также исправлены и некоторые другие баги, о чем можно почитать в <a href="http://common-lisp.net/gitweb?p=projects/asdf/asdf.git;a=log">Changelog'е</a>.<br /><br />Какие проблемы остались? Из существенных для меня — две: нечеткая семантика форсированных операций (параметр <code>:force t</code>), а также недостатки работы с версиями (этой теме будет посвящена отдельная запись, поэтому не буду касаться ее здесь).<br /><br />В завершение скажу, что, очевидно, ASDF будет развиваться, как минимум, в сторону устранения явных проблем и упрощения работы с ним, а также, возможно, и поддержки большего количества сценариев использования и перехода к еще более декларативной модели описания систем.<br /><br />В следующей записи — немного о внутренностях ASDF...<br /><br /><hr /><br /><div style="font-size: smaller;"><br /><sup>1</sup> Мой алгоритм установки Lisp библиотеки:<br /> - Загрузить tar.gz файл<br /> - Развернуть в <code>~/lib/lisp/</code><br /> - Создать символическую ссылку на ASD-файл в <code>~/.lisp/</code><br /> - (И вариация для случая работы с разными версиями одного пакета): ссылка на ASD-файл основной версии в <code>~/.lisp/</code>, а при необходимости использования альтернативной версии, скажем hunchentoot-0.15.7 вместо hunchentoot-1.1.0 <code>(push "~/lib/lisp/hunchentoot-0.15.7/" asdf:*central-registry*)</code> (аналог <code>PATH</code>).<br /></div>Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com5tag:blogger.com,1999:blog-6031647961506005424.post-62939768906755839712010-06-29T23:15:00.004+03:002010-07-27T18:15:46.766+03:00Что такое стартап?Или точнее, что отличает настоящий стартап от других видов бизнеса? Еще один вопрос, не имеющий никакого практического значения, но всегда являющийся интересной темой для поговорить :)<br /><br />После 2-х лет, в течение которых я более-менее имею отношение к этой сфере, сегодня пришла простая формулировка, что же я под ним понимаю.<br /><br />В общем, стартап — это начинание, в котором сочетаются и сбаллансированны 3 составляющих: исследовательская, инженерная и бизнес. Исследовательская отвечает за сферу инноваций, инженерная — за их "доставку" до конечного потребителя, а бизнес — за извлечение из этого денег, позволяющих продолжать работу, развиваться и достигать личных целей основателям. При этом тут, на самом деле, не важна сфера: инженерия сейчас нужна не только в Интернете :), но и в биохимии, и во флористике. (Вот, кстати, хорошая цитата про то, почему же отдельно исследование, а отдельно инженерия, случайно встреченная мною в статье в <a href="http://en.wikipedia.org/wiki/Equal_transit-time_fallacy">Википедии</a>: "The theory, while correct in as far as it goes, is not sufficient to allow one to do engineering").<br /><br />При этом про стартапы часто говорят, что в их основе лежит <a href="http://youtu.be/xjDtI-d3jyo">масштабирование</a>, но, как по мне, это естественное следствие того, что все 3 компоненты присутствуют и хорошо работают: исследования обеспечивают то, что стартап реально помогает людям, т.е. на его продукты есть масштабный спрос, инженерия обеспечивает возможность удовлетворить требуемый масштаб клиентов, а бизнес — донести до них информацию о такой возможности.<br /><br />А вот если одна из составляющих отсутсвует или не работает, как надо, то это уже не стартап а другого рода начинание, тем не менее, тоже имеющее право на жизнь (просто, нужно отдавать себе в этом отчет). Если нет инновационной части, то это просто новый бизнес или даже просто проект (и это как раз основное большинство того, что сейчас называют "стартапами", скажем, веб-сайты служб знакомств). Если нет бизнес-составляющей, то это своеобразная форма творчества, только у инженеров, работа ради самого процесса, а не результата. Впрочем, в таких случаях, как и с любым творчеством, иногда может получится что-то выдающееся. Ну и, если нет инженерии, то это холостой выстрел, бизнес, который не состоится, а лишь расчистит дорогу для других. Хотя чаще бывает, что все-таки удается восполнить этот недостаток: почти все бизнесмены понимают, что исполнение — это большая часть успеха.Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com1tag:blogger.com,1999:blog-6031647961506005424.post-82269671362184703342010-05-31T21:22:00.003+03:002010-05-31T21:26:47.568+03:00Парадигмы программирования, followupПолтора года назад написал про <a href="http://lisp-univ-etc.blogspot.com/2008/12/blog-post.html">парадигмы программирования</a>. Сейчас подумал, что нужно называть вещи своими именами, а не так, как принято. :)<br /><br />Например, функциональный язык — это такой, где вы будете задачи решать через функции. Т.е., когда вы беретесь за новую задачу, то напишите какой-нибудь defun или function. Следующим ключевым свойством функциональных языков является вот что: все есть выражение (expression), т.е. возвращает значение, а не утверждение (statement). Всё. На этом функциональность, строго говоря заканчивается. Но есть еще очень много других видов языков. Вот Haskell, например, язык, вообще говоря, не такой уже функциональный язык. Потому что решать вы все будете через типы и напишите сначала Data :: Type. А функции — лтшь один из частных случаев этих типов (один из типов, попросту говоря), но ведь гораздо интереснее такие типы, как монады или функторы. В общем, язык, на самом деле, алгебраический, т.е. для математиков (по складу ума). Вообще, все языки более-менее для кого-то там по складу ума.<br /><br />Далее, вот, Common Lisp — наполовину функциональный, наполовину декларативный (потому что в половине случаев я начну не с defun, а с defmacro). А еще на треть объектно-ориентированный, потому что для defclass тоже часто найдется место (но без фанатизма). А Scheme — действительно чисто функциональный, так кроме функций больше ничего нет. Дальше посмотрим на Erlang — это сперва язык для конкурентного программирования. Всё закручено вокруг процессов и обмена сообщениями между ними. Это только на поверхности там пролог и функции, это всего лишь синтаксис. Т.е. нельзя отрицать функциональную ориентированность Erlang'а, но язык таки process-oriented/message-passing. А что же этот знаменитый функциональный язык JavaScript? Нет, нет и нет: "всё — выражение" не выполняется, и хотя функции — обычный синтаксис для записи кода, но ядро языка — это события и коллбеки, event-driven, как говорят американцы.<br /><br />Возьмем теперь Ruby. Тут всё на классах и лямбдах, т.е. анонимных функциях. А обычные функции превращены в сообщения. Да еще и всё — выражение. Вот такая анархия или мультипарадигменность. И ко всему прочему полный контроль у программиста. Получается объектно-procедурно-немного декларативно-ориентированный. Еще есть Python. Тут все немного (или намного) регулярней. Есть классы и функции, но не всё выражение, анонимных функций, считай нет. В общем, glorified C (как хорошо показывет пример Cython), но, главное, что динамический и разумно простой. Современный объектно-ориентированный процедурный язык, в общем.<br /><br />C Java, например, всё понятно. Тут чистый классовый подход. Класс на inner классе сидит и анонимным классом погоняет. Говорят, это называется объектно-ориентированный подход, хотя это, вообще-то, про Smalltalk или даже про Python. А тут у нас класс-ориентированный, чистой воды. На закуску остается C++, которому можно всё, особенно в версиях Boost и 0x. Только вот, забывают заметить, что это 3 отдельных языка: С, ++, а также еще Tempaltes. И это не говоря про препроцессор. Тут тоже, как бы, всё class-based, но всё — не объект, а указатель. Короче говоря, такой машинно-ориентированный язык с системами типов и классов, в придачу к возможности застрелить себя в ступню, как говорят опять же американцы...<br /><br />Про brainfuck и все остальное более-менее без изменений.Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com12tag:blogger.com,1999:blog-6031647961506005424.post-62621318175162108652010-05-26T15:43:00.002+03:002010-05-26T15:46:36.158+03:00Лисп — философия разработки<span id="for-and-date"><strong>Написано для: <a href="http://fprog.ru/2010/issue5/vsevolod-dyomkin-lisp-philosophy/">Практика функционального программирования №5</a></strong><br /><strong>Соавтор: <a href="http://twitter.com/manzyuk">Александр Манзюк</a></strong></span><br /><br />Чтобы не делать перепечатку, помещаю тут только аннотацию. Основной текст — по ссылке выше.<br /><br />В статье исследуются подходы к разработке, практикуемые в Лисп-среде, на примерах решения различных прикладных задач на языке Common Lisp. Вы узнаете о том, что макросы — это не только синтаксический сахар, но и прекрасный инструмент инкапсуляции, что кроме глобальных и локальных переменных бывают еще специальные, что полиморфные интерфейсы можно создавать без привязки к классам, а также о том, что определять стратегии передачи управления можно не только с помощью монад. Рассмотрены и решения прикладных задач: клиент для хранилища данных Redis, прокси между двумя бизнес-приложениями, внутренний API веб-сервиса, библиотека парсерных комбинаторов.Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com0tag:blogger.com,1999:blog-6031647961506005424.post-34621538658461001892010-05-18T15:35:00.016+03:002010-05-18T16:05:43.477+03:00Простая модель налоговой реформы для малого И крупного бизнесаЯ не буду касаться здесь каких-то сложных с экономической точки зрения идей и тем, таких как: нужно или не нужно НДС и т.п. Взамен же попробую обратиться к столь общим вопросам, как: простота, эффективность, сбаллансированность и справедливость,— которых "немного" не хватает в текущей системе. Отсюда, как по мне, и растут ноги у всех проблем.<br /><br />Итак, первое, единый налог и проблемы, связанные с его использованием для "оптимизации" налогообложения.<br /><br />Очевидно, что без упрощенной системы налогообложения весь бизнес в Украине вообще загнется или полностью уйдет в тень.<br /><br />Далее, не так очевидно, но легко понять, что в текущем виде упрощенная система — это проблема не только для государства (уход от налогов), но и для других форм бизнеса, а также... для самих предпринимателей.<br /><br />В чем проблема для других форм. Простой пример: есть человек, который выполняет какую-то профессиональную деятельность, например, моет окна. Он может делать это в рамках компании и получать зарплату 4000 грн в месяц, при этом налоговое бремя, которое ложиться на такую компанию — еще 2400 грн, а сам человек на руки получает 3400 грн (а 15% платит налога на доход). Если он моет 20 окон в месяц, то за каждое окно компания должна брать с заказчика 320 грн (+ НДС :), чтобы хотя бы выйти в 0 (будем считать, что больше расходов нет). Теперь представим, что тот же человек моет окна как частный предприниматель: теперь, чтобы получить свои 3400 грн, ему нужно выручить всего то 3600, т.е. при цене 320 грн за окно, помыть около 11 с хвостиком окон. А если он помоет 20, то заработает 7800, что ровно в 2 раза больше. Спрашивается, как компания сможет конкурировать с таким фрилансером? Отсюда и зарплаты в конвертах, и "оптимизация" через СПД. Т.е. виновато не только высокое налоговое бремя (хотя это виновник №1), но и несбаллансированная налоговая система (хотя это все-таки только №2).<br /><br />Второй момент: допустим человек моет окна самостоятельно, но не регистрируется как СПД, а платит обычный налог на доход (15%). Хотя такая ситуация немного противоречит закону, однако, по сути, это те условия, в которых находятся все наемные работники, если вообще не принимать в рассмотрение компании. Т.е. наемные работники тоже проигрывают СПДшникам на эти 15%.<br /><br />Теперь рассмотрим вопрос пенсий. СПД-шник платит в пенсионный фонд 84 грн. В то же время любой наемный работник платит 33,2% своей зарплаты, что составляет минимум 300 грн. В результате оба будут получать одинаковую социальную пенсию. Но даже, если человек будет иметь сногсшибательную зарплату и платить огромные отчисления в ПФ, никто не гарантирует, что через 30 лет он будет получать какую-то громадную пенсию, как и сейчас не получают большой пенсии советские герои труда, проработавшие по 50 лет на каких-то вредных производствах и т.д. Потому что государство у нас такое, ответсвенное. Зато из этих отчислений будет платиться огромная пенсия сегодня депутатам, секретным агентам и академикам. Это, ясное дело, абсолютно не справедливо и неэффективно. Как эффективнее — ниже.<br /><br /><blockquote>Итак, существование СПД-формы — это громадная несправедливость по отношению к: 1. компаниям, 2. простым работникам 3. будущим пенсионерам.<br />С другой стороны, проблема не столько в ней, сколько в гигантском налоговом бремени на компании, точнее собственников (почти 60% начисления на зарплату наемных рабочих + 25% налог на дивиденды, который, фактически, является двойным налогообложением). Это бремя приводит к тому, что блокируется рост зарплат и происходит тенезация экономики. А в пенсионном фонде все равно дырка, которая покрывается из бюджета.</blockquote><br /><br />Предложения по решению проблемы исходят из простейших идей:<br />1. СПД — это самозанятый профессионал, который делает работу самостоятельно.<br />2. Пенсии должны быть справедливыми (во всех отношениях :)<br /><br />Соответственно:<br /><br />1. СПД форма должна существовать и еще более упроститься.<br />- Во-первых, в области видов деятельности. Зачем искусственно ограничивать людей: есть специалисты широкого профиля, почему не дать им заниматься теми разными занятиями, которые им по душе? Т.е. КВЕД можно оставить только для статистики, но ни в коем случае не делать его ограничением, как сейчас.<br />- Во-вторых, уплата налога должна быть не такой частой (хотя бы раз в квартал), особенно в связи с п.2<br />- В-третьих, нужно убрать верхнее ограничение на размер выручки (если специалист мирового класса зарабатывает миллионы, он что, перестает быть предпринимателем?), но в то же время СПД не должен использовать наемный труд. Вообще. Если нужны помощники, пускай тоже оформляются как СПД и работают по подрядному принципу. Если у вас есть сотрудники, то это уже компания. Просто, у нас создание компании является нереально сложным делом.<br /><br />2. Ставка налога на СПД должна быть приравнена к налогу на доход физлиц. Это, во-первых, справедливо по отношению к наемным работникам, во-вторых, выгодно для государства. Ну и, в-третьих, не вызовет принципиальных возражений у любого предпринимателя, который понимает, что налоги платить надо, но они должны быть подъемными. В то же время это будет справедливо к тем, кто по каким-то причинам не работает на данный момент как предприниматель: нет прибыли — нет налога. Вообще, предприниматель, который временно (от 1 квартала) сидит без работы, также должен иметь право стать на биржу труда, как и обычный уволенный сотрудник и какое-то время получать социальное пособие. О соцопсобии — ниже.<br /><br />3. Сферу соцфондов можно радикально упростить и, при этом, сохранить существующие пенсии (потерять которые так боятся чиновники). Все трубят о втором уровне пенсионной системы, но никто не пытается доходчиво объяснить, что это такое. На самом деле все должно выглядеть очень просто: должна быть единая, не зависящая от зарплаты, социальная пенсия, которая выплачивается всем нетрудоспособным людям из ПФ, а также социальная помощь по безработице от государственной страховой компании. Обе эти величины должны равняться прожиточному минимуму. Помощь от несчастных случаев тоже должна быть стандартизована и зависить только от категории (тяжести) травмы, а не от зарплаты. Таким образом, имея статистику по количеству работающих, пенсионеров, безработных и несчастных случаев, можно рассчитать необходимую величину единого социального взноса с каждого работающего (не зависимо от того, сотрудника компании, или СПД, или даже рантье) на бюджетный год для выхода на нужные цифры компенсаций для неработающих. (Т.е. для СПД помимо налога на доход добавляется единый социальный взнос, меняющийся из года в год. Такой взнос нужно будет платить раз в месяц или же наперед).<br /><br />А как быть с пенсией, которая выше социальной и т.п.? Для этого и существует второй уровень пенсионной системы, как и второй уровень страховой системы. Он состоит из <b>добровольных</b> отчислений работающих сверх обязательного социального взноса. Естественно, такие добровольные отчисления могут делаться как в государственный ПФ и СК, так и в негосударственные по выбору страхователя. И они должны быть персонализированны. Впрочем, такие отчисления могут делать за сотрудников и компании (опять же добровольно), и иметь в связи с этим какие-то налоговые льготы и т.п. (это уже вопрос экономических моделей). Также, например, государство может делать такие отчисления для всего госаппарата, чтобы сохранять существующую сейчас систему высоких пенсий чиновникам — это будет его конкурентным преимуществом на рынке труда :)<br /><br />А как покрывать дефицит ПФ для выплаты текущих обязательств государства сверх социальной пенсии (средства на которую, напомню, собираются через единый социальный взнос): пенсий, которые уже начисленны пенсионерам, которые, разумеется, не могут делать никаких отчислений? На самом деле, также как и сейчас — из госбюджета и это будут очень большие деньги :) Но хорошая новость в том, что с введением такой пенсионной системы обязательства бюджета будут с каждым годом уменьшаться, как и список тех пенсионеров, которые будут получать пенсию старого образца. Остается вопрос, как быть с теми, кому 54, 58, 50? Для них (как и для всех остальных работающих) можно ввести прогрессивную шкалу пенсии, которую уже должно государство: разделить количество роработанных лет на полный стаж до пенсии и получить процент от той пенсии, на которую человек мог рассчитывать по старой системе. Т.е. для человека, которому остался работать год, а он проработал 24, это будет 96%, а для того, кто проработал 2, а еще впереди 32 это будет 4%. Это обязательство государства, как и перед текущими пенсионерами. Остальную сумму можно будет набирать за счет добровольных взносов.<br /><br />Таким образом, на мой взгляд, пенсионная система станет не только справедливой, но и подъемной для бизнеса (точнее, бизнес вообще будет устранен от субсидирования ПФ), и, в то же время, можно будет сохранить статус кво для современных пенсионеров. А то, что все это будет субсидироваться из бюджета — так, во-первых, оно и сейчас так происходит, а, во-вторых, это, на мой взгляд, оправданная общесоциальная жертва за радикальное улучшение социально-экономической ситуации в стране. И все остальные налоговые проблемы — это, просто, детский лепет по сравнению с этой....<br /><br />4. Наконец, последнее: нужно радикально упростить создание и, что немаловажно, ликвидацию компаний (ООО). Во-первых, администрирование этих процессов: тут можно очень долго говорить. Во-вторых, снизить ограничение по нижнему размеру уставного фонда (неподъемное для многих бизнесов вначале деятельности). Можно говорить, что есть всякие командитные общества, но это же не полноценная бизнес-форма...Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com0tag:blogger.com,1999:blog-6031647961506005424.post-43655689385328656602010-03-28T09:45:00.000+03:002010-03-28T09:47:59.331+03:00GUI в Common Lisp — еще один миф<span id="for-and-date"><strong>Написано для:</strong> <a href="http://habrahabr.ru/blogs/lisp/89097/">habrahabr.ru</a></span><br /><br />Бытует расхожее мнение, что в Common Lisp нет или же плохая поддержка графики. Это еще один миф из серии, что Lisp — это язык только для подсчета факториалов. На самом деле, как и в большинстве других прикладных сфер общего назначения (например, веб, форматы передачи данных, взаимодействие с БД и т.д.) в Lisp-среде есть полный спектр библиотек и тулкитов для всех основных платформ с разными уровнями абстракции.<br /><br /><h4>Linux/Unix</h4><br />Базовой библиотекой для графики из Common Lisp под Unix является <a href="http://www.cliki.net/CLX">CLX</a>. Это аналог xlib, т.е. низкоуровневый клиент, напрямую общающийся по X-протоколу.<br /><br />Кроме того, есть обертки для основных графических фреймворков разной степени зрелости: LTK, CL-GTK2, CommonQt, CL-CAIRO2. Лично мне доводилось иметь дело с LTK, и работа с ним тривиальна. Хороший пример приложения, его использующего — простой и удобный Lisp-редактор/REPL <a href="http://common-lisp.net/project/able/">ABLE</a>.<br /><img src="http://img.photobucket.com/albums/v473/pufpuf/able.png" alt="ABLE screenshot"/><br /><br /><h4>Windows</h4><br />Кроме возможности использовать кросс-платформенные фреймворки из прошлого раздела, есть еще <a href="http://www.lispworks.com/products/capi.html">LispWorks CAPI</a>, о котором только положительные отзывы. Единственная особенность заключается в том, что, как и большинство профессиональных сред разработки на любых языках под Windows, LispWorks стоит довольно дорого (ок. 1200 $), а CAPI доступна только в профессиональной версии. (Впрочем, попробовать его можно и в trial версии).<br /><br />Также есть CL-OPENGL, которая, разумеется, кросс-платформенная.<br /><br /><habracut /><h4>MacOS X</h4><br />Дополнительно к Unix-библиотекам для MacOS X есть хорошие <a href="http://trac.clozure.com/ccl/wiki/Cocoa">Cocoa-биндинги</a> в Clozure CL.<br /><br /><h4>Специфические Lisp-решения</h4><br />Библиотека <a href="http://common-lisp.net/project/mcclim/">McCLIM</a> реализует Lisp Interface Manager спецификацию, определяющую весьма развитый протокол по оперированию с графическими примитивами. Хотя спецификация является платформо-независимой, сама библиотека на данный момент основанна на CLX с вытекающими отсюда последствиями пригодности только для Unix-среды. Если это не есть ограничением, то именно она нужна вам, если вы собираетесь писать что-то, сильно завязанное на графику: игру, графический или CAD-редактор, или же новый <s>Emacs</s><a href="http://common-lisp.net/project/climacs/">Climacs</a>.<br /><img src="http://img.photobucket.com/albums/v473/pufpuf/climacs.png" alt="Climacs" /><br /><br />Оригинальным подходом к GUI, зародившимся в Lisp-среде является проект <a href="http://common-lisp.net/project/cells/">Cells</a>, который переносит spreadsheet-парадигму взаимозависимых "ячеек" на графический интерфейс. У него несколько Lisp-реализаций c тем или иным бэкендом: CELLS-GTK, CELLTK, CELLO,— а также есть и порты на другие языки.<br /><br /><h4>Выводы</h4><br />В общем, варианты есть на любой вкус и запросы. С чего начать? Для простого GUI я бы выбрал <a href="http://www.peter-herth.de/ltk/">LTK</a> или <a href="http://common-lisp.net/project/cl-gtk2/">CL-GTK2</a>. Оба они кросс-платформенные. Первый по причине максимальной простоты, даже примитивности. Соответственно, и подходит он для примитивных приложений. Второй — потому что это хорошая обертка для современной объектно-ориентированной графической библиотеки, активно развивающаяся, да еще и с русским автором :)<br /><br />PS. Еще несколько более специфических графических библиотек, конечно, можно найти на <a href="http://www.cliki.net/graphics%20library">Cliki</a>.Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com3tag:blogger.com,1999:blog-6031647961506005424.post-1894747754327967262010-03-27T12:31:00.002+02:002010-03-27T12:34:47.392+02:00Пару мыслей об SCTestКак по мне, ценность этого мероприятия даже не в том, что люди могут получить фидбек по своим проектам или потренировать презентации (это можно сделать и другим путем, подчас более эффективным), а в том, что оно создает определенную "школу" стартапов, т.е. происходит передача знаний, опыта, информации от старших коллег, чего-то добившихся, состоявшихся личностей и предпринимателей. На каждом мероприятии приходит кто-то интересный с собственным подходом и взглядом на вещи. Как и в любой школе, тут есть свои гуру, чьи слова постоянно цитируют и в которых находят ответы на все вопросы. В нашем случае это, конечно, Стив Бленк.<br /><br />В этот раз панель была очень показательна, поскольку на ней присутствовали представители 3-х основных категорий участников test'а:<br />- состоявшийся, как правило, без сторонней помощи (венчурных инвесторов), а благодаря личным качествам и работе, предприниматель. Как раз пример того, у кого есть чему поучиться;<br />- tru стартапер, т.е. человек, хорошо изучивший стартап-"технологии", как правильно надувать пузырики, что нужно говорить инвесторам и т.п. (Эти качества, по моему мнению, на самом деле независимы от настоящих бизнес-качеств, которые присущи первой категории людей, так что "настоящий стартапер" может как ими обладать, так и не обладать);<br />- startup kiddie (по аналогии со <a href="http://en.wikipedia.org/wiki/Script_kiddie">script kiddie</a>), т.е. молодой человек, увлеченный хайпом стартап-движения, и порой считающий, что сможет в этой игре переиграть "старших товарищей" (упреждиск: если относить меня к какой-то из этих категорий, то разве что к этой).<br /><br />Интересна реакция на выступление представителя M$ — как раз синдром startup-kiddies, которые всё знают и всё могут. Я не говорю о том, что у большинства из пристуствующих на компьютере стоит ворованная копия той самой Windows (упреждиск: пишу это на Linux). Дело в другом: выступал отличный профессионал, с местами интересным докладом, содержащим много полезной информации (конечно, не без корпоративщины и прямой рекламы, а также самолюбования: оказывается, Vista была провалом, потому что Intel с чипсетами подкачал :) А реакция была не поискать интересное, а поерничать, когда же он будет кричать "Developers, developers...", и поприкалываться, что человек не умеет пользоваться PowerPoint'ом (что в данном случае было банальной глупостью). Элементарной культуры и самоуважения не хватает...Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com7tag:blogger.com,1999:blog-6031647961506005424.post-70207656865779394312009-11-07T23:41:00.005+02:002011-10-20T15:46:10.405+03:00О страшных монадахВсегда полезно <a href="http://lisp-univ-etc.blogspot.com/2009/11/haskell.html">поспорить</a>, чтобы еще раз озаботиться своим пониманием темы. Поэтому я решил снова взглянуть на монады и попытаться ответить на вопрос, в чем их проблема (а также попутно понять, в чем от них польза).<br /><br />Как верно было <a href="http://rssh.livejournal.com/142551.html">подмечено</a>, монады — это очень просто. А еще монады — везде. Почему же тогда у <a href="http://zabivator.livejournal.com/364173.html">многих</a> с ними проблемы?<br /><br />1. Терминология<br />Где-то написано, что использование нетипичной для предметной области терминологии — это аттрибут троллинга (к сожалению, не удалось найти ссылку). Можно возразить: "да ведь монады — это же математический термин". Да, но математика и инженерия (в частности, программирование) — разные дисциплины. Математики не строят мосты, да. А термин монада не относится к предметной области программирования. В ней есть такое понятие как "вычисление" (computation). (Пусть меня осудят, но) фактически, монада в Haskell — это <b>стратегия вычисления</b>.<br /><br />Почему же тогда говорят "поместить/извлечь значение в/из монаду/ы"? Говорят так для простоты. На самом деле, можно лишь запустить определенное вычисление с определенным входным значением (отсюда и идиома run...). Слишком похоже на "запустить процедуру", не так ли?<br /><br />Так что с термином "монада" либо создатели Haskell были недостаточно дальновидны, либо, просто, хотели эпатировать программистский мир.<br /><br />2. Подход к изучению<br />К сожалению, большинство объяснений монад так или иначе отталкиваются от того, что монада — это всего навсего тип и 2 простые функции. Но проблема в том, что от того, что вы поймете, как работают функции return и bind, вам не станет сразу ясно, зачем они нужны вместе. Отталкиваться в понимании того, что такое <strike>монады</strike> стратегии вычисления, нужно от того, зачем они нужны? Сейчас я сделал именно так, и все легко стало на свои места.<br /><br />Какие бывают стратегии вычисления? Я сейчас не буду называть самую важную из них, а напишу о ней в следующей записи — пока предлагаю прийти к ответу самостоятельно. Могут быть: вычисление, которое завершается неудачно, если неудачно завершается хотя бы одно из внутренних подвычислений — это знаменитая монада Maybe, которую мы сейчас рассмотрим. Может быть вычисление, которое использует какое-то доступное для всех подвычислений "хранилище" данных — State. Есть вычисление, которое связанно с обменом данными с внешней средой — IO и т.д.<br /><br />Бытует мнение, что Haskell — ленивый язык (более того, это официальная позиция). На самом деле, все немного сложнее или проще, зависит от того, как посмотреть. Во-первых, всем понятно, что абсолютно (или, как говорится, чисто) ленивого языка быть не может, поскольку в таком случае мы никогда не получим какого-то результата работы программы. Каким образом Haskell'исты выходят из этого затруднения? Принято говорить, что, в целом, язык ленивый, а вся неленивость/энергичность (eagerness) находится (так и хочется сказать, заточЕна) внутри монад. Но дело в том, что любая Haskell-программа находится внутри монады IO. В итоге оказывается, что ленивыми в Haskell являются только чистые функции, а все вычисления (в понимании программистском), которых, на самом деле, большая часть — все равно остаются энергичными. В то же время любая чистая Haskell-функция, учитывая pattern matching и немутируемые переменные, по большому счету представляют из себя одну case-конструкцию и вызов других функций. Красиво? Часто, да. Однобоко? Тоже. ОК, но самое интересное дальше.<br /><br />И в других языках примерно то же самое! По сути, разница только в том, что:<br />(а) не проведена четкая граница между ленивыми вычислениями и энергичными. Впрочем, каждый может для себя ее проводить сам: например, можно думать о ленивых вычислениях как об аттрибутах "математических" функций (и таким образом их и программировать), а об энергичных — как о процедурах. В любом языке может быть реализована ленивая стратегия вычислений, однако не везде это сделать одинаково легко...<br />(б) программист самостоятельно может строить процесс вычисления адекватный конкретной задаче. По сути, построение вычислительных процессов и есть одно из главных составляющих собственно того, что мы понимаем под программированием<br /><blockquote>Можно выдвинуть предположение, что <b>все разные виды вычислений можно свести к <strike>нескольким десяткам</strike> определенному количеству монад</b>. И, изучив их, можно будет составлять вычисления, как кубики в конструкторе. (И это предположение действительно выдвигается). В таком случае монады и вправду <b>становились бы решающим методом абстракции в программировании</b>. Впрочем, оказывается, что даже сведя вычисления к монадам, составлять их также легко, как кубики, не удается, и приходится прибегнуть к помощи еще одних монад — монадных преобразователей (monad transformers). В итоге, монада на монаде сидит и монадой погоняет... :) (Можно было догадаться исходя из принципа вычислительной эквивалентности. Вообще говоря, приняв во внимание этот принцип, можно понять, что максима <a href="http://thesz.livejournal.com/645774.html">"there is no silver bullet"</a> таки справедлива всегда).<br />И еще одно: вывод про сведение всех вычислений к определенному набору не нов. Он лежит в основе структурного программирования, где доказано, что все вычисления можно свести к комбинации последовательного выполнения, условного выражения и цикла.</blockquote><br />Вот тут то разница между Haskell'ем и другими языками становится наиболее существенной: другие языки имеют синтаксическую поддержку определенного узкого набора монад, и дают возможность создавать другие монады ad hoc с использованием того или иного механизма. Haskell, по сути, делает то же самое, только создание новых монад регуляризирует и вгоняет в определенные рамки. Итак Haskell — это язык, в котором дисциплина на первом месте.<br /><br />В общем, <b>концепция монад очень полезна, чтобы понять, что могут быть совершенно любые стратегии вычислений</b> и не стоит считать, что возможны только те, которые определенный язык считает родными (вот, в С не было стратегии вычисления Exception, а в С++ появилась :)<br /><br />Возвращаясь к Maybe. Его все понимают, так ведь? Это нужно для того, чтобы если где-то в последовательности вычислений у нас получился элемент Null (Nothing), это значение в итоге вернулось в качестве результата, и при этом другие части вычисления не имели проблем с обработкой такого значения.<br /><br />Этот шаблон хорошо знаком всем, кому доводилось встречаться с <a href="http://en.wikipedia.org/wiki/Short-circuit_evaluation">логическими операторами с коротким замыканием</a>. Идеально он работает в Common Lisp, где значение nil также является логической ложью (это, кстати, считается многими одним из кардинальных преимуществ Common Lisp над Scheme, в котором эти 2 значения разделены). Ниже приводится пример реализации Maybe на CL, аналогичный примеру из <a href="http://www.haskell.org/all_about_monads/html/meet.html">пособия по монадам</a>: <br /><pre><code><br />(defmacro maybecall (val &rest funs)<br /> "Consequently apply a sequence of FUNctionS to the<br />return value of the previous computation, starting with VAL"<br /> `(and-it ,val<br /> ,@(mapcar (lambda (fun)<br /> `(funcall ,fun it))<br /> funs)))<br /><br />;; где:<br />(defmacro and-it (&rest args)<br /> "Like AND, but IT is bound to the value of the previous computation"<br /> (cond ((null args) t)<br /> ((null (tail args)) (first args))<br /> (t `(let ((it ,(first args)))<br /> (when it<br /> (and-it ,@(tail args)))))))<br /><br />;; работает?<br /><br />(defun mother (x)<br /> (gethash x *mothers*))<br /><br />(defun father (x)<br /> (gethash x *fathers*))<br /><br />(setf (gethash :b *fathers*) :e<br /> (gethash :a *fathers*) :d<br /> (gethash :b *mothers*) :c<br /> (gethash :a *mothers*) :b)<br /><br />(maybecall :a #'mother #'father) => :e<br />(maybecall :a #'mother #'father #'father #'brother) => nil<br />;; и даже на brother не ругается, потому что до него дело не доходит<br /></code></pre><br /><br />Впрочем, я понял, что обычный AND (или AND-IT) в CL — это и есть полноценная реализация Maybe стратегии. Притом простая и понятная, нет? <br /><br />3. Нужны ли монады за пределами Haskell'я?<br />То, что в других языках представляет из себя единую ткань вычислений, в рамках Haskell разделено на 2 класса: чистые (обычные функции) и "грязные" (монады). Это приводит к разделению (дублированию) синтаксиса и усложнению программного кода (полная противоположность <a href="http://letoverlambda.com/">дуалистического подхода</a>). В то же время, это делает рассуждения о программах более структурированными и помогает (однако не гарантирует!) соблюдению принципа ссылочной целостности. В теории концепция монад полезна для понимания того, что такое вычисления. На практике же введение дополнительных правил приводит к тому, что появляется определенный предел выразительности языка и близости его к предметной области. Именно так, несмотря на заявления программистов на Haskell об обратном: концепция монады, монадного трансформера и т.д. неимоверно далеки от любой предметной области, которая встречается при программировании, а абстрагировать их средствами Haskell не удастся. Более того, само понятие вычисления, которое в других языках остается имплицитным, в Haskell'е достается на поверхность. Именно с этим, по-моему, связанны частые претензии к преувеличенной сложности Haskell программ.<br /><br />---<br /><br />В следующей записи я дам ответ на вопрос про самую главную монаду, а также попробую разобрать по-отдельности несколько других монад.<br /><br />Еще по теме:<br />* <a href="http://www.haskell.org/all_about_monads/html/index.html">All About Monads</a><br />* <a href="http://marijn.haverbeke.nl/monad.html">Why monads have not taken the Common Lisp world by storm</a>Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com32tag:blogger.com,1999:blog-6031647961506005424.post-87407394456706382422009-11-02T21:25:00.008+02:002009-11-03T19:25:28.861+02:00О Haskell<span style="font-size:-1;">Обновление: обсуждение на habrahabr.ru <a href="http://habrahabr.ru/blogs/Haskell/74164/">Изучай Haskell ради... Haskell'а</a></span><br /><br />Я долго (несколько лет) не решался составить окончательное мнение о Haskell'e: слишком противоречивы были мысли. И вот, наконец, благодаря этой записи о <a href="http://lionet.livejournal.com/44305.html">разборе программки определения двудольности графа</a> я могу это сделать :)<br /><br />Я понял, что Haskell-программисты — в основном, нужно сказать, хобби-программисты — это те, кто программирует не решение задачи, алгоритм, систему, а Haskell! <a href="http://www.blogger.com/post-create.g?blogID=6031647961506005424#h1">[1]</a> Посмотрите, какой простой алгоритм описан в заметке, а сколько вокруг него нагромождено языковых конструкций, объяснений и дискуссий. Чтоб доказать, что он очень простой, привожу пример кода на Lisp'е, который решает ту же задачу без никаких монадных трансформеров и т.п. (не обращайте внимание на большой docstring):<br /><code></code><pre>(defun test-bipartite (graph)<br />"Test for bipartiteness a GRAPH, that is given as a list of pairs<br />vertix - list of immediately connected vertices, like:<br /><br />'((0 . (1)) (1 . (0 2 8))<br />(2 . (1 3)) (3 . (2 6))<br />(4 . (5 7)) (5 . (4))<br />(6 . (3)) (7 . (4 8))<br />(8 . (1 7)))<br />-- bipartite one<br /><br />'((0 . (1 2)) (1 . (0 2 8))<br />(2 . (1 3)) (3 . (2 6))<br />(4 . (5 7)) (5 . (4))<br />(6 . (3)) (7 . (4 8))<br />(8 . (1 7)))<br />-- not bipartite one<br /><br />'((0 . (1 2)) (1 . (0 2 8))<br />(2 . (1 3)) (3 . (2 6))<br />(4 . (5 7)) (5 . (4))<br />(6 . (3)) (7 . (4 8))<br />(8 . (1 7)) (9 . ()))<br />-- not connected one"<br /><br />(let ((map (make-hash-table))<br /> (visited '()))<br /><br /> (labels ((con1 (v)<br /> "vertices immediately connected to V"<br /> (tail (assoc v graph)))<br /><br /> (paint (v level)<br /> "paint the graph from vertix V, return all<br /> visited so far (in subsequent calls of PAINT) vertices"<br /> (pushnew v visited)<br /> (setf (gethash v map) level)<br /> (mapc (lambda (v) (paint v (1+ level)))<br /> (remove-if (lambda (v) (member v visited))<br /> (con1 v)))<br /> visited))<br /><br /> ;; first we paint and check, that all vertices are visited<br /> (unless (set-exclusive-or (paint 1 0)<br /> (mapcar #'first graph))<br /> (every (lambda (entry)<br /> (every (lambda (v)<br /> (oddp (+ (gethash (first entry) map)<br /> (gethash v map))))<br /> (tail entry)))<br /> graph)))))</pre><br /><br />Мне хорошо знакомо это умонастроение — когда в погоне за максимальным использованием мощи языка забываешь о самой задаче,— поскольку в Lisp-мире оно тоже часто встречается: есть языки, которые способны действительно увлечь. И выражение "это взорвало мне мозг" часто звучат и по поводу Lisp'а, и по поводу Haskell'а. Но это же — фигня! Конечно, не может не радовать узнать что-то новое, но не нужно же радоваться этому, как ребенок новой игрушке. Хороший язык программирования должен быть максимально понятен и прост, должен давать человеку свободу самовыражения. Честно говоря, именно этому я обрадовался, когда открыл для себя Lisp: что нашел то, что искал. А не тому, что увидел какую-то конструкцию или изворот, который не доводилось встречать раньше.<br /><br />Так что же, вывод: всем программировать на Lisp? Я, конечно, за, но вывод тут другой: Haskell — очень интересный язык, у которого есть как плюсы, так и минусы. Плюсы: это интересная семантика и сильная теоретическая база, хорошая скорость выполнения современных компиляторов. Минусы: ужасно нерегулярный синтаксис <a href="http://www.blogger.com/post-create.g?blogID=6031647961506005424#h2">[2]</a>, искусственная ограниченность, которая приводит к необходимости задействовать сложные подоходы там, где отлично справятся и простые. И им просто обязательно стоит заниматься, если вас интересует тема языков программирования как таковых, их развития и исследований. Из Haskell берут многое другие более практичные языки: яркий пример тому Clojure. <b>Но он не для написания больших систем и даже не для исследования алгоритмов в общем случае.</b> У языков программирования кроме синтаксиса и семантики есть еще третий аспект, пожалуй даже важнейший, о котором часто забывают — прагматика. То, как язык используется, для чего он предназначается, чем живет сообщество его разработчиков и пользователей. <b>Прагматика Haskell'а заключается в том, что он существует прежде всго для исследования... Haskell'а</b>.<br /><br /><a name="h1">[1]</a> Есть, конечно, исключительные, прекраснейшие Haskell-программисты, написавшие на нем много полезного кода для реального мира, но это, как говорится в нелюбимом мной афоризме, только подтверждает правило.<br /><br /><a name="h2">[2]</a> Для современного языка нерегулярный синтаксис — это неуважение к своим пользователям. Ведь никто в современном мультиязыковом мире не программирует на одном языке, поэтому нельзя требовать от человека держать в голове идиосинкразии каждого. И этих общеупотребимых языков будет все больше и больше, а количество legacy кода уменьшаться не будет. Я сейчас имею дело с Lisp, Python, Php, C, JavaScript, Shell, Java. И это ведь не самый яркий пример.Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com35tag:blogger.com,1999:blog-6031647961506005424.post-90099568757722990092009-08-03T14:40:00.002+03:002009-08-03T14:44:26.568+03:00Очередная встреча по Lisp<a href="http://www.developers.org.ua/calendar/event/531khsfol4dujub66mkc4cet74/ ">Первая встреча по Lisp'у</a>, я считаю, удалась.<br />Попробуем еще раз: <a href="http://www.developers.org.ua/calendar/event/f2hv5cnvjg2uj3qqtk43r2fv7s/">http://www.developers.org.ua/calendar/event/f2hv5cnvjg2uj3qqtk43r2fv7s/</a>.<br />Тема: кодогенерация.Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com1tag:blogger.com,1999:blog-6031647961506005424.post-24667621854767531432009-06-12T14:22:00.001+03:002009-06-12T14:24:26.378+03:00О микроплатежах<b>Написано для:</b> <a href="http://blog.liga.net/user/vdyomkin/article/2733.aspx">Лига.Блоги</a><br /><br />В своем <a href="http://blog.liga.net/user/vdyomkin/article/2727.aspx">предыдущем комментарии</a> я затронул тему того, что до сих пор ни в Украине, ни в мире не появился удобный и получивший широкое распространение сервис микроплатежей. Вообще, под микроплатежами можно понимать разные вещи. Я имею в виду платежи, которые незначительны настолько, что человеку проще перелатить даже до 10-20% от их суммы, зато произвести их быстро и без лишних забот. Понятно, что для каждого это разные суммы, но в среднем это платежи, комиссия по которым не превышает пары десятков гривен (для нашей страны в данное время). В общем, современная экономика говорит, что главная проблема — это транзакционные издержки и их нужно стараться минимизировать. Эти издержки складываются из финансовых, временных, а также усилий и рисков. Микроплатежи находятся в той части спектра экономической активности, где увеличение финансовых издержек наименее нежелательное из перечисленных пунктов.<br /><br />Еще раз, зачем нужны микроплатежи?<br /><br /> * Для оплаты товара в Интернет-магазинах. Иногда да, хотя в данном случае, поскольку имеет место акт передачи товара из рук в руки, можно применять оплату наличными (как, собственно, у нас в основном и делаеют). К сожалению, как в анекдоте, что заказывать товары при коммунизме можно будет по телефону, а получать по телевизору — у нас пока не получается. Микроплатежи отлично могли бы применяться при доставке товаров по почте, но таких товаров у нас, традиционно, не много.<br /> * Для переводов небольших сумм между людьми. Однозначно, это намного проще, чем банковские или мгновенные переводы. И они в этой сфере уже применяются, но пока по-серому.<br /> * Для оплаты различных услуг: коммунальных, виртуальных и некоторых реальных. Это основное применение для микроплатежей. На самом деле, как раз отсутствие такой полноценной возможности тормозит развитие очень большого числа мелких услуг (в первую очередь виртуальных).<br /><br />На мой взгляд, единственный путь появления полноценных микроплатежей — через операторов мобильной связи. По сути дела, сейчас они предоставляют такую возможность, но только в ограниченной форме: пополнение счета другого человка (*125*...) И этой возможностью некоторые уже сейчас пользуются даже для организации оплаты мелих услуг. Это типичные мироплатежи, в которых комиссия составляет до 25% (и не смотря на такой высокий процент, люди пользуеются этой услугой).<br />В чем ограниченность? Главное, нельзя использовать переведенные деньги для оплаты чего-то иного, кроме услуг самого оператора и его афиллированных компаний, нельзя, что называется, "вывести деньги из системы". Второй, менее значительный недостаток: сейчас суммы платежей должны быть кратны 10 (а ведь нет никаких проблем снизить кратность до 1).<br /><br />Осталось сделать последний шаг: упразднивший эти недостатки. И тогда каждый человек с мобильным телефоном сможет без помощи банков переводить деньги другому. Каждый предприниматель получит удобный и надежный способ получать оплату за свои услуги. Оператор, сделавший такой шаг первым, сможет переманить к себе большое количество абонентов, это не говоря о комиссионных доходах, которые он будет получать от работы этой системы.<br /><br />Так почему же этого не делается? Загвоздки:<br /><br /> * Юридические. При этом оператор становится банком? Не совсем. Всего лишь расчетной системой. Скорее всего подобная деятельность требует лицензирования и согласования. Однако операторам и так приходится заниматься подобными вещами постоянно. Использование системы для ухода от налогов? Во-первых, не больше, чем при расчете наличными :) Во-вторых, есть пути мимимизации этого: например, обязательная регистрация для мерчантов (тех, кто хочет использовать систему для получения оплаты за товары или услуги). А так ведь, всё прозрачно — наоборот удобно для налоговой.<br /> * Издержки при вводе денег в систему: условно говоря, человек покупает карточку предоплаты на 30 гривен, выпуск и распространение которой стоит 1 грн. Эта гривна — это 3%, которые не сможет компенсировать комиссия за вывод денег из системы (которая тут должна быть не больше тех же 2-3%). Но ее можно компенсировать введя комиссию на каждый платеж: ту же гривну или даже 50 копеек.<br /> * Мошенничество. Для микроплатежей этот вопрос стоит на последнем месте. Ведь никого не смущают возможности мошенничества при пополнении чужого мобильного счета: такая функция намного полезнее, так ведь?<br /> * Организационные. Безусловно, построение подобной системы — это серьезный проект. Однако, он менее серьезен, чем построение самой сети оператора, с чем они успешно справляется.<br /><br />В общем, как по мне, неразрешимых проблем здесь нет: все упирается лишь в наличие видения и желания у руководства любого из общенациональных мобильных операторов. Микроплатежи — это естественная смежная область, развитие бизнеса в которую для мобильных телекомов дало бы толчек и их развитию в целом, и было бы несомненно полезно для общества.<br /><br />А в перспективе подобная система может стать вообще глобальной и составить конкуренцию таким фирмам как Visa и MasterCard.Vsevolod Dyomkinhttp://www.blogger.com/profile/07729454371491530027noreply@blogger.com1