2008-12-25

Технология надежной и удобной аутентификация для web

Написано для: habrahabr.ru
Время написания: январь 2008


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

В чем именно неэффективность каждого из существующих способов?
  • Простой пароль: удобно, но есть несколько угроз, и самая главная даже не столько несанкционированное ознакомление с ним, сколько то, что примерно одна и та же комбинация логин/пароль может быть использована для множества разнообразных сервисов, часть из которых может быть недостаточно защищена.
  • Одноразовые пароли: безопасно и относительно удобно (но, все-таки, добавляется лишнее устройство), но довольно дорого.
  • Сертификаты цифровой подписи: безопасно, но очень неудобно (проблемы с кросс-платформенной поддержкой токенов), а также дорого.
  • Использование второго канала связи для подтверждения (обычно, мобильного телефона): относительно безопасно, относительно удобно, относительно масштабируемо (пока...).
  • OpenID: безопасно, но на данный момент труднодоступно из-за того, что у 99% людей нет доверенного веб-сервера.


Однако, сейчас уже можно замахнуться на глобальную систему аутентификации, если использовать сочетание ставших уже реальностью 3 феноменов:
  • IPv6;
  • OpenID;
  • стабильное интернет подключение с мобильного телефона/коммуникатора.

Вот она:
Каждый мобильный телефон, находясь в сети провайдера, будет постоянно подключен к интернету и иметь статический IPv6 адрес, а также DNS вида <номер телефона>.<домен оператора>. В каждом телефоне будет встроен сервис OpenID.
Таким образом, человеку нужно будет лишь каждое утро логиниться на свой телефон для того, чтобы иметь возможность автоматически аутентифицироваться на любом сайте. В такой системе, разумеется, появляется уязвимое место — сам телефон, в случае завладения которым, злоумышленники могут выдать себя за его владельца. Но тут, даже на первый взгляд, видится достаточно много способов защиты:

  1. (не говоря о блокировке телефона по звонку оператору);
  2. для каких-то чувствительных тракзакций (например, платежей) можно сделать дополнительную авторизацию в виде, например, пароля (вот уже и двухфакторная аутентификация);
  3. можно добавить биометрическую аутентификацию или использование дополнительного токена, например, RFID-брелка, который человек может носить на связке ключей, шее или запястье, и который должен находится не дальше, например, 2 м от телефона, чтобы работал OpenID сервис.
    Думаю, есть и другие разумные способы...



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

2008-12-23

Surprise with Lambdas

Tobias Rittweiler posted an explanation about the difference between (lambda and #'(lambda. The article is really useful, because I remember several times seeing the question in discussions unanswered.
Well, after reading LoL I sticked to using (lambda variant and I consider it to be the right thing: the less tokens, the less questions and confusion.

All I can add is, that Common Lisp continues to surprise me with how well thought it's design is. So, throw away the reader macros — it works by itself :)

2008-12-19

Мой текущий проект: fin-ack.com

Написано для: habrahabr.ru
Время написания: декабрь 2008

1. Кому нужен учет личных финансов?


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

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

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

Разумеется, многие осознали для себя необходимость вести организованный учет (а кого-то это заставляют делать те или иные жизненные обстоятельства) и уже используют какие-то средства. Я, например, начинал с обычных текстовых файлов. Большинство, конечно, предпочтут Excel из-за встроенных в него финансовых функций. Кто-то идет дальше и использует для этого специализированные программы: GNU Cash, MS Money, Quicken или еще одну из сотни аналогов. Многие из этих программ написаны для PDA и дают огромное преимущество того, что находятся всегда под рукой, однако теряют в функциональности...

Этот подход, безусловно, самый простой. Самый ли удобный? Все зависит от ситуации.

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

  • Другой, пожалуй даже большей головной болью является забота о резервном копировании.

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

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

Я думаю, понятно, что мы предлагаем подход к учету финансов в облаке, на веб. Он лишен описанных выше недостатков, однако, безусловно, имеет определенные особенности. Разумеется, идея не оригинальна, в том числе и в сфере личных финансов. Когда я заинтересовался этим вопросом года 2 назад, уже начали появляться подобные системы. На западе их уже, наверное, около десятка. Зачем нужна еще одна?


2. Наш подход и принципы, на которых построен сервис


Повторюсь, у каждого, наверно, есть примеры того, как веб-средства хранения личной информации выводят работу с ней на новый уровень. Забавно видеть, когда коллеги из разных компаний, которые пользуются корпоративной почтой, теряют время и бизнес-возможности из-за того, что оказываясь вне офиса (в коммандировке или на больничном) не могут получить доступ к ней. В итоге, все равно, приходится использовать G- или Y-mail. А сколько денег приходится тратить компаниям для обеспечения доступа к корпоративной почте через веб?.. Для меня такой killer app в свое время стал del.icio.us, который вернул мне возможность полноценно и легко пользоваться букмарками. Кстати, именно del.icio.us (в его предыдущей инкарнации) был вдохновением в дизайне пользовательского интерфейса для fin-ack.

В сфере управления небольшими объемами данных веб-сервис — это, как говорят, американцы win-win. И разработка с поддержкой проще и дешевле, и использование. Веб-сервис выигрывает за счет эффекта масштаба. Если каждому из тысячи пользователей нужно потратить на организацию бэкапа своих данных 10 минут, то даже если проработка сложной и надежной схемы резервного копирования займет у разработчиков день, эти 8 часов все равно не сравнить с 160 часами, потраченными каждым пользователем в отдельности. Также и на поддержку.

Чем же уникально наше решение? Конечно, я изучал то, что уже есть, а также (по возможности) мнения людей. Говорят, главная проблема с учетом финансов в том, что людям лень постоянно вводить свои траты и доходы. На этом предположении даже основана система 4konverta. В Америке эту проблему решают по своему: поскольку у них 90% рассчетов происходит по карточкам (т.е. в безналичной форме), они берут информацию о тратах прямо у банков, и каждая из систем пытается конкурировать в том, кто лучше автоматически обработает эту информацию, категоризирует ее и т.д. (По-моему, это выброс усилий на ветер). Да и удивляешься доверчивости американцев, спокойно отдающим аутентификационные данные от своего счета третьей стороне. Скольку уже было скандалов с кражей личных данных у любых компаний, начиная с AOL?! Другим недостатком такого подхода является то, что их системы заточены под автоматический учет трат, поэтому у них зачастую даже нет возможности вносить их вручную (например, насколько я помню, так устроена система Mint).

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

  • Во-первых, нужно самому придумать удобный для себя workflow. Например, мне совершенно не интересно, что в моем чеке из супермаркета 20 наименований продуктов. Я запишу их просто как "еда" и поставлю общую сумму. В среднем за день мне нужно ввести в систему 3-5 записей, а занимает это 3-5 минут. За это время я могу посмотреть на свои балансы, подумать о планах, сделать еще какой-то анализ.

  • Интерфейс системы ввода должен быть удобным (это, вроде как, понятно, но, почему-то, не всегда делается)

  • Не нужно запоминать свои траты — их нужно вводить сразу. Для этого мы разработали специальный мобильный клиент. У которого к тому же есть некоторые другие функции, актуальные для устройства в вашем кармане.

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

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

  • Передача данных защищена SSL. Данные хранятся на нашем сервере, а не у третьих сторон.

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

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

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

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

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

  • инструменты для решения своих задач;

  • рассказать, как ими можно пользоваться;

  • показать примеры разных подходов к их применению.

Fin-ack.com не является Web2.0 приложением, но он к этому движется. По моему мнению, в большинстве случаев создания веб-сервисов именно так и нужно поступать: сначала должна быть добротная базовая система, которая не хуже оффлайн аналогов. А по мере привлечения пользователей она должна развиваться вместе с ними и создавать возможности, просто недоступные для своих оффлайн конкурентов. Это и есть реализация одного из принципов Web2.0 по Тиму О'Рейлли: системы, которая становится тем лучше, чем больше людей ее используют. У нас есть идеи, как можно использовать аггрегированную информацию множества пользователей в их интересах, но об этом не стоит говорить до их появления. Я уверен, что многие идеи прийдут от пользователей.

В то же время мы движемся и дальше — к Web3.0 :) Если так можно назвать тенденцию, которую тот же Тим О'Рейлли называет "transcend the web", а другие — тем, что большинство людей в будущем будут работать с веб-приложениями посредством своего мобильного телефона. А в мобильный нельзя "запихнуть" веб-сайт. Приложения для него должны разрабатываться с учетом ограничений этого устройства, а также тех контекстных возможномтей которые оно дает.

Рассказывать о конкретных особенностях нашей системы не буду, статья и так уже достаточно большая. Надеюсь, fin-ack.com сам способен рассказать о себе.

PS.


Почему в теме Lisp? Ну, потому что вся серверная часть написана на Lisp... :)

2008-12-15

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

Со всем спектром придуманных на сегодня парадигм программирования можно ознакомиться в Википедии. В этой статье я хотел остановиться на тех из них, которые имеют значительное влияние на современные языки и программные среды, и о которых, соответственно, обязательно иметь представление любому разработчику. Обязательно потому, что за каждой из парадигм стоит огромная работа по поиску путей решения типичных проблем, возникающих при программировании "интуитивным"[1] путем, и не учитывать этот опыт — значит наступать на те же грабли в который раз и заново придумывать велосипед. Разумеется, многое из написанного в статье общеизвестно, но несмотря на это дискуссии на эти (базовые) темы возникают вновь и вновь. Эта статья — попытка упорядочить для себя общую картину, а также установить точку отсчета, к которой можно было бы привязываться впоследствии.

Основные парадигмы


  • Структурное программирование
    Эта парадигма представляет в основном теоретическое значение (хотя и является неотъемлемой частью практически всех современных языков). По сути, это был первый сдвиг парадигмы в программировании, когда после появления первых абстрактных и высокоуровневых по сравнению с ассемблером языков, пришло осознание, что разрабатывать на них также нужно на более высоком уровне абстракции, чем на ассемблере: в терминах условных выражений и циклов, а не передачи управления между метками. И, что не удивительно, был достигнут консенсус, неоднократно с тех пор подтвержденный практикой, что при использовании более абстрактных конструкций программирование становится более доступным, а программы — расширяемыми, поддерживаемыми,— и, в целом, можно решать намного более сложные задачи.
    Эта парадигма основывается на работах Дейкстры, Хоара, Вирта и других, которые доказали, что любое вычисление можно выразить через 3 базовые операции:

    • последовательное выполнение;
    • условный переход;
    • цикл с условием.

    Девиз структурного программирования — "GOTO является вредным".


  • Объектно-ориентированное программирование
    Это самая распространенная на сегодняшний день парадигма, которая является развитием идей структурного программирования. Она подается как реализация "естественного" взгляда на окружающий мир, в котором всё является объектом. Она, как известно, покоится на 3-х китах:

    • инкапсуляция;
    • наследование;
    • полиморфизм.

    Однако, учитывая распространение, которое она приобрела, а также, наверное, тот факт, что понятие "естественного" и строго математического определения немного отличаются, каждый ОО-язык понимает эти 3 концепции по-своему (во всяком случае, последние две), и каждое такое понимание имеет право на жизнь и применение.
    Наследование включает, условно говоря, наследование "свойств" и "функциональности".
    Для свойств оно может быть основано на классе (от абстрактного к конкретному) — см. C++, Java,— или же на прототипе (от конкретного к абстрактному) — JavaScript.
    Наследование же функциональности и полиморфизм — это 2 стороны одной медали. В Lisp подходе, основанном на родовых функциях эти 2 концепции унифицируются в рамках одной абстракции. Наследование функциональности может пониматься по-разному: как наследование реализации или как наследование интерфейса (см. http://weblog.raganwald.com/2008/04/is-strictly-equivalent-to.html). С другой стороны спектра находится обобщение подхода родовых функций — мультиметоды Clojure — диспетчиризация не только по типу аргументов, а и по любому предикату от аргументов.

    Самой распространненой, однако не единственной рализацией ОО-модели является придуманная в SmallTalk и перенятая в той или иной степени C++, Java, Python и другими основными современнымя языками модель передачи сообщений (диспетчиризация метода по типу первого аргумента, который является объектом, принимающим сообщение). Этот подход я бы еще назвал субъектно-ориентированным программированием, потому что в нем неявно считается, что каждый объект совершает действия, т.е. становится субъектом. В зависимости от динамичности языка в нем может поддерживаться утиная типизация (Duck typing), согласно которой для вызова метода для объекта этот объект должен иметь метод с такой сигнатурой, при этом его тип может не проверяться (динамическая передача сообщений).


  • Функциональное программирование
    Эта парадигма имеет свои корни в Лямбда-исчислении Черча и ее первой реализации — оригинальному Lisp'у — больше лет, чем первому структурному языку Algol-60.
    Сейчас функциональная парадигма (в форме декларативного программирования) противопоставляется императивному подходу. Ее основа — это функция в математическом понимании (преобразование входных данных), а не функция как процедура, меняющая состяние мира.
    Концепциями функциональной парадигмы являются:

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

    Основным направлением в ФП сейчас направлением являются т.н. "чисто" функциональные языки, которые реализуют такие концепции, как:

    • ленивые вычисления
    • строгая типизация и вывод типов
    • сопоставление с образцом (pattern matching)

    Это Haskell, ML (Ocaml, SML), Scala и Qi.
    Однако, и динамические (не поддерживающие строгую типизацию) функциональные языки (как правило, имеющие Lisp-основу) также существуют и активно развиваются: Scheme, Erlang, Clojure.


  • Мета-программирование
    Основными идеями этой парадигмы являются возможности расширения базового языка превоклассными (такими, которые смогут использоваться наравне со встроенными) конструкциями, а также всегда программирование на уровне адекватном логике решаемой задачи и прдеметной области. Мета-программирование поддерживается методологией проектирования и разработки снизу-вверх, при которой сложная система разбивается на уровни, каждый из которых соответствует определенным независимым горизонтельным задачам: от уровня утилит-расширений языка вплоть до уровня доменно-специфического языка для моделирования той или иной прикладной области,— и реализуется постепенно уровень за уровнем. При этом на каждом уровне сложность задачи не увеличивается, поскольку примитивами на нем являются абстракции, выработанные на более низких уровнях.
    Таким образом мета-рограммирование порождает концепцию DSL — доменно специфических языков, создаваемых для той или иной предметной области (языко-ориентированное программирование), одновременно и использующими возможности хост-языка (все его базовые подсистемы, такие как: сбор мусора, математические библиотеки и т.д.), и являющимися адаптированными в синтаксисе и семантике к той сфере, для которой они реализованы.
    К мета-программным возможностям можно отнести те или иные особенности многих языков. Например, в C это перпроцессор, в C++ — в каком-то смысле, перегрузка операторов, а также применение шаблонов для абстракции на уровне системы типов. В Python — это механизм декораторов, позволяющий динамически дополнять реализацию методов. Попытки добавить определенные макросистемы делаются во многих языках. Это, например, MetaLua, а также модули andand, rewrite, rubymacros и др. в Ruby и т.д. Безусловно, базовым языком для парадигмы мета-программирования является Lisp, в котором и зародились (и продолжают зарождатся) ее концепции.


  • Скриптинговые языки
    Можно сказать, что это недопарадигма, поскольку она не основывается на какие-то фундаментальные исследования или принципы. Основная идея тут — максимальное удобство и простота ad-hoc реализации опеределенного класса решений на определенной архитектуре (иными словами, практичность). Это обуславливает такие характеристики языка, как:

    • динамичность
    • интерпретируемость
    • привязка к хост-системе

    В рамках этой парадигмы получили свое рождение такие языки, как: Perl (и PHP), Python, Ruby, Tcl, Lua, Basic, JavaScipt, Shell-языки (Bash, PowerShell, ActionScript, AppleScript, ...), некоторые из которых впоследствии переросли ее и перешли, как правило, в категорию объектно-ориентированных языков.


  • Программирование, ориентированное на параллелизм
    Это самая новая парадигма и, пожалуй, еще не до конца сформировавшаяся, поскольку пока что нет косенсуса на счет того, какая из предложенных концепций станет общепризнанной (если это вообще произойдет). А это и развитие архитектуры, подобной MapReduce Google, и асинхронная передача сообщений между процессами (полностью без общего состояния) Erlang'а, и использование программной транзакционной памяти — обобщения концепции транзакции в базах данных на любое вычисление (Haskell, Clojure). Основой для выделение этого подхода в отдельную парадигму стало понимание того, что использование блоков для синхронизации параллельных вычислений не является масштабируемым и поддерживаемым решением при реализации многопоточности (в том числе см. http://jcp.org/en/jsr/detail?id=166). Можно провести параллели между этой парадигмой и процедурной: проблема с использованием блоков аналогична проблеме goto. Пока что ясно одно: эта парадигма станет развитием функционального подхода с его краеугольными камнями немутируемых структур данных и ссылочной целостности.


Дуализм типизации


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

  1. Слабая vs сильная. Это различие скорее количественное, чем качественное. "Абсолютно" сильная типизация (как это реализовано в Haskell'е) не позволяет никакого приведения типов (во время исполнения). Более слабые системы типизации дают такую возможность до определенной степени. Например, многие источники называют С слабо-типизированным языком, поскольку он позволяет выполнять неявное приведение типов, а также явное приведение указателей (что не дает возможность проверить тип на этапе компиляции).
  2. Статическая (проверка типов во время компиляции) vs динамическая (проверка на этапе исполнения). Суть динамической типизации можно изложить во фразе из Common Lisp: "у переменных нет типов, типы есть только у значений". Это не значит, что типы не проверяются (как это происходит в нетипизированных языках, тких как Assembler и Forth, но они проверяются при выполнении операций над конкретными значениями во время исполнения программы. Поэтому в динамическом с сильной типизацией Lisp'е не может возникнуть ошибки сегментации памяти при попытке использовать значение не того типа, которая часто встречается в статическом со слабой типизацией С. По мнению идеологов статической типизации, благодаря ее использованию можно исправить большой класс ошибок в программе, связанных с использованием несоответствующих типов, с помощью явного указания типов и их проверки компилятором. А также увеличить быстродействие программы благодаря отсутствию необходимости проверки типов во время исполнения. В то же время написание таких программ является более длительным и сложным процессом (языки со строгой статической типизацией еще называют bondage & discipline languages), и что самое неприятное, их становится очень трудно менять впоследствии. Поэтому, наверное, ни тот ни другой подход не являются предпочтительными в общем случае и могут быть оба использованы в зависимости от конкретной задачи и преференций разработчиков. В идеале, должны появиться языки, которые будут позволять задействовать обе системы параллельно или на выбор. Например, в Common Lisp есть возможность объявлять типы для выражений и переменных для того, чтобы отключить их проверку во время исполнения — это решает проблему скорости для динамического языка, однако не адресует вопрос статической проверки типов при компиляции.

Ссылки на другие интересные парадигмы


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

  • Программирование на основе стека
    Языки на основе стека — это очень простые языки, примитивные операции которых построенны вокруг манипуляции этой структурой данных. Такой подход, в основном, избавляет от необходимости использования переменных для ссылок на значения (point-free programming). Кроме того, синтаксис таких языков становится весьма унифицированным (как правило, используется постфиксная нотация), что дает возможность задействовать мета-программирование.
    Языками на основе стека являются PostScript и Forth. Кроме того, большинство виртуальных машин современных языков, такие как JVM, .Net CLR, Perl Parrot и т.д. также являются языками основанными на стеке.

  • Логическое программирование
    Это реализация в языке модели формальной логики. При этом результаты работы программы часто являются побочными эффектами логического вывода. Интерес таких языков в том, что они предлагают совершенно иную модель вычислений, чем фон Неймановская архитектура или Лямбда-исчесление Черча, соответственно некоторые задачи, например, связанные с праллельными вычислениями или имеющие четкую математическую модель, можно решать с применением совсем других подходов. В рамках этой парадигмы получили развитие такие концепции, как бэктрекинг и сравнение с образцом (pattern matching).

  • Программирование в массивах
    Это направление было введено в APL и продолжает развиваться в его последователях J/K/Q, также его реализации присутствуют в MatLab'е и Mathematica. Базовая идея заключается в том, чтобы обобщить скалярные операции на векторные типы данных. Кроме того в этих языках, как правило поддерживается point-free programming. Их удобно использовать для выполнения сложных математических вычислений и они представляют скорее исследовательский интерес. Интересным примером промышленной системы с использованием таких языков является БД kdb+.


P.S



В заключение хотелось бы упомянуть еще одну парадигму или, скорее, анти-парадигму: эзотерические языки (эзоязыки). Эту концепцию также называют Turing tarpit (бочка дегтя Тюринга), потому что эзоязыки показывают, что можно легко создать Тюринг-полный язык, на котором будет совершенно невозможно программировать. По-моему, это хороший урок для тех, кто утверждает, что язык программирования не имеет значения. Да, на любом Тюринг-полном языке можно реализовать любой алгоритм и любую программу, но поробуйте сделать это на brainfuck'е, использующем только 8 допустимых символов и 8 операций, на котором "Hello world" выглядит так:
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.
, или на Whitespace, для которого весь набор значащих символов составляют пробел, табуляция и новая строка.


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

2008-12-14

1 namespace to rule them all

This thing scares and confuses me: filter even [1..10]
Is even a function or a var?
Personally I'd prefer: filter #'even [1..10]

Common Lisp's n-namespace property is one of the most underestimated features of this most underestimated language. In CL you virtually always know, what you are dealing with, a variable or a function. "Phew!" you say, "in C++ as well". Not so fast. If you try to use callbacks you don't. And when we enter the realm of the functional paradigm, where functions fly all around like daggers in the infamous house, it's really good to know for sure.

Let's restate the rules:
1. Prefix notation: in first position of a form should always be a name of a function/macro/special operator (unless it's an inner form of a macro, which introduces its own structure). So:
* (+ 1 2), not (1 + 2)
* (print 1) -- just like print(1)
* (with-open-file (in "test.txt") ...) -- a structural macro

2. If you want to pass a function somewhere #' is your best friend. Thus you can (fun)call it, apply it, map/filter with it etc.

3. Finally we need to add one more ingredient to the recipe -- the ability to use (lambda ...) forms in function position. We can do it with such a reader macro:
(set-dispatch-macro-character #\# #\f
(lambda (stream subchar arg)
(declare (ignore subchar)
(ignore arg))
(let ((sexp (read stream t nil t)))
(let ((fname (gensym)))
(setf (symbol-function fname) (eval `(lambda ,@sexp)))
fname))))

which allows us to write this code:
CL-USER> (#f((a b) (+ a b)) 1 2)
3

2008-10-26

Парадокс Монти Холла

Пример того, как программистский подход к решению проблемы может сделать даже парадокс тривиально понятным.
(defun monty-hall (n)
"Возвращает отношение доли успешных и неуспешных розыгрышей
игры Монти Холла при выборе стратегии всегда менять изначально
выбранную дверь"
(let ((succ 0.0)
(fail 0.0))
(dotimes (i n)
(let ((choice (random 3))
(prize (random 3)))
(if (= choice prize)
(incf fail) ; мы выбрали правильную дверь, но придется поменять
(incf succ)))) ; мы выбрали неправильную дверь, значит, после смены -- правильную
(/ succ fail)))

И статистика:
CL-USER> (monty-hall 100)
1.5641025
CL-USER> (monty-hall 1000)
1.6455027
CL-USER> (monty-hall 10000)
1.9958059
CL-USER> (monty-hall 100000)
2.0147724
CL-USER> (monty-hall 1000000)
1.9966408
CL-USER> (monty-hall 10000000)
1.9986455

Monty Hall Paradox

The example of how tackling a problem in a "hacker's hat" can make a paradox trivial to understand.
(defun monty-hall (n)
"Returns a ratio of success rate to failure rate, if we follow
the strategy of always switching the initially selected door in
the Monty Hall Problem"
(let ((succ 0.0)
(fail 0.0))
(dotimes (i n)
(let ((choice (random 3))
(prize (random 3)))
(if (= choice prize)
(incf fail) ; we've chosen the right door, but will need to switch
(incf succ)))) ; we've chosen the wrong door and after switching -- the right
(/ succ fail)))

And the statistics:
CL-USER> (monty-hall 100)
1.5641025
CL-USER> (monty-hall 1000)
1.6455027
CL-USER> (monty-hall 10000)
1.9958059
CL-USER> (monty-hall 100000)
2.0147724
CL-USER> (monty-hall 1000000)
1.9966408
CL-USER> (monty-hall 10000000)
1.9986455

2008-10-23

"Clojure for Lisp Programmers" Talk Summary

In his talk Rich Hickey made an overview of the major features of Clojure and the design rationale behind them. Except for being Lisp-1, as far as I understand, Clojure is mostly based in and inspired by Common Lisp. Moreover, it tries to push some of the CL technologies to even purer abstraction and generality.

I've tried to categorize the discussed features in their relation to Common Lisp:
  1. Taken from CL and stretched/modified
    • destructuring
    • nil behavior in conditionals
    • defmacro
    • multimethods with dispatch by an arbitrary function of arguments
      Obviously is more general, than the CL variant, although Rich failed to mention, that CL's generic functions have also dispatch by EQL, which is enough for, I think, 95% of the situations.

    • first-class immutable & persistent data-types: list, vector, hash-map and set
    • seq(uence) protocol
      I consider the last 2 a very important idea and a good candidate for implementation as a CL library.

  2. Intentionally different from CL
    • Side-effect free reader & separate symbol resolution for macros
      This is also a well thought-out choice, which gives a lot of food for thought.

    • Lisp-1. Symbols and vars separated (in Lisp-2 vars are symbol-value or symbol-function)
      In my opinion, not the best option. But it's, obviously, the simpler one to implement

    • egal equality

  3. Additional technologies
    • STM (MVCC), agents & refs, commute
      A full-fledged innovative concurrent infrastructure.

    • Call-ability of data-structures as vars
      A controversial choice, which may lead to a lot of confusion.

    • Metadata
      In some sense, a substitute for lack of multiple-values. Besides, a partial introduction of support for the prototype pattern (although lacking inheritance).

  4. Different from CL because of the need for JVM integration (CL solution being superior)
    • no multiple-values
    • namespaces instead of packages
    • boolean true/false

  5. Arbitrary choices inferior to CL solutions (because too much work needed to implement & some infrastructure already available)
    • exception handling
    • debugging & profiling tools
    • boxed integers instead of numeric tower

2008-08-25

Re: An Acceptable Lisp

Recently I've been thinking about programming languages in general and how they relate to Common Lisp, spurred by such questions (and discussions) as Why Ruby is an acceptable Lisp?, Does Common Lisp need a better type system?, What do you LISPers think of Haskell?, Paul Graham's Arc is released today... what is the long term impact? etc.

As far as I understand, what people mean, when they talk about acceptable Lisp, is not Lisp per se, but a universal high-level programming language. It's just, that Lisp has made a bold claim to be such one. Is it? More on that later.

...So, first I've analyzed the languages, which are wide-spread now, whether they can deliver universality, so to say.

Kent Pitman has very accurately noted (Q16: Scheme in CS), that most people program in assembly languages, be they concrete, like C/C++, or abstract, like Java. And that is understandable, because these languages allow a straightforward solution, and for the majority is content with that. And that's one of the most important arguments, why purely functional programming languages can't become universally-accepted.

What about high-level languages? First of all, what kind of an animal is that? My idea is, that it should be a language, in which you can declare what to do and not how, think of any problem in terms of its domain vocabulary and not in terms of moving bits and bytes around. But that's, obviously, an oversimplification. One of the most important qualities of a high-level language is, that it provides efficient means of abstraction (and the function is the most important — that's why languages without first-class functions don't count as high-level). The high-level language should not only provide means to solve a real-world problem, but as well to solve a problem of solving such problems. Well, although I can't express concisely and clearly, what's that, I'm sure, that there exists a common understanding, or rather feeling, of that concept among the people, who raise such questions.

So, what languages do we consider high-level?

Python is an attempt at a high-level language. But it's stuck in the middle of a road from imperative to high-level. The goal of the language's design was not to achieve universality, but to create a robust dynamically-typed object-oriented language for scripting, and it's reached, but there's no more ambitious aims ahead...

Ruby is definitely not an "acceptable Lisp" — it's a clean Perl (with a "mixin" of Smalltalk). The aim of a language is power, but in a somewhat myopic (Perl) view, i.e.: the ability to hack (which in the context is the opposite of build complex systems) neatly and fast. That's why everything, which was considered in the historical context of the language's creation as a powerful feature to have, was incorporated into it. From classes to keyword parameters to regular-expressions to anonymous functions. Surely a lot of the achievements of language design from Lisp ans Smalltalk and other languages are in Ruby, but it's not "turtles all the way down" -- not uniform enough to be able to efficiently develop new high-level concepts on top of it, which will fit (so you're mostly left with what's already in the language. (Btw, Mr. Braitwaite tries to prove the opposite).

C# (it seems, they are incorporating first-class functions in C#3) is an interesting example of a language, which is based on C++ and Java and gradually moves to high-levelness. But still it lacks underlying vision of universality, it's very similar to Python in a way, that the imperative paradigm with it's class-oriented extension if considered the most important and basic one, thus limiting the others to being second-level citizens.

Erlang is just a DSL for concurrency programming. It's functional, but not at all intended to be universal.

Haskell is definitely different from the previously discussed languages, because it really is built on the basis of an abstract ideology and not as a pile of features. It's 2 fundamentals are pure functionality and type-inference, and as a manifestation of this ideas it achieves prominent results. But this ideas do not lead to a language, capable of adapting to every problem and producing the most natural solution.

Forth, J et al.. Why they can't be universal? I think it's a debate between an ordinary to ourselves (alphabetic) language and an hieroglyphic one. They are definitely a way of their own, and a very interesting one, but not a way to unification, I think.

Scheme is a high-level language, built from a set of Lisp "axioms", whose biggest flaw is prohibition of real macros. Paul Graham's Arc is an attempt to bring macros to Scheme, but, i think, it's early to say, whether it will succeed as a separate language or a successor to Scheme.

Common Lisp
Is Common Lisp an acceptable Lisp, i.e. is it a universal high-level programming language? I don't think, that it is, and that has been proven by history, so to say (it's not even near to being universally accepted). But it, surely, is the most advanced language in this direction and it possesses some of the high-level features, that are not present in other languages (and these features are actually the features of "ideal" Lisp, from which Common Lisp derives its name). Briefly about them:
* Parens are not a drawback. On the contrary, they are a powerful basis for abstraction, because they help to denote a form — the universal building block of it. Form is both data and code. Then there are S-expressions which are supported by parens, but have the other underlying concept — prefix notation, which unifies operations (mostly coded in infix notation in other languages) and function application (either prefix or suffix, but never infix).
* As Paul Graham has pointed, the best way to design a language is to start from the most general set of axioms. And Lisp (Common Lisp included) follows this principle, moreover its set of axioms is probably the most general one.
* Macros are built on top of Lisp's general-purpose s-expressions being code and data. I think it's obvious, why they are obligatory to a high-level programming language -- because syntactic abstraction is one of the most common ones.
* No "religious" adherence to any specific programming paradigm, but taking the best principles from every one (at least at the time of language's design).
* A concise standard. It's funny, when other language programmers "complain" about the CL standard being bloated, incomplete and outdated. Maybe, the only language, which can claim to have at least a somewhat accurate, complete and up to date one is Java (or rather could), while others either don't have any (Ruby, Python etc.) or have a lot of revisions, vendor-specific parts and incompatibilities between them (JavaScript, C++ and so on). And the fact, that the functional programming languages, which usually have clear formal specifications, are constantly in the process of revising them, as for me, seems to prove, that they just didn't reach maturity yet.

But, still, there are some features, or rather underlying principles, that Common Lisp lacks.
* What static typing folks want?
I "belong" to the dynamic typing camp, but I think, I understand, what the other camp needs. The Lisp idea, that variables don't have types and only values have, should be acceptable to them. But they want a compiler subsystem, which checks one of the dimension of the program's correctness (the dimension, that really allows verification), based on the programmer's declaration. And that surely can be considered an important high-level feature. Moreover, it seems to be a reachable goal.
What CL has is optional type declaration and runtime check. Qi implements static type-checking and type-inference, and besides other functional-paradigm features. But it abandons prefix-notation and multi-paradigm approach as a whole. Thus it's a very advanced functional language with roots in Lisp, but not Lisp. Neither it is universal.
What can be done inside Lisp to add static type-checking to dynamic one? I think there's a possibility at least to implement partial "lexical" type-checking for lexical variables. But how to do that properly is a different question. Anyway, I'm sure, that although a "universal language's" type system can have a deep mathematical foundation, it should allow not only logical formalisms for defining types, but as well plainer and simpler variants.

* What Schemers wanted, and why they chose Scheme?
As far as I understand, people who choose Scheme over CL do that not because it nil and false, nor for the lack of macros. And not because it's Lisp-1, but to be more precise, because it supports evaluation of the car of a list, while CL only allows function (and macros, and special-forms) names in it. I don't see, why there can't be a compromise between the 2 approaches: leave multiple namespaces, which is, as practice shows, very beneficial, and at the same time treat the function cell (car of a list) differently: if it's a name, it should be from the function namespace, while if it's a list -- it's a form, which should be evaluated and produce either a function object or a function name.

* Some idiosyncrasies, mostly inherited, should be removed (not all functions are generic; errors do not subclass standard-object etc.). The MOP should be in the standard. And the tradition of incorporating the best from every paradigm should be continued: at least the concurrency-oriented paradigm is relevant.

* Community process.
If the ideal Lisp is ever to appear it should have a standard and a process of modifying it without loosing backward compatibility.
I understand, what kind of a difficult and bureaucratic process was the effort for the Common Lisp standard, and that it's not going to be repeated. Well, it's not necessary. The times have changed, and now excellent implementations of a language are produced by the community, which is globally distributed. There's no central power and a big client (like DARPA), which demands official standard. The standard should and could be produced by the community as a result of a consensus. Why Python, Perl or Arc will never become universal? Because their development is mostly determined by the tastes and preferences of their "benevolent dictators", and their views, whatever enlightened and advanced be them, are subjective and can't be accepted universally. Today there's virtually no other power in the Lisp world, except its users. Nor big organizations, like Microsoft, Google or DARPA, neither dominant implementation producers. So I think, that if the members of the CL community with the biggest impact on it, like the participants of the original standardization process, who are still active in the Common Lisp world: Kent Pitman and Daniel Weinreib,-- as well as the new generation of prominent lispers: Pascal Constanza, Edi Weitz, Paul Graham,-- and others were able to self-organize and setup a community process for the creation of the new Lisp standard, it could yield a desired result.

Maybe it's time to start advancing from Common Lisp to Universal Lisp?..

2008-08-23

Shell — секретное оружие Unix :)

Написано для: habrahabr.ru
Время написания: февраль 2008


Как я написал в одном комментарии:
если не хотите знать про коммандную строку, то в линуксе вам делать нечего. Будет вам та же винда, только вместо одних проблем получите другие...
--comment605950


С другой стороны, как раз в shell заключается одно из кардинальных отличий Unix-систем, то, что делает эти системы открытыми...

В чем же основные особенности Unix shell, которых нет в Windows?
  1. Весь процесс конфигурации и администрирования Unix построен на работе с текстовыми файлами. Shell — это полноценный язык программирования, заточенный для манипуляции строками и работы с тектсовыми файлами. К тому же, именно как расширение возможностей shell по обработке строк и возник Великий язык PERL!.
  2. Благодаря п.1 shell — это отличный инструмент для постепенного изучения самой операционной системы.
  3. Наличие shell способствует реализации важного принципа создания любых программ:
    Если это простая программа, которая предназначена для выполнения ограниченного числа операций, ее нужно реализовывать как набор команд, родных для платформы, на которой она работает (как правило, платформой является ОС). Если же имеем дело со сложной интерактивной программой с потенциально неограниченным набором операций — ее нужно реализовывать как язык опять же на родном для платформы носителе (сейчас наиболее родным и удобным носителем при взаимодействии с компьютерами все же является текст, а не звук, графика или что-либо еще).
    Примерами 1-го и 2-го подхода могу служить tcpdump, реализованный полностью в текстовом режиме, над котором не представляет труда надстроить интерфейс (по сути, Ethereal — и есть подобный интерфейс), и emacs — самый расширяемый текстовый редактор.
    В этом проявляется основное отличие философии Unix и Windows: в Win интерфейс програм по умолчанию делается на графическом языке, из-за чего программы практически невозможно сопрягать или строить на их основе новые. Потому что для этого нет поддержки на уровне ОС. И для того, чтобы сделать программы расширяемыми, все равно приходится использовать текст (пример — тот же VBA в Word и Excel).

  4. Автодополнение команд и, что самое главное, путей. Трудно даже представить, сколько времени и сил экономит эта небольшая возможность!
  5. Полный набор утилит для всех возможных задач администрирования.


Стоит также перечислить утилиты shell, которые обязательно нужно использовать в повседневной работе с системой:
  • man — это справка по ОС, которая включает не только описание работы всех команд, но также и что должно быть в большинстве из конфигурационных файлов, а также много другой полезной информации;
  • less — просмотр текстовых файлов;
  • grep (программистов, не знающих о grep, не берут на работу в Amazon.com :);
  • locate — быстрый поиск файлов.

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


Можно только повториться, что для тех, кто не хочет работать в shell, Linux или другая Unix-based ОС — во многом такая же неудобная и непонятная система, как и Windows. Но стоит все-таки попробовать разобраться с shell — хотя бы для того, чтобы посмотреть, как правильно администрируются информационные системы.

2008-07-25

О пользе унификации и вреде предубеждений

Написано для: developers.org.ua (изменен код макро gcase)
Время написания: июль 2007


In fact, let’s not even worry about Java. Let’s not complain about Microsoft. Let’s not worry about them because we know how to program computers, too, and in fact we know how to do it in a meta-way. We can set up an alternative point of view, and we’re not the only ones who do this, as you’re well aware.[1]
–Alan Kay
There is a huge difference working with a language where you have to wait a year to get a new loop statement from the language designer and compiler implementer - or - where you do it yourself in twenty minutes.[2]
–Rainer Joswig


1. Принципы хорошего стиля программирования



На Западе широко известна следующая концепция из классического учебника MIT по программированию “Структура и интерпретация компьютерных программ”:
Сперва мы хотим утвердить идею, что компьютерный язык — это не просто способ добиться от компьютера выполнения операций, но, скорее, что это новое средство для формального выражения идей о методологии. Таким образом, программы должны быть написаны для людей, чтобы они их читали, и только между прочим для машин, чтобы они их исполняли.[3]


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

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

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

К сожалению, современные мейнстрим языки жертвуют целью сделать возможным хороший стиль из коньюнктурных соображений реализации. В результате нам приходится использовать корявые языки, что приводит к полной неэффективности на более высоких уровнях абстракции. Мало того, что программист для того, чтобы получить возможность создания серьезных программ, вынужден вкладывать большое количество времени в изучение эзотерических технологий, которые через пару лет устареют (они устаревают как раз из-за своей ограниченности и привязки к конкретным промежуточным архитектурам), таких как: фабрики классов COM или же MFC на пару с Win32 API, AJAX и пр. Применение этих технологий серьезно затрудняет развитие достигнутых результатов другими людьми, которые не имеют времени, желания или возможности изучить их, не говоря уже о внутренних ограничениях самих технологий.

Также и хорошая парадигма, как и любая другая идеология, дает большой выигрыш на начальном этапе, но становится тормозом развития впоследствии, и языки программирования созданные для реализации концепций одной парадигмы становятся ее заложниками. Человек лучше всего думает на естественном языке, и хороший язык программирования должен давать ему возможность наиболее естественно (т.е. близко к их оригинальной форме) выразить свои мысли о структурах данных и алгоритмах, которые составляют по Вирту все, из чего состоят программы, накладывая при этом как можно меньше ограничений. В языке программирования должны быть равные возможности реализации любой прадигмы, любой концепции, способной зародиться в голове программиста, наиболее простым и элегантным путем. Такой подход лежит в основе Lisp’а, “программируемого языка програмиирования”.

2. Ограниченность языков с жестким синтаксисом



Если говорить по существу, то первая проблема большинства языков — жесткий и часто весьма запутанный и не унифицированный синтаксис, который, если немного перефразировать слоган Perl’а, делает некоторые вещи (которые по мнению создателя языка должны быть простыми) простыми, а остальные — как прийдется. А приходится так, что, большинство концепций, которые не были приняты во внимание при создании языка, требуют огромных трудозатрат для реализацию теми, кто в них нуждается. В качестве примера можно привести такую вездесущую потребность, как передача функций в качестве параметров другим функциям (так называемым, higher-order functions), которую не возможно элегантно реализовать в императивных языках (С или Java). Можно обратиться к функциональным языкам, но и здесь мы видим, что в основу языка заложено множество семантически-нагруженных конструкций, об универсальности которых говорить не приходится.

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

Показательным в этом отношении является шаблон with-..., который используется в Lisp’е в таких задачах, как работа с файлами и потоками, интерфейсами и протоколами обмена информацией. Примером может быть запись данных в файл. Это требует выполнения таких операций, как объявления переменной потока вывода, открытие физического файла на запись, при этом проверки на предмет того, возможно ли это, и выполнения соответсвующих операций в случае ошибок. Также не нужно забывать о необходимости закрыть файл после окончания манипуляций с данными. Все эти операции просты и рутинны, а необходимость их повторного написания много раз приводит к бесполезной трате времени, а также к тому, что файл в конце концов забывают закрыть… В Lisp эта констукция реализуется с помощью макро with-open-file в теле которого выполняются манипуляции с данными. Почему такой подход не применяется в других языках — об этом ниже.

3. Синтаксис Lisp’а



Почему-то распространено мнение, что у Lisp’а сложный синтаксис, впрочем обычно подразумевается другое — зачем все эти скобки??! А вот Lisp-программисты считают, что у Lisp’а вообще нет синтаксиса.

На самом деле, как верно подмечено в комментарии Тима Бредшоу у Lisp’а синтаксис минимальных обязательств. Можно сказать, что он выполняет только лишь функцию необходимой регуляризации английского языка для того, чтобы исключить неоднозначность.

Программы Lisp’а — это текст: есть атомы — символьные идентификаторы — это слова, есть список — атомы, разделенные пробелами и взятые в скобки — это предложение. Lisp Reader4 (можно легко представить себя на его месте) читает этот текст и интерпретирует его в качестве так называемых s-выражений, т.е. подходит к нему с таким набором предпосылок:
  • в любом s-выражении первым идет имя операции, а остальные элементы могут быть s-выражениями либо атомами

  • любой атом является либо именем либо означает сам себя

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

Так зачем все-таки все эти скобки??! Риску начать издалека. Когда люди впервые занялись символьной математикой, они начали с использования простых операций — = + - * > < — которые имели 2 аргумента, т.е. выражения записывались в виде c = а + b. Им сразу же пришлось столкнуться с проблемой порядка операций (что будет, если записать d = a + b * c, и появились скобки, которые вернули в математику однозначность :). Чуть позднее появилась концепция функции, т.е. обощенной операции, которая имеет аргументы и значения. Поскольку многие функции также могут иметь несколько аргументов и даже несколько значений, их пришлось записывать, например, так: f(x, y, z) := (x, y) + (y, z). Здесь уже теряется единообразие: для базовых операций используется инфиксная нотация (т.е. операция записывается между своими аргументами), а для самой функции — по сути дела префиксная (или польская нотация). Что если попытаться свести все к единой нотации: := f x y z + (x y (y z. Не все понятно и однозначно. Убрать неоднозначность можно, добавив скобок: (define f (x y z) (+ (x y) (y z))). Выглядит не совсем привычно, но и изначальная конструкция тоже не была первым, что мы изучили в школе на математике.

4. Метапрограммирование



Что дает нам префиксная запись операций по сравнению, например, с С-подобным синтаксисом? Думаю, кому-то интересно будет узнать, что во внутренних структурах компилятора программа С выглядит так же. Я, конечно, не имею в виду, что там у них тоже скобки :). Но после прохождения сложного синтаксического разбора С-программа преобразуется компилятором в абстрактное синтаксическое дерево (AST). Код Lisp’а — это и есть AST, подавляющая часть которого скомпрессирована в виде MACRO’в (или, как находчиво придумал Conrad Barski — SPEL’ов), записанный прямо у нас перед глазами. И, поскольку мы избавлены от необходимости трансляции кода программы из ее обычного синтаксиса в AST и обратно, открывается возможность для широкого использования MACRO’в: если мы видим повторяющиеся части в дереве, их легко абстрагировать.

упрощенный вариант преобразования AST, выполняемое с помощью MACRO with-open-file

На это можно посмотреть и с другой стороны:
Lisp — это исполняемый XML с более дружественным синтаксисом.[4]


Ведь XML-файл — это тоже по сути дерево данных и метаданных. Только, чтобы избежать круглых скобок, создатели SGML/HTML/XML придумали использовать угловые и записывать название каждого тэга дважды! HTML элементарно преобразуется в подмножество Lisp’a, как показывают многочисленные реализации Lisp’овых библиотек для HTML-генерации (например, CL-WHO или ParenScript, который также реализует JavaScript и CSS). Интересно то, что в Lisp’е HTML-генерация намного естественней заточенного специально под задачу создания динамических веб-страниц PHP, в котором все в конце концов сводится к банальному вызову функции echo.

За счет использования MACRO’в, Lisp позволяет выражать свои мысли в наиболее лаконичной и адекватной форме, а каждый программист имеет способы самостоятельно сформировать набор примитивных конструкций языка, с которыми он будет работать в рамках конкретной проблемы, программы или платформы. Вы можете сказать, как и в большинстве других языков if ... then ... else ..., но также вы можете сказать when … then …, unless ... then ...; кроме того, вы можете сказать condition 1 -> expression 1; ... condition n -> expression n, case ... variant 1: expression1 ... variant n: expression n. И это далеко не полный список, на самом деле, вы можете легко реализовать любую логическую конструкцию и по возможностям использования в программах она ни чем не будет отличаться от обычного if-then-else.

Конкретный пример: в стандарте Lisp есть 3 вида case конструкций, которые имеют такую обобщенную форму:

case-конструкция тестовый элемент
(значение1 форма1)
...
(значениеN формаN)
[(otherwise формаN+1)]


Они отличаются тем, что обычная case-конструкция имеет default clause, а 2 другие: ecase и ccase, — выдают ошибки в случае ненахождения значения тестового элемента в списке возможных. Ограничением этих конструкций является использование для сравнения функции eql (опять же одной из множетсва операций, которые используются для проверки на равенство). Эта функция не работает для строк, поэтому я решил написать собственный вариант case, который бы давал возможность использовать любую функцию сравнения. Например, эта может быть функция, которая проверяет нахождение числа в рамках заданного интервала. Хочу обратить внимание на то, что конструкция gcase объявлена мной совершенно таким же образом, как и оригинальные case-конструкции — а возможность узнать их реализацию, не залезая глубоко в код или описание стандартных библиотек (доступа к которым может и не быть), как это делается в других языках, можно, как правило, просто запустив macroexpand.

(defmacro gcase ((keyform &key (test #'eql)) &body clauses)
"GENERALIZED-CASE -- the difference from simple CASE is that it can
use any given TEST-function. TYPE-ERRORs signaled by TEST-functions are ignored"
(unless (listp clauses) (error "~a -- bad clause in CASE" clauses))
(let ((t-clause? nil))
(when (eql (caar (last clauses)) 'otherwise)
(setf t-clause? t))
`(let ((it ,keyform))
(cond
,@(mapcar #'(lambda (clause)
(if (and t-clause? (eql (car clause) 'otherwise))
`(t ,@(cdr clause))
(w/uniqs (c)
`((handler-case (funcall ,test it ,(car clause))
(type-error (,c) (warn "The value ~a is not of type ~a"
(type-error-datum ,c)
(type-error-expected-type ,c))))
,@(cdr clause)))))
clauses))))) [5]


То же самое касается любых других абстракций. Людям, привыкшим работать в жестко ограниченных языках, не понятно, зачем иметь столько конструкций для циклов: do, dotimes, dolist, loop, …,— к которым могут быть дописаны еще какие-нибудь dotree, domap и т.п. (Хотя многие, наверно, вспомнят, что были рады увидеть, наконец, в C# конструкцию foreach, которой так не хватало в С++). В действии все тот же принцип хорошего стиля — писать все только один раз: во-первых, не нужно копи-пейстить одни и те же шаблоны снова и снова, во-вторых, сразу понятно, какого типа цикл перед нами (а не так, как в С: for (;;)), в-третьих, легко можно уточнить и расширить эти конструкции с помощью инструментов такого же типа (все эти операции циклов — тоже MACRO)...

Почему макро-систем нет в других языках? На самом деле они были и есть: Dylan — это Lisp с С-синтаксисом, metalua и пр. Очевидно, что для поддержки макров язык должен иметь возможность задействовать позднее связывание, но главная проблема остается в другом: разнородности и запутанности синтаксиса — в трудности учета самим программистом всех возможных побочных эффектов “скрытия” каких-то частей кода. Не то, чтобы такая система была не возможна — она, просто, не так практична, потому что появляется необходимость дополнительной трансляции кода между разными представлениями в воображении. А для полноценного использования макров, как показывает даже простой пример вверху, часто не удается ограничиться только одним уровнем вложения макрокода.

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

5. Обещание Lisp’а



Большинство книг и введений в Lisp обещает, что изучив его, программист станет другим, лучшим. Вряд ли это так, потому что никого нельзя изменить при отсутствии собственного желания измениться (и всегда можно будет найти объяснение отсутствию желания, будь то “наличие/отсутствие скобок, переменных, объектов, …”, отсутствие библиотек, необходимость кормить семью…). На самом деле, главное обещание Lisp’а в другом — в том, что человек сможет выражать свои мысли при программировании свободно, в любой принятой им парадигме или вне парадигм без необходимости руководствоваться искуственными ограничениями. Идея, которая стоит за Lisp’ом сегодня — ясность. Но, как и за все остальное, за нее приходится чем-то расплачиваться. И, как по мне, пусть это что-то будет парой лишних скобок... :-)

Сноски:
[1] На самом деле, давайте не будем беспокоиться о Javе. Давайте не жаловаться на Microsoft. Давайте не беспокоиться о них, потому что мы тоже знаем, как программировать компьютеры, и, на самом деле, мы знаем, как делать это мета путем. Мы можем установить альтернативную точку зрения и мы не единственные, кто делает так, как вы хорошо знаете
[2] Есть большая разница между работой с языком, в котором вы должны ждать год, чтобы получить новое выражение для цикла от дизайнера языка и реализатора компилятора — или, когда вы можете сделать это сами за 20 минут
[3] First, we want to establish the idea that a computer language is not just a way of getting a computer to perform operations but rather that it is a novel formal medium for expressing ideas about methodology. Thus, programs must be written for people to read, and only incidentally for machines to execute.
[4] http://www.defmacro.org/ramblings/lisp.html
[5] В этой реализации, конечно, есть не только скобки, но и специфические функции, а также несколько синтаксических элементов (называемых синтаксическим сахаром): ' ` , #' ,@. На самом деле, это практически исчерпывающий список всех подобных элементов в Common Lisp, более того все они имеют свои аналоги в форме (имя ...), т.е., в конце концов, можно ограничиться только скобками и именами.

Дилема зеків на київських дорогах

Написано для: Украинская правда (не опубликовано)
Время написания: январь 2008


Є такий важливий результат теорії ігор — дилема в'язнів.

Коротко, двоє підозрюваних, А і Б, арештовані. У поліції немає достатніх доказів для звинувачення, і ізолювавши їх один від одного, вони пропонують їм одну і ту ж операцію: якщо один свідчить проти іншого, а той зберігає мовчання, то перший звільняється, а другий одержує 10 років в'язниці. Якщо обидва мовчать, у поліції мало доказів, і вони засуджуються до 6 місяців. Якщо обидва свідчать проти один одного, вони одержують по 2 роки. Кожен ув'язнений вибирає, мовчати або свідчити проти іншого. Проте жоден з них не знає точно, що зробить інший. Що відбудеться?

Математично доведено: якщо кожний зі злодіїв буде діяти виключно у своїх (егоїстичних) інтересах, тобто, виражаючись формальною мовою, раціонально, обидва будуть говорити і тому отримають по 2 роки. Чому? Це легко пояснити. Візьмемо одного зі злодіїв. Якщо другий буде говорити, то і йому вигідно те ж саме, бо він отримає тільки 2 роки, а не 10. Якщо ж другий буде мовчати, то і тут вигідно співпрацювати з поліцією, бо замість півроку у в'язниці наш злодій одразу виходить на свободу.

Як бачимо, діючи скоординовано, ці двоє отримали б менший строк, ніж діючи кожний виключно у своїх інтересах. Це пов'язано з тим. що, виражаючись математичною мовою, ця гра — не з нульовою сумою.

Цікаво... А тепер порівняйте це з ситуацією на київських вулицях, особливо під час пробки. Деякі люди, бачачи пробку, думають: "зараз я швиденько прорвуся по порожній зустрічній смузі", "виїду на червоне світло, бо ці бовдури з перпендикулярної вулиці затримались на перехресті більше належного" і так далі. А виходить, що це тіьки погіршує становище. Мабуть, тому, що на кожного хитрого знайдеться ще хитріший, а на кожного зухвалого — ще зухваліший.

Так що, певне — напрошується питання — у більшості киян зеківське світосприйняття? :)

Добре, окрім жартів, чи є вихід з такої ситуації? Ну, як мінімум є 2 виходи: простий і складний. Складний полягає в тому, щоб всім домовитись між собою про певні "правила гри", усні чи письмові, або навіть прийняти закони і створити систему забезпечення їх дотримання.

Є ще простий вихід, який, мабуть, здасться безглуздим хитрому лису, про якого пише Ніко Ланге у своїй статті "Україна не є правовою державою". Справа в тому, що для людей з релігійною (або навіть просто моральною) свідомістю дилеми в'язнів взагалі не існує, бо кожна релігія вчить поводитись по відношенню до оточуючих так, як ви хочете, щоб поводились по відношенню до вас. Це значить, наприклад, пропустити іншого попереду, бо ти хочеш, щоб і тебе пропустили , коли ти поспішаєш; не лізти поперед іншими, бо і тобі не подобається, коли перед тобою влізають... Виявляється, навіть математика говорить, що це, може, й не такий поганий варіант.

2008-07-16

Мысли по поводу ICFPС '08

1. Забавные факты о нашей команде


Изначально наша команда состояла из 4-х человек:
- двоих из Питера: того, кто нас собрал -- Якова Зайцева (lj:iakovz), -- выбравшего Erlang, и его друга с C++
- питонщика из Омска
- меня с Common Lisp
(Более распределенной и полиглотной, наверно, трудно представить. Впрочем, мы считали, что наш основной язык -- Lisp).

В этом составе мы провели тренировку...

Когда началось соревнование, нас было (потенциально) 6: к нам присоединился друг нашего Омского товарища и Эндрю Бейн -- программист на Lisp'е, который искал Lisp'овую команду в списке рассылке.

...А когда мы отправляли, наша готовая программа состояла из 2=х модулей: низкоуровневого на Erlang'е, отвечавшего за маневрирование, написанного Яковом, и моего высокоуровневого для выбора направления. Кроме того, в СКВ была куча C++ кода (не интегрированного с нашим), написанного парнем из Омска, который присоединился последним, в день соревнования. Я не отправлял ее, поскольку мы не знали, что там к чему...

2. Коммуникация


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

Но, что касается нашей пары, с ней тоже произошла забавная история с коммуникацией: в последний вечер мы отлаживали часть Якова во в некотором роде "распределенной" манере: он делал исправление, заводил его в SVN, я забирал его, отправлял через scp на машину с LiveCD, запускал, отлавливал в консоли ошибки, сохранял в текстовый файл, scp назад на свой ноутбук и отправлял Якову по Skype. Она продолжалась, мне кажется, часа 3 и, что интересно, в конце я смог отправить почти работающую программу. :) Это произогло из-за того, что у Якова на работе были какие-то неполадки с интернетом, поэтому он не мог загрузить liveCD, а я оказался настолько тупым, что не подумал запускать клиента по сети...

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

3. Минимальный набор инструментария


который бы понадобился вам в этом году для участия в составе гео-распределенной команды:
* groupchat
* SCM в сети -- GoogleCode доя нас сработал отлично
* отдельный комп для запуска liveCD

4. Задание


Задание этого года -- это как раз такое задание, которое я бы давал студентам в качестве курсовой работы по дисциплине, которая рассматривает основы программирования и алгоритмы. С одной стороны, оно учит большинству умений, необходимых чтобы начать работу в профессиональной группе программистов, а с другой, оно достаточно сложное, чтобы действительно научить чему-то об алгоритмах. Мы также пробовали задание 2004 года -- Муравьев -- оно тоже похоже в этом смысле, но все же больше подходит для курса по компиляторам. В целом, если бы я был преподователем программирования, я бы однозначно использовал материалы всех ICFP соревнований для подобных задач.

5. Чего мне не хватало


Я думаю, для меня наилучшим вариантом было бы участвовать в команде из 4-5 человек, находящихся не более чем в двух разных местах (большая разница во времени между которыми была бы очень на руку), и программирующих не более, чем на 2-х языках, чтобы у каждого был напарник для работы над кодом вместе. И было бы отлично, если бы каждый знал оба языка.

Вывод:


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

P.S. Наш сабмишн.

2008-07-15

ICFPC '08 Thoughts

1. Funny things about our team


Our team consisted initially of 4 people:
- 2 guys in St.Petersburg: the guy, who've brought us together -- Yakov Zaytsev (lj:iakovz) -- choosing Erlang, and his friend with C++
- a pythonista in Omsk (Siberia, Russia)
- me with Common Lisp
(As distributed and polyglot, as you can get. Though, we consider Lisp to be our main language).

We had a training session with this roster...

When the contest began, there were (potentially) 6 of us: we were joined a friend of our Omsk teammate and Andrew Baine -- a Common Lisp hacker, who asked in the mailing list to join a Common Lisp team.

...And when we submitted, our final program consisted of 2 modules: a low-level Erlang one for maneuvering by Yakov and my high-level part for direction setting. Besides in the SCM there was a heap of C++ code (not integrated with ours), written by the Omsk guy, who appeared last. (I hadn't submit it for we didn't know, what was it all about)...

2. Communication


As for me, our main problem was communication (and, to some extend, leadership -- there was no outstanding authority among us to resolve all the disagreements): we couldn't agree on the division of tasks, so everyone virtually did, what he wanted, except for us with Yakov. Andrew tried to participate, but didn't succeed because of poor communication and big time difference, I think. We were Russian-speaking and prepared together and, moreover, he joined, when we've being already far in the discussion late at night. So, we didn't have enough time, to get him involved in the beginning, and on the next day it was even harder to start for him.

But with our pair it was also a funny story with communication: on the final evening we debugged Yakov's part in a somewhat "distributed" manner: he made a correction to his module, committed it to SVN, I checked it out, scp'ed to the machine with LiveCD, ran, caught errors popping in the console, wrote them to text file, scp'ed back to my notebook and sent to Yakov over Skype. This debugging session lasted for some 3 hours I think, and actually, I was able to submit some half-working code in the end. :) Such situation was caused by Yakov being at work with poor Internet connection, so he couldn't get hold of liveCD, and me being stupid enough not to think of running the client over the net...

But, from the technical viewpoint, Skype, actually, is as close to perfect as you can get for communication in such distributed groups.

3. Minimum set of tools


you would have needed for participating this year with a geo-distributed team:
* groupchat
* SCM on the net -- GoogleCode worked perfectly for us
* a separate PC to run liveCD

4. Task


This year's task is just a kind of a task to give students for their term paper in the course, that teaches basic programming and algorithms. From one perspective, it would teach you most of the things you need to know to start working in a professional group of programmers, and from the other it's sufficiently advanced to really learn something about algorithms. We've also tried the 2004's task -- Ants -- it's also similar in this sense, but is more suited for the Compiler course. Generally speaking, if I was a programming professor, I would have definitely used the materials from all the ICFP contests as such kinds of tasks.

5. What I lacked


I think, for me the best case would have been to have a team of 4-5 people, situated in at most 2 places (and big time difference between them would've been really helpful), and programming in at most 2 different langs, so that everyone had a supporting person to work on the code together. And, it would've been great if everyone knew both languages.

Conclusion:


It was really a great experience, which I'd recommend to try at least once to anyone, who is fond of programming.
...And it's best, when you are in a well-prepared team :)

P.S. Our submission.

2008-06-24

ПИФы и экономика украинского фондового рынка

Написано для: журнала Эксперт-Украина (не опубликовано)
Время написания: сентябрь 2007

Некоторые аспекты инвестирования, о которых не все задумываются


Тема украинского фондового рынка (УФР) интересовала меня давно, но его "участником" я стал только в августе этого года, и смог многие моменты, что называется, на собственном опыте прочувствовать намного лучше, чем в то время, когда был лишь сторонним наблюдателем. В этой стат5ье изложены мои субъективные оценки перспектив "инвестирования" в УФР для частных лиц – граждан Украины.

Почему "инвестирования" в кавычках? Среди базовых форм экономической деятельности, которыми являются:
  • создание и/или распределение ценностей (производство, продажа);
  • потребление ценностей (покупка);
  • посредническая деятельность (эксплуатация транзакционной неэффективности экономики);
  • инвестирование (вложение средств/ресурсов для получения большего возврата в будущем)
в настоящий момент многие люди рассматривают инвестирование как "серебряную пулю", способную обеспечить достаточный доход относительно легко и в более короткие сроки (по сравнению с обычной работой, которая является, как правило, первой из форм хозяйственной деятельности). Но нужно учитывать, что как и работа клерка отличается от работы директора предприятия, так и в инвестировании разные его формы требуют разных качеств, разных форм вложений (не только денег, но и времени, усилий, умений и пр.) и дают разную отдачу.

Фактически к инвестированию сейчас принято относить деятельность двух существенно разных направленностей, определяемых тем, каким образом будет получен возврат от вложений:
  • вложение по принципу "купи подешевле – продай подороже";
  • вложение для получения доходов разными порциями в течение долгого промежутка времени1.

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

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

Подход к инвестиционной деятельности двух типов инвесторов настолько отличается, что даже стили их рабочей жизни кардинально разные. Можно сравнить, например, как описывают свою работу двое из величайших инвесторов всех времен по мнению Investopedia.com: директор ПИФа Питер Линч и директор управляющей компании Уоррен Баффет.
Хотя внешне уход мой был неожиданным, решение уйти созрело не за одну ночь. Отслеживать такое количество компаний становилось все тяжелее к середине 80-х, когда индекс Dow Jones перевалил за 2000, а мне исполнилось 43. Управляя портфелем стоимостью, эквивалентной ВНП Эквадора, я почти перестал бывать дома и совсем забросил детей. А они быстро растут. Каждые выходные я с трудом вспоминал, как их зовут. Я проводил больше времени с Fannie Mae, Freddie Mac и Sallie Mae, чем со своими детьми.

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

-- П. Линч, "Переиграть Уолл-стрит"
Что касается распределения капитала — это то, чем мы с Чарли [партнер Баффета] любим заниматься и что приносит нам бесценный опыт. В практическом смысле возраст не играет никакой роли в этом деле: вам не нужна хорошая координация или натренированные мускулы, чтобы таскать мешки с деньгами (слава небесам). До тех пор пока наш мозг будет действовать эффективно, мы с Чарли будем заниматься своей работой так же, как мы занимались ею раньше.
<...>
Сейчас в корпорации Berkshire действует вторая модель управления, и она будет действовать до тех пор, пока я нахожусь в компании. А у меня, позвольте заметить, отменное здоровье. Хорошо это или плохо, но, похоже, еще какое-то время я останусь владельцем и управляющим корпорации.
<...>
Чтобы не заканчивать наше повествование разговором о смерти, я хочу заверить вас, что никогда в жизни не чувствовал себя лучше. Мне нравится управлять Berkshire, а если удовольствие продлевает жизнь, то рекорд Мафусаила может быть побит.

-- У. Баффет, "Эссе об инвестициях, корпоративных финансах и управлении компаниями"

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

С одной стороны, квартира — не акции, и жить где-то нужно. Что ж, при существующих сейчас ценах на аренду и ипотечные кредиты и тенденциях изменения на период 10 лет аренда 1-комнатной квартиры в Киеве обойдется примерно в 60 тысяч долларов на весь период против 210 тысяч при ставке кредита 12%. Со стороны арендодателя тоже не радужная перспектива: инвестирование сейчас в квартиру для последующей сдачи принесет 3-4% годовых, по сравнению с 10-15%, которые дает тот же депозит. И это не говоря о намного больших рисках и требуемых существенных затратах времени и усилий. Т.е. стоимость квартиры должна ежегодно расти даже минимум — на 10%, чтобы компенсировать разрыв с самым простым и безопасным способом вложения. Но есть ли долгосрочные предпосылки для такого роста? Ошибка многих спекулятивных инвесторов в том, что, видя единичный случай необычного роста, они принимают его за нормальный, способный к репродукции в будущем. Это было бы не так плохо, если бы ситуация затрагивала только самих инвесторов, которые получали бы прибыль или расплачивались только за свои решения, но в случае с квартирным рынком такое положение вещей влияет на гораздо больший круг людей...

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

Почему украинские акции (пока) ничего не стоят?

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

  2. Т.н. Emerging Markets (например, Украина), не имеющие долгих традиций рыночной экономики. На первый взгляд, в действии тот же принцип: часть акций корпорации находится в публичных руках и по ним должны выплачиваться дивиденды. И ведь у нас есть много корпораций приносящих большую прибыль, не так ли? Банковская сфера на подъеме, цены на металл высокие, природные монополии тоже, вроде как, могут при грамотном управлении получать большие доходы... Но есть одно но, у всех наших даже условно публичных корпораций есть только один фактический собственник, владеющий порой даже не контрольным пакетом акций. И выходит так, что интересы этого собственника не совпадают с интересами миноритарных акционеров — он не хочет получать процент прибыли предприятия, пропорциональный своему участию в его капитале, и делиться с остальными собственниками. И оказывается, что в нашей среде это не так трудно организовать. Вариантов много: это и использование посреднических структур-трейдеров, которым продукция предприятия продается по себестоимости, и выплата дивидендов только по привилегированным акциям с реинвестированием нераспределенного остатка — ведь отрасль на подъеме. Казалось бы, должны помочь приход в Украину западных компаний и выход украинских предприятий на IPO на мировых финансовых рынках. Поживем-увидим, пока что, во всяком случае, первое не принесло никаких изменений (да и зачем западным компаниям делится прибылями с украинскими акционерами и таким образом уменьшать доходы собственных), а второе еще не набрало достаточных объемов, чтобы можно было делать какие-то выводы.
А в целом, вывод один: в среднесрочной перспективе (как минимум, на ближайшие 5 лет) ожидаемые дивиденды по акциям любой публичной украинской компании, торгующейся на ПФТС, не превышают 5-10% от их цены, а то и вовсе равны 0. Почему же тогда — появляется резонный вопрос — индекс ПФТС вырос за год больше, чем на 100% и находится на отметке 1000 пунктов? Для того, чтобы ответить, нужно разобраться в том, кто является основным участником рынка, кто, выражаясь биржевым языком, его "делает"? А это, по оценкам экспертов 3, на 70% нерезиденты (читай, западные инвест-фонды). Что это за игроки? Как правило, это высокодоходные (что означает высокорискованные) хедж-фонды, в которые вкладывают свои деньги очень богатые американцы и европейцы. Они используют простой метод получения прибыли: вкладывать в рискованные и потенциально высокодоходные финансовые инструменты с большой диверсификацией и хеджированием рисков, которая доступна им благодаря оперированию огромными средствами в глобальном масштабе: и если в каждый из отдельно взятых инструментов или направлений, в которые инвестирует фонд, ни один из них никогда не вложил бы все или даже существенную часть своих средств, то разбросать по 5-10% по различным Emerging Markets, способным принести 50-100% годовых, контролируя риск на основании довольно обоснованных математических выкладок,— это и есть самая суть их подхода4. В таких стратегиях никогда не рассматривается цель долгосрочного инвестирования — они действуют иначе: вложил, подождал год, зафиксировал прибыль или убытки, и пошел дальше — рынок "отыгран".

По сути, УФР сейчас — финансовая пирамида: основные участники рынка оперируют входной и выходной ценой, а не реальной стоимостью акций — это не долгосрочные инвесторы. И так будет продолжаться до тех пор, пока у существенной части компаний на рынке не появится традиции и перспективы дивидендов, ведь пока реальная стоимость их акций фактически равна 0. Для больших мировых игроков это еще одна площадка для вложения избыточных средств, для игры между собой, за ними тянутся маленькие игроки, пытающиеся успеть ухватить кусок с общего пирога. Для украинского инвестора – частного лица — это казино, даже для украинских ПИФов, не имеющих возможности диверсифицировать вложения — это тоже казино, что можно видеть из рис. 1.


Рис. 1 — показывает, что подавляющее большинство украинских открытых ПИФов повторило падение ПФТС, обусловленного августовским ипотечным кризисом в США5

Украинский рынок очень волатильный. Он в первую очередь сильно зависит от мировой конъюнктуры, как показывают августовские события — вследствие кризиса на американском ипотечном рынке индекс ПФТС откатился на 13%, хотя новости от украинских предприятий в основном позитивные — западным фондам просто нужно уменьшать риск своих портфелей. Во-вторых, он зависит от политической ситуации в стране: это неправда, что рынок не отреагировал на кризис с досрочными выборами — он потерял 13% за 2 дня сразу после 2 марта, но быстро восстановил утраченное, когда испуг прошел, и стало ясно, что кардинальных изменений не намечается. Западным инвесторам по большому счету все равно, кто у власти, но они хотят, чтобы риск находился под контролем их расчетов, в которые плохо укладываются катастрофические события. Это легко можно увидеть по графику изменения индекса ПФТС за последние 3 года: бурный рост, начавшийся в конце 2006 стал возможен после того, как в стране наконец сложилась, как казалось на тот момент, достаточно долгосрочная политическая конъюнктура. (Поэтому можно с уверенностью сказать, что, например, до конца этого года индекс тоже не будет расти, пока не сформируется новая коалиция...)


1 кроме того, картина была бы не полной, если не учесть такой вид, как сбережение (в идеале, безрисковое вложение с определенным доходом, превышающим инфляцию), относительно которого всегда оценивается привлекательность рискованных видов инвестирования
2 У.Баффет пишет: "Неограниченная прибыль должна удерживаться, только если есть разумная перспектива, имеющая исторические прецеденты или обусловленная глубоким анализом будущего,— когда каждый доллар, удержанный корпорацией, создаст для владельцев, по меньшей мере, один доллар рыночной стоимости. Это произойдет, если только нераспределенные средства принесут прибыль не меньше той, что обычно доступна инвесторам"
3 http://www.expert.ru/printissues/ukraine/2006/10/13_ukr_fin_market/
4 Подобный финансовый механизм также ипользуется в таких изобретениях страховой индустрии, как перестрахование и секьюритизация катастроф — http://radar.oreilly.com/archives/2007/08/securitizing_ri.html
5 В графике показано:
  • для каждого ПИФа: изменение стоимости условных 1000 гривен средств, вложенных 1 января 2007 г.;
  • для ПФТС: изменение индекса (которое не приводилось к первоначальной базе 1000 пунктов). На 1 января значение индекса составляло примерно 500 пунктов, т.е. процентный рост индекса в таком же масштабе, как и у представленных ПИФов был бы в 2 раза выше. Но, учитывая то, что портфель этих ПИФов только на 40% может состоять из акций, коэффициенты взаимно компенсируются и на рисунке индексы приведены в примерное соответствие. Также нужно учесть тенденцию расхождения индекса и стоимости пая под конец периода из-за получения фондами доходов от вложений в депозиты и облигации