<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-9184507877749766768</id><updated>2012-02-16T13:52:47.185+04:00</updated><title type='text'>Есть табак, да нечем нюхать</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>20</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-2790499941350296517</id><published>2012-01-09T13:31:00.000+04:00</published><updated>2012-01-09T19:54:37.535+04:00</updated><title type='text'>Coders at Work</title><content type='html'>&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;&lt;a href="http://www.codersatwork.com/"&gt;Coders at Work&lt;/a&gt;&amp;nbsp;— коллекция интервью с разными компьютерными отцами, местами довольно интересная.&lt;br /&gt;&lt;hr width="100%"&gt;Jamie Zawinski, разработчик Netscape Navigator, вспоминает о кошмаре отладки на системе с &lt;a href="http://en.wikipedia.org/wiki/Speculative_execution"&gt;упреждающим выполнением команд&lt;/a&gt;:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;I’d gotten to the point where it’s running the executable and it’s trying to bootstrap Lisp and it gets 500 instructions in and crashes. So there I am leaning on the S key, stepping through trying to figure out where it crashes. And it seems to be crashing at a different place each time. And it doesn’t make any sense. I’m reading the assembly output of this architecture I only barely understand. Finally I realize, “Oh my god, it’s doing something different when I step; maybe it’s timing-based.” Eventually I figure out that what’s going on is this is one of the early machines that did speculative execution. It would execute both sides of the branch. And GDB would always take the branch if you single-stepped past a branch instruction. There was a bug in GDB!&lt;/blockquote&gt;перевод:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Я&amp;nbsp;докопался до&amp;nbsp;места, где оно исполняет программу и&amp;nbsp;бутстрапит Лисп и&amp;nbsp;после выполнения 500 инструкций падает. Начинаю долбить клавишу&amp;nbsp;S шаг за&amp;nbsp;шагом, пытаясь вычислить, где оно падает. И&amp;nbsp;так получается, что оно падает в&amp;nbsp;новом месте каждый раз. И&amp;nbsp;совершенно непонятным образом. Читаю ассемблерный код для этой архитектуры, в&amp;nbsp;котором я&amp;nbsp;едва разбираюсь. Это приводит к&amp;nbsp;догадке &amp;laquo;Боже мой, во&amp;nbsp;время прохода по&amp;nbsp;шагам оно работает иначе; может баг зависит от&amp;nbsp;времени&amp;raquo;. В&amp;nbsp;конце концов я&amp;nbsp;понимаю, что это одна из&amp;nbsp;тех ранних машин, на&amp;nbsp;котором выполнение команд было упреждающим. При ветвлении исполнялись обе ветки; а&amp;nbsp;GDB, при пошаговом выполнении, шёл только по&amp;nbsp;одной. Это был баг в&amp;nbsp;GDB!&lt;/blockquote&gt;&lt;hr width="100%"&gt;Brad Fitzpatrick, создатель Livejournal, делится впечатлениями о современных IDE:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;My coworkers try to tell me—if they see me doing something in Emacs—that Eclipse or IntelliJ does it for them automatically. So every six months I try out one of them, Eclipse or IntelliJ. And the damn thing just sits there spinning forever, consuming memory and maybe crashes in the middle of me typing or can’t keep up with me typing. Come on—syntax-highlight in the background or compile in a different thread. Why are you blocking my typing to do this? OK, I’ll try it again in six months, guys. So I’m glad I’m not forced to use that. &lt;/blockquote&gt;перевод: &lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Мои коллеги, когда видят, как я делаю что-то в Emacs, спешат сообщить мне, что в Eclipse и IntelliJ всё это делается автоматически. Каждые полгода я пробую то или другое. И эта хрень только и делает, что без конца шуршит чем-то, расходуя память, и легко может упасть во время набора, или не может угнаться за мной. Неужели так сложно осуществлять подсветку в фоновом режиме и компилировать в отдельном треде. Какой смысл мешать мне печатать? Ладно, чуваки, приходите ещё через полгода. Как я рад, что всё это не является обязаловкой.&lt;/blockquote&gt;&lt;hr width="100%"&gt;Douglas Crockford, создатель ECMAScript 3.1, критически оценивает прогресс ПО:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Progress isn’t always forward. Sometimes we’re leaping forward and sometimes we’re leaping backwards. When we leaped to the PC, we lost a whole lot of stuff. In the timesharing era, we had social systems online. A timesharing system was a marketplace. It was a community and everybody who was a part of that system could exchange email, they could exchange files, they could chat, they could play games. They were doing all this stuff and all that got lost when we went to PCs. It took another 20 years or so to get that back.&lt;/blockquote&gt;перевод:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Прогресс не всегда идёт по нарастающей. Временами мы шагаем вперёд, временами откатываемся назад. Когда мы шагнули к PC, мы кучу всего потеряли. В эру &lt;a href="http://en.wikipedia.org/wiki/Time-sharing"&gt;разделения времени&lt;/a&gt; у нас были социальные сети. В системах разделения времени была своя движуха. Было сообщество пользователей и все, кто были в системе, могли обмениваться письмами, файлами, могли чатиться и играть в игры. Они реально всё это делали и всё это пропало с переходом на PC. Потребовалось лет двадцать, чтобы это вернуть.&lt;/blockquote&gt;&lt;hr width="100%"&gt;Brendan Eich, создатель Javascript вообще, оправдывается за то, что в его детище нет макросов:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;So we were concerned that if we went off to do macros we were doing research, and if we were doing research we were not going to have Microsoft engaged and we were not going to be putting competitive pressure on them. So macros have had to wait.&lt;/blockquote&gt;перевод:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Ну, мы были озабочены тем фактом, что если бы мы ушли в макросы, это бы значило, что мы занимаемся исследовательской работой, а исследовательская работа означала бы, что Microsoft может потерять интерес к делу, что лишило бы нас возможности оказывать на них конкурентное давление. В общем, с макросами решили подождать.&lt;/blockquote&gt;&lt;span style="font-size: x-small;"&gt;(Microsoft, в итоге, одобрила Javascript, а макросы в нём так никогда и не появились.)&lt;/span&gt;&lt;br /&gt;&lt;hr width="100%"&gt;Joshua Bloch, создатель Java Collections, хвастается своим чутьём:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Were real engineers bitching about the lack of generics? I think the unfortunate answer to that question is, no, they weren’t. I think I was guilty of putting in something because it was neat. And because it felt like the right thing to do. That said, a lot of engineering is from the gut. Had people been telling me to put in foreach? No. They hadn’t been telling me to do that either. But I just knew that it was the right thing to do. And I was right—everybody likes it.&lt;/blockquote&gt;перевод: &lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Думаете, обычные программисты обламывались по&amp;nbsp;поводу отсутствия дженериков? Как ни&amp;nbsp;печально, им&amp;nbsp;было по&amp;nbsp;барабану. Я&amp;nbsp;под свою ответственность включил в&amp;nbsp;язык что-то клёвое. Потому что считал, что это как раз&amp;nbsp;то, что надо. Вообще многое в&amp;nbsp;технологии чуется нутром. Думаете, люди меня просили добавить foreach? Нет, они молчали и&amp;nbsp;о&amp;nbsp;нём тоже. Но&amp;nbsp;я&amp;nbsp;словно знал, как правильно. И&amp;nbsp;всем нравится&amp;nbsp;— значит, я&amp;nbsp;был прав.&lt;/blockquote&gt;&lt;hr width="100%"&gt;Joe Armstrong, создатель Erlang, даёт рецепт бутстрапа:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;So then Mike did the virtual machine in C and I did the compiler in Prolog. Then the compiler compiled itself and produced byte-code and you put it in the machine and then we changed the grammar and the syntax and compiled the compiler in itself and came out with an image that would bootstrap and then we’re flying. We’ve lost our Prolog roots and we’re now a language.&lt;/blockquote&gt;перевод:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;И тогда Майк сделал виртуальную машину на C, а я написал компилятор на Прологе. Затем компилятор скомпилировал самого себя и получился байт-код, который мы запустили в машину, а затем мы изменили грамматику и синтаксис и снова скомпилировали компилятор самим собой, и получился образ, который бутстрапился, короче мы взлетели. Забыли свои прологовские корни и сделали новый язык.&lt;/blockquote&gt;&lt;hr width="100%"&gt;Simon Peyton Jones, создатель Haskell, восхищается программной транзакционной памятью:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;A sequential implementation of a double-ended queue is a first-year undergraduate programming problem. For a concurrent implementation with a lock per node, it’s a research paper problem. That is too big a step. It’s absurd for something to be so hard. With transactional memory it’s an undergraduate problem again. You simply wrap “atomic” around the insert and delete operations—job done. That’s amazing, I think.&lt;/blockquote&gt;перевод:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Последовательная реализация двусвязной очереди&amp;nbsp;— это школьная задачка. Параллельная реализация того&amp;nbsp;же самого с&amp;nbsp;поэлементными локами&amp;nbsp;— уже исследовательская проблема. Разница слишком разительна. Не&amp;nbsp;должны быть вещи такими сложными, это бред. А&amp;nbsp;с&amp;nbsp;транзакционной памятью это снова задача для школьника. Просто объявляешь операции вставки и&amp;nbsp;удаления атомарными, и&amp;nbsp;дело сделано. Изумительная штука.&lt;/blockquote&gt;&lt;span style="font-size: x-small;"&gt;(Также он признаётся, что так и не выучил таблицу умножения.)&lt;/span&gt;&lt;br /&gt;&lt;hr width="100%"&gt;Peter Norvig, глава исследовательского отдела Google, раскрывает интересные детали &lt;a href="http://en.wikipedia.org/wiki/Mars_Climate_Orbiter#Communications_loss"&gt;катастрофы&lt;/a&gt; Mars Climate Orbiter (в то время Питер работал в &lt;a href="http://www.nasa.gov/centers/ames/home/index.html"&gt;исследовательском центре Эймса&lt;/a&gt;):&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;It was a joint effort between JPL in Pasadena and Lockheed-Martin in Colorado. There were two people on two different teams and they just weren’t sitting down and having lunch together. I’m convinced that if they had, they would have solved this problem. But instead, one guy sent an email saying, you know, “Something not quite right with these measurements, seems like we’re off by a little bit. It’s not very much, it’s probably OK, but—" [...] During the flight they had chance and chance to catch it. They knew something was wrong and they sent this email but they did not put it into the bug-tracking system. If they had, NASA has very good controls for bug tracking and at later points in the flight somebody would have had to OK it. Instead it was just an informal email that never got an answer back, and JPL said, “Oh, I guess Lockheed-Martin must have solved this problem.” And Lockheed says, “Oh, JPL’s not asking anymore—they must not be concerned.”&lt;/blockquote&gt;перевод:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Это была совместная программа JPL в&amp;nbsp;Пасадене и&amp;nbsp;Lockheed-Martin в&amp;nbsp;Колорадо. Были два человека, один тут, другой там, и&amp;nbsp;им&amp;nbsp;не&amp;nbsp;случалось сидеть в&amp;nbsp;одной комнате, обедать вместе. Если&amp;nbsp;бы не&amp;nbsp;это, я&amp;nbsp;уверен что проблема была&amp;nbsp;бы решена. Один парень послал другому письмо, типа, «что-то тут не&amp;nbsp;так с&amp;nbsp;единицами измерения, мы&amp;nbsp;где-то напутали, не&amp;nbsp;очень сильно, но—» [...] Во&amp;nbsp;время полёта они не&amp;nbsp;раз могли это выяснить. Они точно знали, что что-то не&amp;nbsp;так, и&amp;nbsp;они послали это письмо, но&amp;nbsp;оно не&amp;nbsp;было занесено в&amp;nbsp;баг-трекер. Если&amp;nbsp;бы они это сделали, то&amp;nbsp;благодаря отменным средствам контроля NASA, кто-то обязательно должен был этот баг закрыть. Но&amp;nbsp;вместо этого было лишь одно неформальное письмо, на&amp;nbsp;которое никто не&amp;nbsp;ответил, и&amp;nbsp;в&amp;nbsp;JPL подумали «ага, видимо Lockheed-Martin решили эту проблему». А&amp;nbsp;там подумали «ага, JPL замолчали, значит это их&amp;nbsp;не&amp;nbsp;тревожит».&lt;/blockquote&gt;&lt;span style="font-size: x-small;"&gt;(А ещё он признаётся, что не очень-то читал тома Кнута, но подпирал ими монитор.)&lt;/span&gt;&lt;br /&gt;&lt;hr width="100%"&gt;Guy Steele, один из&amp;nbsp;авторов SICP, отмечает, что случись вьетнамские протесты в&amp;nbsp;Америке чуть раньше&amp;nbsp;— не&amp;nbsp;знать&amp;nbsp;бы ему Лиспа и&amp;nbsp;не&amp;nbsp;работать в&amp;nbsp;MIT в&amp;nbsp;возрасте 16&amp;nbsp;лет:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Then around the beginning of July I heard that Bill Martin at MIT was looking for Lisp programmers. I thought, “Aha, I know Lisp.” I’d hung around MIT so much and had obtained copies of Lisp documentation from the Artificial Intelligence Lab and I would sneak into the labs and play with the computers. The doors were open in those days—the Vietnam protests had not yet happened, which is what caused them to put locks on the doors.&lt;/blockquote&gt;перевод: &lt;br /&gt;&lt;blockquote class="tr_bq"&gt;В&amp;nbsp;начале июля я&amp;nbsp;узнал, что Билл Мартин из MIT ищет программистов на&amp;nbsp;лиспе. Я&amp;nbsp;подумал: «ага, а&amp;nbsp;я&amp;nbsp;знаю лисп». Потому что я&amp;nbsp;постоянно ошивался в&amp;nbsp;MIT, делал копии лисповой документации из&amp;nbsp;лаборатории искуственного интеллекта, тайком проникал в&amp;nbsp;комнаты и&amp;nbsp;игрался с&amp;nbsp;компьютерами. Двери в&amp;nbsp;то&amp;nbsp;время были открыты&amp;nbsp;— вьетнамские протесты, из-за которых им&amp;nbsp;пришлось повесить замки, ещё не&amp;nbsp;случились.&lt;/blockquote&gt;&lt;span style="font-size: x-small;"&gt;(Речь идёт про лето 1971 года; &lt;a href="http://en.wikipedia.org/wiki/Opposition_to_the_U.S._involvement_in_the_Vietnam_War#1971_and_after"&gt;нашествие хиппи на университеты&lt;/a&gt; случилось в апреле 1972.)&lt;/span&gt;&lt;br /&gt;&lt;hr width="100%"&gt;Dan Ingalls, автор первых реализаций Smalltalk, признаётся, что если бы он любил Lisp конца 1960-х, то за Smalltalk он бы не взялся.&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;When I got to Xerox there wasn’t much interactive except the Lisp guys’ stuff. I happened not to be into Lisp. Things would have been different if I were, I expect. [...] I think what I worked on with Alan had that same kind of nice, lively, expression feel, but it included the notion of objects and messages more naturally. I think if I had been as comfortable in a system like Lisp, I never would have bothered. I would have tried working around it to get objects, but starting with the notions of objects from the get-go and then making that nice and interactive and convenient was, I think, a contribution.&lt;/blockquote&gt;перевод: &lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Когда я&amp;nbsp;попал в&amp;nbsp;Xerox, с&amp;nbsp;интерактивными средами было не&amp;nbsp;особо, за&amp;nbsp;исключением хозяйства лисперов. Так вышло, что лисп мне не&amp;nbsp;пришёлся по&amp;nbsp;душе. А&amp;nbsp;так, думаю, всё сложилось&amp;nbsp;бы иначе. [...] То, над чем я&amp;nbsp;работал с&amp;nbsp;Аланом, создавало схожее впечатление живой, выразительной системы, но&amp;nbsp;при этом объекты и&amp;nbsp;сообщения представлялись в&amp;nbsp;ней более естественно. Думаю, если&amp;nbsp;бы меня устраивали лисповые системы, я&amp;nbsp;бы не&amp;nbsp;стал этим заниматься. Я&amp;nbsp;бы скорее навесил на&amp;nbsp;лисп объекты. Но&amp;nbsp;я&amp;nbsp;начал первым делом с&amp;nbsp;объектов, после чего сделал работу с&amp;nbsp;ними приятной, интерактивной и&amp;nbsp;удобной&amp;nbsp;— в&amp;nbsp;этом был мой вклад.&lt;/blockquote&gt;&lt;hr width="100%"&gt;L Peter Deutsch, создатель Ghostscript, даёт урок расставания с надоевшим проектом:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;I basically burned out on Ghostscript. Ghostscript was one of my primary technical interests starting in 1986 and it was pretty much my only major technical project starting somewhere around 1992–’93. By 1998, roughly, I was starting to feel burned out because I was not only doing all the technical work; I was also doing all the support, all the administration. I was a one-person business, and it had gotten to be too much. I hired someone to basically build up a business, and he started hiring engineers. Then it took another two years to find the right person to replace me. And then it took another two years after that to get everything really handed over. By 2002, I had had it. I didn’t want to ever see Ghostscript again.&lt;/blockquote&gt;перевод:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;В сущности, Ghostscript меня вывел из строя. Он был одним из моих главных технических увлечений в 1986 году, и где-то с 1992-93 он стал практически единственным моим большим проектом. Приблизительно в 1998 я стал чувствовать, что выдыхаюсь. Я делал не только всю техническую работу, то и целиком осуществлял поддержку, вёл все дела. Это был бизнес для одного, и он стал слишком тяжёлым. Я нанял человека, чтобы расширить дело, и он стал нанимать программистов. Поиск замены мне занял два года. Ещё два года ушло на то, чтобы передать все полномочия. К 2002 для меня всё было закончено. Я больше не хотел слышать о Ghostscript.&lt;/blockquote&gt;&lt;span style="font-size: x-small;"&gt;(Ещё через полтора года Питер решил вовсе расстаться с программированием, в возрасте 57 лет.)&lt;/span&gt;&lt;br /&gt;&lt;hr width="100%"&gt;Ken Thompson, один из создателей UNIX и Plan 9, раскрывает секрет успеха C++:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Stroustrup campaigned for years and years and years, way beyond any sort of technical contributions he made to the language, to get it adopted and used. And he sort of ran all the standards committees with a whip and a chair. And he said “no” to no one. He put every feature in that language that ever existed. It wasn’t cleanly designed—it was just the union of everything that came along. And I think it suffered drastically from that.&lt;/blockquote&gt;перевод:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Страуструп затеял многолетнюю кампанию, далеко превосходившую всю его техническую работу над языком, ради того, чтобы язык был принят к&amp;nbsp;использованию. Он&amp;nbsp;типа как укротитель с&amp;nbsp;хлыстом, обошёл все комитеты по&amp;nbsp;стандартам и&amp;nbsp; ни единому не&amp;nbsp;сказал &amp;laquo;нет&amp;raquo;. Он&amp;nbsp;все когда-либо предложенные фичи запихнул в&amp;nbsp;язык. В&amp;nbsp;результате получилось нелепое объединение всего подряд. Мне кажется, язык от&amp;nbsp;такого подхода колоссально пострадал.&lt;/blockquote&gt;&lt;hr width="100%"&gt;Fran Allen, первая (из двух) женщина-лауреат премии Тьюринга, сетует на победу низкоуровнего программирования над высокоуровневым:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;The motivation for the design of C was three problems they couldn’t solve in the high-level languages: One of them was interrupt handling. Another was scheduling resources, taking over the machine and scheduling a process that was in the queue. And a third one was allocating memory. And you couldn’t do that from a high-level language. So that was the excuse for C. [...] By 1960, we had a long list of amazing languages: Lisp, APL, Fortran, COBOL, Algol 60. These are higher-level than C. We have seriously regressed, since C developed. C has destroyed our ability to advance the state of the art in automatic optimization, automatic parallelization, automatic mapping of a high-level language to the machine. This is one of the reasons compilers are ... basically not taught much anymore in the colleges and universities.&lt;/blockquote&gt;перевод:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;У истоков создания C стоят три проблемы, которые не могли быть решены в языках высокого уровня. Во-первых, обработка прерываний. Во-вторых, распределение ресурсов: забрав управление у машины, управлять процессом, который стоит в очереди. В-третьих, выделение памяти. Ничего из этого нельзя было сделать в высокоуровневых языках. Это оправдывает появление C. [...] К 1960 году у нас было большое количество замечательных языков: Lisp, APL, COBOL, Algol 60. Все они были более высокоуровневыми, чем C. С развитием C мы сильно откатились назад. C придушил развитие автоматической оптимизации, автоматического распараллеливания, автоматического перевода высокоуровневых команд на машинный язык. Это одна из причин, по которым компиляторы ... практически исчезли из курсов колледжей и университетов.&lt;/blockquote&gt;&lt;span style="font-size: x-small;"&gt;(Данный феномен достаточно хорошо объясняется в статье &amp;laquo;&lt;a href="http://dreamsongs.com/RiseOfWorseIsBetter.html"&gt;The Rise of Worse is Better&lt;/a&gt;&amp;raquo;&lt;/span&gt;&lt;span style="font-size: x-small;"&gt;.)&lt;/span&gt;&lt;br /&gt;&lt;hr width="100%"&gt;Bernie Cosell, разработчик софта для &lt;a href="http://en.wikipedia.org/wiki/Interface_Message_Processor"&gt;первых роутеров&lt;/a&gt;, вспоминает, как его программа DOCTOR (ныне существующая в своей &lt;a href="http://www.emacswiki.org/emacs/EmacsDoctor"&gt;Emacs-реинкарнации&lt;/a&gt;), прошла тест Тьюринга:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;I got a little glimmer of fame because Danny Bobrow wrote up “A Turing Test Passed”. That was one of the first times I actually got some notice for my stupid hacking: I had left Doctor up. And one of the execs at BBN came into the PDP-1 computer room and thought that Danny Bobrow was dialed into that and thought he was talking to Danny. For us folk that had played with ELIZA, we all recognized the responses and we didn’t think about how humanlike they were. But for somebody who wasn’t real familiar with ELIZA, it seemed perfectly reasonable. It was obnoxious but he actually thought it was Danny Bobrow. “But tell me more about—” “Earlier, you said you wanted to go to the client’s place.” Things like that almost made sense in context, until eventually he typed something and he forgot to hit the go button, so the program didn’t respond. And he thought that Danny had disconnected. So he called Danny up at home and yelled at him. And Danny has absolutely no idea what was going on. Except Danny knew about my terminal. So he came in and tore the typescript off of the thing, to save it.&lt;/blockquote&gt;перевод:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;На&amp;nbsp;меня упал тусклый луч славы, когда Дэнни Бобров написал статью «Тест Тьюринга пройден». Это был первый раз, когда я&amp;nbsp;стал известен благодаря своим дурацким занятиям: я&amp;nbsp;оставил Doctor-а включённым, и&amp;nbsp;кто-то из&amp;nbsp;начальства BBN зашёл в&amp;nbsp;комнату с&amp;nbsp;PDP-1 и&amp;nbsp;подумал, что это пишет Дэнни Бобров, и&amp;nbsp;стал с&amp;nbsp;ним общаться. Мы-то, наигравшись с&amp;nbsp;ELIZA, знали все ответы наизусть и&amp;nbsp;не&amp;nbsp;задумывались об&amp;nbsp;их&amp;nbsp;правдоподобности. А&amp;nbsp;для тех, кто об&amp;nbsp;ELIZA не&amp;nbsp;знал, они казались весьма осмысленными. Программа глумилась над ним, а&amp;nbsp;он&amp;nbsp;реально думал, что это Дэнни Бобров. «Давайте поговорим об&amp;nbsp;этом». «Вы&amp;nbsp;говорили, что хотите нанести клиенту визит». Всё шло гладко, до&amp;nbsp;тех пор пока он&amp;nbsp;не&amp;nbsp;забыл нажать кнопку, программа не&amp;nbsp;ответила, и&amp;nbsp;он&amp;nbsp;подумал что Дэнни отсоединился. Он&amp;nbsp;позвонил ему домой и&amp;nbsp;наорал на&amp;nbsp;него. Дэнни сначала не&amp;nbsp;понял, в&amp;nbsp;чём дело, но&amp;nbsp;потом вспомнил про мой терминал, приехал и&amp;nbsp;забрал себе ленту с&amp;nbsp;копией разговора, чтобы не&amp;nbsp;потерялось.&lt;/blockquote&gt;&lt;hr width="100%"&gt;Donald Knuth, автор &lt;a href="http://ru.wikipedia.org/wiki/TeX"&gt;сами&lt;/a&gt; &lt;a href="http://ru.wikipedia.org/wiki/%D0%98%D1%81%D0%BA%D1%83%D1%81%D1%81%D1%82%D0%B2%D0%BE_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F"&gt;знаете&lt;/a&gt; &lt;a href="http://www.literateprogramming.com/"&gt;чего&lt;/a&gt;, отрицает 64-битные указатели:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;Pointers have gone out of favor to the point now where I had to flame about it because on my 64-bit computer that I have here, if I really care about using the capability of my machine I find that I’d better not use pointers because I have a machine that has 64-bit registers but it only has 2 gigabytes of RAM. So a pointer never has more than 32 significant bits to it. But every time I use a pointer it’s costing me 64 bits and that doubles the size of my data structure. Worse, it goes into the cache and half of my cache is gone and that costs cash—cache is expensive. So if I’m really trying to push the envelope now, I have to use arrays instead of pointers. I make complicated macros so that it looks like I’m using pointers, but I’m not really.&lt;/blockquote&gt;перевод:&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;К указателям я сейчас охладел настолько, что не могу не высказаться: у меня здесь 64-битный компьютер, и если исходить из стремления эффективно использовать его ресурсы, то я против указателей, потому что в моей машине 64-битные регистры при всего лишь двух гигабайтах памяти, что означает, что указателям хватило бы и 32 бит. Но каждый указатель стоит мне 64 бит, и это удваивает размер моей структуры данных. Хуже того, оно попадает в кеш, и половина кеша тратится зазря, а кеш это дорогая штука. Так что сейчас я пытаюсь выйти за рамки привычного, используя массивы вместо указателей. Я пишу хитрые макросы, которые позволяют мне использовать как бы указатели, которые в действительности ими не являются.&lt;/blockquote&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-2790499941350296517?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/2790499941350296517/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=2790499941350296517' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/2790499941350296517'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/2790499941350296517'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2012/01/coders-at-work.html' title='Coders at Work'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-3811457447812198668</id><published>2011-12-09T23:54:00.001+04:00</published><updated>2011-12-10T01:14:40.656+04:00</updated><title type='text'>Dynamic linking considered harmful?</title><content type='html'>А&amp;nbsp;оказывается, &lt;a href="http://harmful.cat-v.org/software/dynamic-linking/"&gt;многие известные люди считают&lt;/a&gt;, что динамическое связывание это дурь и&amp;nbsp;блажь. Серьёзное приложение-де &lt;a href="http://blogs.oracle.com/rvs/entry/what_does_dynamic_linking_and"&gt;не&amp;nbsp;может&lt;/a&gt; находиться в&amp;nbsp;зависимости от&amp;nbsp;каких-то там обновляемых системных библиотек. В&amp;nbsp;самом деле, от&amp;nbsp;несанкционированных обновлений может быть не&amp;nbsp;только польза (баг поправят, или там дыру закроют), но&amp;nbsp;и&amp;nbsp;серьёзный вред. При этом, оказывается, исправление бага в&amp;nbsp;системной либе может быть полезно для одних приложений и&amp;nbsp;вредно для других. Получается, и&amp;nbsp;обновлять нельзя, и&amp;nbsp;не&amp;nbsp;обновлять нельзя.&lt;br /&gt;&lt;br /&gt;Про поломку бинарной совместимости и&amp;nbsp;говорить нечего, см. &lt;a href="http://en.wikipedia.org/wiki/DLL_hell"&gt;DLL&amp;nbsp;Hell&lt;/a&gt;. Версионирование библиотек, придуманное для решения этой проблемы, по&amp;nbsp;большому счёту только &lt;a href="http://harmful.cat-v.org/software/dynamic-linking/versioned-symbols"&gt;усугубляет&amp;nbsp;её&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Короче, серьёзные приложения все свои библиотеки носят с&amp;nbsp;собой, и&amp;nbsp;в&amp;nbsp;итоге ни&amp;nbsp;память не&amp;nbsp;экономится, ни&amp;nbsp;место на&amp;nbsp;диске, и&amp;nbsp;сама идея динамически связываемых библиотек превращается в&amp;nbsp;профанацию.&lt;br /&gt;&lt;br /&gt;Статическое связывание, напротив, всё упрощает! Из-за отсутствия необходимости в&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/Position-independent_code"&gt;PIC&lt;/a&gt; код становится много компактнее (что в&amp;nbsp;большей части случаев &lt;a href="http://port70.net/~nsz/32_dynlink.html"&gt;перевешивает&lt;/a&gt; расходы на&amp;nbsp;дублирование кода в&amp;nbsp;приложениях), межмодульные оптимизации становятся возможны, плюс к&amp;nbsp;тому экономятся драгоценные &lt;a href="http://ru.wikipedia.org/wiki/%D0%91%D1%83%D1%84%D0%B5%D1%80_%D0%B0%D1%81%D1%81%D0%BE%D1%86%D0%B8%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B9_%D1%82%D1%80%D0%B0%D0%BD%D1%81%D0%BB%D1%8F%D1%86%D0%B8%D0%B8"&gt;TLB&lt;/a&gt;. При этом ещё не&amp;nbsp;надо забывать о&amp;nbsp;том, что код нескольких копий одного процесса тоже является общим (равно как и&amp;nbsp;код форкнутого процесса с&amp;nbsp;родительским), то&amp;nbsp;есть экономия памяти тоже не&amp;nbsp;очень-то страдает.&lt;br /&gt;&lt;br /&gt;Ещё одна причина, по&amp;nbsp;которой используют динамически связываемые библиотеки&amp;nbsp;&amp;mdash; это разного рода плагины. Но&amp;nbsp;для механизма плагинов этого динамическое связывание не&amp;nbsp;является необходимостью. Достаточно динамической &lt;i&gt;загрузки&lt;/i&gt; кода. Кстати, Windows DLL это как раз и&amp;nbsp;есть динамически загружаемые, а&amp;nbsp;не&amp;nbsp;связываемые,&lt;i&gt; &lt;/i&gt;библиотеки. Динамического связывания в&amp;nbsp;Windows вообще нет. Код DLL&amp;nbsp;не является position-independent.&lt;br /&gt;&lt;br /&gt;Тут&amp;nbsp;бы и&amp;nbsp;подумать, что Windows со&amp;nbsp;своими DLL идёт впереди прогресса, не&amp;nbsp;поддаваясь на&amp;nbsp;провокации. Но&amp;nbsp;нет. Во-первых, само присутствие DLL как отдельных системных файлов порождает проблемы, о&amp;nbsp;которых сказано в&amp;nbsp;первых четырёх абзацах. Почему-то принято завязываться на&amp;nbsp;DLL, а&amp;nbsp;не&amp;nbsp;линковать всё статически. Почему? Неизвестно.&lt;br /&gt;&lt;br /&gt;Во-вторых, в&amp;nbsp;Windows очень капризная libc. Она &lt;a href="http://msdn.microsoft.com/en-us/library/ms235460(VS.80).aspx"&gt;не&amp;nbsp;переносит&lt;/a&gt;, когда память, выделенная в&amp;nbsp;одном экземпляре, освобождается в&amp;nbsp;другом! Из&amp;nbsp;личного опыта скажу, что даже strcpy-ровать её&amp;nbsp;нельзя, хотя по&amp;nbsp;ссылке этого не&amp;nbsp;написано. Что делает статическую линковку с&amp;nbsp;libc бессмысленной в&amp;nbsp;том случае, когда у&amp;nbsp;вам есть более-менее сложные плагины. Приложения и&amp;nbsp;их&amp;nbsp;плагины должны линковаться с&amp;nbsp;динамической libc. И&amp;nbsp;вообще, в&amp;nbsp;Windows &lt;a href="http://shmat-razum.blogspot.com/2010/12/blog-post.html"&gt;море проблем&lt;/a&gt; с&amp;nbsp;написанием плагинов.&lt;br /&gt;&lt;br /&gt;Говорят (по&amp;nbsp;самой первой ссылке) что в&amp;nbsp;plan9 статическая линковка и&amp;nbsp;всё при этом просто и&amp;nbsp;удобно, в&amp;nbsp;том числе с&amp;nbsp;&lt;a href="http://9fans.net/archive/2006/04/508"&gt;плагинами&lt;/a&gt;. Хотя и&amp;nbsp;плагины там &lt;a href="http://9fans.net/archive/2000/06/529"&gt;не&amp;nbsp;очень нужны&lt;/a&gt;, при наличии 9P. Сам не&amp;nbsp;пробовал, подтвердить не&amp;nbsp;могу.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-3811457447812198668?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/3811457447812198668/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=3811457447812198668' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/3811457447812198668'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/3811457447812198668'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2011/12/dynamic-linking-considered-harmful.html' title='Dynamic linking considered harmful?'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-8525112664044119670</id><published>2011-12-02T10:11:00.001+04:00</published><updated>2011-12-02T10:21:21.817+04:00</updated><title type='text'>Из фидов</title><content type='html'>&lt;ol style="text-align: left;"&gt;&lt;li&gt;Иногда &lt;a href="http://www.yosefk.com/blog/graham-coase-when-big-companies-are-a-good-idea.html"&gt;стартапы&lt;/a&gt; — это не так уж и круто, по сравнению с большими компаниями.&lt;/li&gt;&lt;li&gt;Иногда &lt;a href="http://lisp-univ-etc.blogspot.com/2011/11/clojure-complexity.html"&gt;Clojure&lt;/a&gt; — это не так уж и круто, по сравнению с динамическими лиспами.&lt;/li&gt;&lt;/ol&gt;Обе статьи написаны на английском русскоговорящими чуваками.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-8525112664044119670?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/8525112664044119670/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=8525112664044119670' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/8525112664044119670'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/8525112664044119670'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2011/12/blog-post.html' title='Из фидов'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-9055413247363830659</id><published>2011-11-03T02:40:00.001+04:00</published><updated>2011-11-04T10:44:08.895+04:00</updated><title type='text'>Разработка языка программирования на Racket</title><content type='html'>&lt;a href="http://racket-lang.org/" class="underline"&gt;Racket&lt;/a&gt;&amp;nbsp;&amp;mdash; современная платформа для разработки языков программирования, &lt;a href="http://shmat-razum.blogspot.com/2011/09/dsl.html" class="underline"&gt;одна из&amp;nbsp;самых прогрессивных&lt;/a&gt;, наследница Lisp.&lt;br /&gt;&lt;br /&gt;Пара слов о&amp;nbsp;Лиспе: будучи созданным 50&amp;nbsp;лет назад, он&amp;nbsp;живёт и&amp;nbsp;развивается до&amp;nbsp;сих пор, причём последние 20&amp;nbsp;лет практически &lt;a href="http://en.wikipedia.org/wiki/AI_winter" class="underline"&gt;без финансирования&lt;/a&gt;. Что означает, что язык действительно хорош. Надо отметить, что Джон Маккарти (автор термина &amp;laquo;искусственный интеллект&amp;raquo;, AI) изобрёл Лисп как подручное средство для создания этого самого AI. Маккарти умер, AI&amp;nbsp;как не&amp;nbsp;было так и&amp;nbsp;нет, но&amp;nbsp;идеи, заложенные в&amp;nbsp;Лисп, в&amp;nbsp;течение полувека находят применение в&amp;nbsp;совершенно не&amp;nbsp;связанных с&amp;nbsp;AI областях. Что означает, что идеи действительно хороши.&lt;br /&gt;&lt;br /&gt;Цель авторов Racket можно сформулировать так: дать разработчикам возможность создавать свои языки, не&amp;nbsp;лишая их&amp;nbsp;при этом прелестей Лиспа. &amp;laquo;Лисп как основа и&amp;nbsp;любой ваш каприз вдобавок&amp;raquo;&amp;nbsp;&amp;mdash; звучит как предложение, от&amp;nbsp;которого невозможно отказаться. В&amp;nbsp;отличие от&amp;nbsp;Common Lisp, где новые языки как правило создаются на&amp;nbsp;базе грамматики S-выражений, в&amp;nbsp;Racket для них допустима любая грамматика. Плюс к&amp;nbsp;тому, отладчик и&amp;nbsp;редактор с&amp;nbsp;подсветкой синтаксиса&amp;nbsp;&amp;mdash; практически даром. Решительно невозможно отказаться.&lt;br /&gt;&lt;br /&gt;По&amp;nbsp;какой-то причине в&amp;nbsp;сети нет подробного руководства по&amp;nbsp;созданию своего языка в&amp;nbsp;Racket &amp;laquo;от&amp;nbsp;и&amp;nbsp;до&amp;raquo;. Впрочем, уже есть, раз вы&amp;nbsp;его читаете. Оно было собрано по&amp;nbsp;кускам из&amp;nbsp;&lt;a href="https://gist.github.com/1271632/510063ead6a4bab586030ab538639f7bf2a9a014" class="underline"&gt;одного короткого примера&lt;/a&gt;, &lt;a href="http://docs.racket-lang.org/" class="underline"&gt;документации Racket&lt;/a&gt; и&amp;nbsp;кода встроенных в&amp;nbsp;Racket языков (&lt;a href="http://en.wikipedia.org/wiki/Racket_features#Algol" class="underline"&gt;Algol&lt;/a&gt;, &lt;a href="http://docs.racket-lang.org/datalog/" class="underline"&gt;Datalog&lt;/a&gt;). Для руководства такие изыски ни&amp;nbsp;к&amp;nbsp;чему, поэтому мы&amp;nbsp;сейчас сделаем в&amp;nbsp;Racket калькулятор. Типа такого:&lt;br /&gt;&lt;pre&gt;X = 2.8&lt;br /&gt;Y = (X + 1) * 5&lt;br /&gt;print X / Y&lt;/pre&gt;с&amp;nbsp;блекдж... пардон, с&amp;nbsp;пошаговым выполнением, бектрейсами и&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/REPL" class="underline"&gt;REPL&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Вещи, относящиеся к&amp;nbsp;языкам, Racket желает видеть в&amp;nbsp;одной из&amp;nbsp;своих &amp;laquo;системных&amp;raquo; директорий, поэтому лучше с&amp;nbsp;самого начала работать в&amp;nbsp;директории $HOME/.racket/$VERSION/collects/, или создать симлинк из&amp;nbsp;неё в&amp;nbsp;место, где лежат исходники. Пусть язык называется calc, тогда нужно сделать примерно следующее&lt;br /&gt;&lt;pre&gt;mkdir /home/username/calc&lt;br /&gt;ln -s /home/username/calc /home/username/.racket/5.1.3/collects/&lt;/pre&gt;(Кажется, в&amp;nbsp;Windows тоже можно создавать симлинки, начиная с&amp;nbsp;Vista, но&amp;nbsp;я&amp;nbsp;не&amp;nbsp;проверял.)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Этап&amp;nbsp;1: базовый комплект&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Начнём с&amp;nbsp;лексического анализатора, который будет в&amp;nbsp;файле &lt;code&gt;calc/lexer.rkt&lt;/code&gt;:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;#lang racket&lt;br /&gt;&lt;br /&gt;(require&lt;br /&gt;  ; lex -- стандартное средство для построения лексических анализаторов&lt;br /&gt;  parser-tools/lex &lt;br /&gt;  ; Библиотека регулярных выражений для lex. Символы, импортированные из неё,&lt;br /&gt;  ; будут предваряться знаком двоеточия, в целях избежания конфликтов имён&lt;br /&gt;  (prefix-in : parser-tools/lex-sre))&lt;br /&gt;&lt;br /&gt;; Указанные вещи из этого модуля идут на экспорт&lt;br /&gt;(provide value-tokens op-tokens&lt;br /&gt;         position-line position-col position-offset&lt;br /&gt;         calc-lexer)&lt;br /&gt;&lt;br /&gt;; Регулярные выражения для букв, цифр и всяких пробелов&lt;br /&gt;(define-lex-abbrevs&lt;br /&gt;  (lex:letter (:or (:/ #\a #\z) (:/ #\A #\Z)))&lt;br /&gt;  (lex:digit (:/ #\0 #\9))&lt;br /&gt;  (lex:whitespace (:or #\newline #\return #\tab #\space #\vtab)))&lt;br /&gt;&lt;br /&gt;; Токены, имеющие значения -- идентификатор (например X) и число (например 2.8)&lt;br /&gt;(define-tokens value-tokens (IDENTIFIER NUMBER))&lt;br /&gt;&lt;br /&gt;; Токены, не имеющие значений -- арифметические операции, скобки, присваивание,&lt;br /&gt;; операция печати (PRINT) и специальный токен EOF&lt;br /&gt;(define-empty-tokens op-tokens&lt;br /&gt;  (EOF ASSIGN PLUS MINUS MULTIPLY DIVIDE LEFT-PAREN RIGHT-PAREN PRINT))&lt;br /&gt;&lt;br /&gt;; calc-lexer -- это функция, полученная в результате вызова lexer-src-pos&lt;br /&gt;(define calc-lexer&lt;br /&gt;  ; lexer-src-pos отличается от lexer тем, что выдаёт не просто токены,&lt;br /&gt;  ; а position-token -- токены с информацией об их положении в исходном файле&lt;br /&gt;  (lexer-src-pos&lt;br /&gt;   ; пробелы и прочее пропускаем, рекурсивно запуская lexer дальше&lt;br /&gt;   ((:+ lex:whitespace) (return-without-pos (calc-lexer input-port)))&lt;br /&gt;   ; токены операций&lt;br /&gt;   ("=" (token-ASSIGN))&lt;br /&gt;   ("+" (token-PLUS))&lt;br /&gt;   ("-" (token-MINUS))&lt;br /&gt;   ("*" (token-MULTIPLY))&lt;br /&gt;   ("/" (token-DIVIDE))&lt;br /&gt;   ("(" (token-LEFT-PAREN))&lt;br /&gt;   (")" (token-RIGHT-PAREN))&lt;br /&gt;   ("print" (token-PRINT))&lt;br /&gt;   ; идентификатор = буква + [произвольный набор букв и цифр]&lt;br /&gt;   ((:: lex:letter (:* (:or lex:letter lex:digit)))&lt;br /&gt;    (token-IDENTIFIER (string-&amp;gt;symbol lexeme)))&lt;br /&gt;   ; число = [знак] + не менее одной цифры + [точка [с цифрами]]&lt;br /&gt;   ((:: (:? #\-) (:+ lex:digit) (:? (:: #\. (:* lex:digit))))&lt;br /&gt;    (token-NUMBER (string-&amp;gt;number lexeme)))&lt;br /&gt;   ; специальный токен EOF необходимо вернуть при достижении конца файла&lt;br /&gt;   ((eof) 'EOF)))&lt;br /&gt;&lt;/pre&gt;Теперь парсер, &lt;code&gt;calc/parser.rkt&lt;/code&gt;:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;#lang racket&lt;br /&gt;&lt;br /&gt;(require&lt;br /&gt;  ; модуль с генератором парсеров&lt;br /&gt;  parser-tools/yacc&lt;br /&gt;  ; модуль, содержащий нужную нам функцию raise-read-error&lt;br /&gt;  syntax/readerr&lt;br /&gt;  ; наш лексический анализатор&lt;br /&gt;  calc/lexer)&lt;br /&gt;&lt;br /&gt;(provide calc-read-syntax&lt;br /&gt;         calc-read)&lt;br /&gt;&lt;br /&gt;; Функция, которую мы будем вызывать при ошибке парсинга&lt;br /&gt;(define (on-error source-name)&lt;br /&gt;  (lambda (tok-ok? tok-name tok-value start-pos end-pos)&lt;br /&gt;    ; генерирует исключение&lt;br /&gt;    (raise-read-error &lt;br /&gt;             "Parser error" ; текст исключения&lt;br /&gt;             source-name                  ; имя файла&lt;br /&gt;             (position-line start-pos)    ; номер строки с ошибкой&lt;br /&gt;             (position-col start-pos)     ; номер колонки с ошибкой&lt;br /&gt;             (position-offset start-pos)  ; смещение от начала файла&lt;br /&gt;             (- (position-offset end-pos) ; длина фрагмента с ошибкой&lt;br /&gt;                (position-offset start-pos)))))&lt;br /&gt;&lt;br /&gt;; calc-parser -- это функция, возвращающая функцию, полученную&lt;br /&gt;; в результате вызова функции parser из модуля parser-tools/yacc&lt;br /&gt;(define (calc-parser source-name)&lt;br /&gt;  (parser&lt;br /&gt;   (src-po  s)   ; это нужно для того, чтобы в функцию обработки ошибки&lt;br /&gt;                 ; передавалась позиция проблемного куска&lt;br /&gt;   (start start) ; стартовый нетерминал у нас называется start&lt;br /&gt;   (end EOF)     ; конец файла у нас называется EOF&lt;br /&gt;   (tokens value-tokens op-tokens) ; две группы токенов, определённые в calc-lexer&lt;br /&gt;   (error (on-error source-name))  ; функция обработки ошибок (см. выше)&lt;br /&gt;   &lt;br /&gt;   ; Грамматика&lt;br /&gt;&lt;br /&gt;   (grammar&lt;br /&gt;    ; программа = команды&lt;br /&gt;    (start&lt;br /&gt;     ((statements) $1))&lt;br /&gt;    &lt;br /&gt;    (statements&lt;br /&gt;     ; команды = ничто&lt;br /&gt;     (() '())&lt;br /&gt;     ; или команда + команды&lt;br /&gt;     ; $1 и $2 обозначают соответственно первый и второй элементы&lt;br /&gt;     ; сопоставления, то есть statement и statements&lt;br /&gt;     ((statement statements) (list* $1 $2)))&lt;br /&gt;    &lt;br /&gt;    (statement&lt;br /&gt;     ((assignment) $1)&lt;br /&gt;     ((printing) $1))&lt;br /&gt;    &lt;br /&gt;    (constant&lt;br /&gt;     ((NUMBER) $1))&lt;br /&gt;    &lt;br /&gt;    (expression&lt;br /&gt;     ; выражение = терм&lt;br /&gt;     ((term) $1)&lt;br /&gt;     ; или выражение плюс/минус токен.&lt;br /&gt;     ; PLUS и MINUS здесь -- терминалы, взятые из op-tokens из calc/lexer.&lt;br /&gt;     ((expression PLUS term) (list 'plus $1 $3))&lt;br /&gt;     ((expression MINUS term) (list 'minus $1 $3)))&lt;br /&gt;    &lt;br /&gt;    (term&lt;br /&gt;     ((factor) $1)&lt;br /&gt;     ((term MULTIPLY factor) (list 'multiply $1 $3))&lt;br /&gt;     ((term DIVIDE factor) (list 'divide $1 $3)))&lt;br /&gt;    &lt;br /&gt;    (factor&lt;br /&gt;     ((primary-expression) $1)&lt;br /&gt;     ((MINUS primary-expression) (list 'negate $2))&lt;br /&gt;     ((PLUS primary-expression) $2))&lt;br /&gt;    &lt;br /&gt;    (primary-expression&lt;br /&gt;     ((constant) $1)&lt;br /&gt;     ((IDENTIFIER) (list 'value-of $1))&lt;br /&gt;     ((LEFT-PAREN expression RIGHT-PAREN) $2))&lt;br /&gt;    &lt;br /&gt;    (assignment&lt;br /&gt;     ((IDENTIFIER ASSIGN expression) (list 'assign $1 $3)))&lt;br /&gt;    &lt;br /&gt;    (printing&lt;br /&gt;     ((PRINT expression) (list 'print $2))))))&lt;br /&gt;&lt;/pre&gt;Теперь свяжем лексический и&amp;nbsp;синтаксический анализаторы в&amp;nbsp;одно целое с&amp;nbsp;помощью функции, которая принимает некоторый входной поток (в&amp;nbsp;Racket это называется &amp;laquo;порт&amp;raquo;) и&amp;nbsp;соответствующее ему имя файла, и&amp;nbsp;возвращает результат граматического разбора. Правим дальше &lt;code&gt;calc/parser.rkt&lt;/code&gt;:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(define (parse-calc-port port file)&lt;br /&gt;  ; посчитать номера строк заранее&lt;br /&gt;  (port-count-lines! port)&lt;br /&gt;  ; создать и сразу вызвать парсер&lt;br /&gt;  ((calc-parser file)&lt;br /&gt;   ; и передать в него функцию, которая должна выдавать токены один за другим&lt;br /&gt;   (lambda ()&lt;br /&gt;     ; calc-lexer как раз это и делает &lt;br /&gt;     (calc-lexer port))))&lt;br /&gt;&lt;/pre&gt;Поскольку наш анализатор должен будет работать в&amp;nbsp;среде Racket на&amp;nbsp;тех&amp;nbsp;же правах, на&amp;nbsp;которых работает &amp;laquo;родной&amp;raquo; анализатор, мы&amp;nbsp;должны реализовать функции &lt;code&gt;calc-read&lt;/code&gt; и &lt;code&gt;calc-read-syntax&lt;/code&gt;, которые придут на&amp;nbsp;замену &lt;a href="http://docs.racket-lang.org/reference/Reading.html" class="underline"&gt;стандартным&lt;/a&gt; &lt;code&gt;read&lt;/code&gt; и &lt;code&gt;read-syntax&lt;/code&gt;. Окончание &lt;code&gt;calc/parser.rkt&lt;/code&gt;:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(define (calc-read in)&lt;br /&gt;  (syntax-&amp;gt;datum&lt;br /&gt;   (calc-read-syntax #f in)))&lt;br /&gt;&lt;br /&gt;(define (calc-read-syntax source-name input-port)&lt;br /&gt;  (parse-calc-port input-port source-name))&lt;br /&gt;&lt;/pre&gt;Пояснение: в&amp;nbsp;то&amp;nbsp;время как &lt;code&gt;calc-read&lt;/code&gt; возвращает прочитанные данные (datum) вида &lt;code&gt;((assign X (+ 1 2))&lt;/code&gt;, &lt;code&gt;calc-read-syntax&lt;/code&gt; возвращает &lt;a href="http://docs.racket-lang.org/reference/syntax-model.html#(part._stxobj-model)" class="underline"&gt;синтаксические объекты&lt;/a&gt;, что есть по&amp;nbsp;сути те&amp;nbsp;же самые данные, но&amp;nbsp;с&amp;nbsp;привязкой к&amp;nbsp;участкам исходного файла и&amp;nbsp;прочей информацией. В&amp;nbsp;зависимости от&amp;nbsp;режима работы Racket пользуется либо &lt;code&gt;read&lt;/code&gt;, либо &lt;code&gt;read-syntax&lt;/code&gt;, и&amp;nbsp;во&amp;nbsp;избежание неожиданностей лучше сделать их&amp;nbsp;одинаковыми. Что и&amp;nbsp;сделано: &lt;code&gt;calc-read&lt;/code&gt; просто берёт синтаксические объекты из &lt;code&gt;calc-read-syntax&lt;/code&gt; и&amp;nbsp;превращает их&amp;nbsp;в&amp;nbsp;данные с&amp;nbsp;помощью &lt;code&gt;syntax-&amp;gt;datum&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Настало время встроить наш анализатор в&amp;nbsp;среду Racket. Это очень просто: нужно создать файл &lt;code&gt;calc/lang/reader.rkt&lt;/code&gt; следующего содержания:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(module reader syntax/module-reader&lt;br /&gt;  #:language 'racket              ; об этом позже&lt;br /&gt;  #:read calc-read                ; переопределение стандартного read&lt;br /&gt;  #:read-syntax calc-read-syntax  ; переопределение стандартного read-syntax&lt;br /&gt;  #:whole-body-readers? #t ; этот флаг устанавливается в том случае, когда наши&lt;br /&gt;                           ; read и read-syntax всегда читают входной поток до&lt;br /&gt;                           ; конца (calc-read и calc-read-syntax так и делают)&lt;br /&gt;  (require calc/parser))   ; модуль, в котором лежат calc-read и calc-read-syntax&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Racket, увидев &lt;code&gt;#lang calc&lt;/code&gt; в&amp;nbsp;начале файла, будет искать calc/lang/reader.rkt в&amp;nbsp;системных директориях, включая &lt;code&gt;$HOME/.racket/5.1.3/collects/&lt;/code&gt;, где мы&amp;nbsp;всё и&amp;nbsp;делаем. Поэтому мы&amp;nbsp;уже можем начинать писать код на&amp;nbsp;calc в&amp;nbsp;среде Racket. Создадим файл &lt;code&gt;test.calc&lt;/code&gt;:&lt;br /&gt;&lt;pre class="prettyprint lang-python"&gt;#lang calc&lt;br /&gt;X = 3&lt;br /&gt;Y = X + 1&lt;br /&gt;Print X * X + Y * Y&lt;br /&gt;&lt;br /&gt;pi = 3.1415926535&lt;br /&gt;r = 12.2&lt;br /&gt;l = 40&lt;br /&gt;Print pi * r * (2 + l)&lt;/pre&gt;Запускать файл на&amp;nbsp;выполнение ещё рано, т.к. реализации языка пока ещё нет. Но&amp;nbsp;уже можно посмотреть на&amp;nbsp;результат синтаксического анализа кода, для чего в&amp;nbsp;Racket есть удобный инструмент под названием Macro stepper. Нажимаем...&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-BHqJTmEIES0/TrFTN6745mI/AAAAAAAAAGQ/9WT2PK2zucY/s1600/01-syntax-error.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="232" src="http://2.bp.blogspot.com/-BHqJTmEIES0/TrFTN6745mI/AAAAAAAAAGQ/9WT2PK2zucY/s320/01-syntax-error.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;и&amp;nbsp;видим сообщение &amp;laquo;Parser error&amp;raquo; из&amp;nbsp;нашего &lt;code&gt;calc/parser.rkt&lt;/code&gt;. Ошибка состоит в&amp;nbsp;том, что слово Print написано с&amp;nbsp;большой буквы, тогда как лексический анализатор допускает только &amp;laquo;print&amp;raquo; без вариантов. Сделаем его нечувствительным к&amp;nbsp;регистру (&lt;code&gt;calc/lexer.rkt)&lt;/code&gt;:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;; вспомогательная функция, преобразующая строку в выражение, нечувствительное&lt;br /&gt;; к регистру, например "foo" -&amp;gt; (:: (:or #\f #\F) (:or #\o #\O) (:or #\o #\O))&lt;br /&gt;(define-for-syntax (string-&amp;gt;ci-pattern s)&lt;br /&gt;  (cons ':: (map (lambda (c)&lt;br /&gt;                   (list ':or (char-downcase c) (char-upcase c)))&lt;br /&gt;                 (string-&amp;gt;list s))))&lt;br /&gt;&lt;br /&gt;; специализированный макрос для применения в лексическом анализаторе&lt;br /&gt;(define-lex-trans lex-ci&lt;br /&gt;  (lambda (stx)&lt;br /&gt;    (syntax-case stx ()&lt;br /&gt;      ((_ id) ; здесь id -- это строка, которую мы преобразуем, т.е. "print"&lt;br /&gt;       (with-syntax ((result (string-&amp;gt;ci-pattern&lt;br /&gt;                              (syntax-&amp;gt;datum #'id))))&lt;br /&gt;         #'result)))))&lt;br /&gt;&lt;br /&gt;(define calc-lexer&lt;br /&gt;  (lexer-src-pos&lt;br /&gt;   ((:+ lex:whitespace) (return-without-pos (calc-lexer input-port)))&lt;br /&gt;   ("=" (token-ASSIGN))&lt;br /&gt;   ("+" (token-PLUS))&lt;br /&gt;   ("-" (token-MINUS))&lt;br /&gt;   ("*" (token-MULTIPLY))&lt;br /&gt;   ("/" (token-DIVIDE))&lt;br /&gt;   ("(" (token-LEFT-PAREN))&lt;br /&gt;   (")" (token-RIGHT-PAREN))&lt;br /&gt;   &lt;b&gt;((lex-ci "print") (token-PRINT))&lt;/b&gt;&lt;br /&gt;   ((:: lex:letter (:* (:or lex:letter lex:digit)))&lt;br /&gt;    (token-IDENTIFIER (string-&amp;gt;symbol lexeme)))&lt;br /&gt;   ((:: (:? #\-) (:+ lex:digit) (:? (:: #\. (:* lex:digit))))&lt;br /&gt;    (token-NUMBER (string-&amp;gt;number lexeme)))&lt;br /&gt;   ((eof) 'EOF)))&lt;br /&gt;&lt;/pre&gt;Запускаем Macro stepper вторично на &lt;code&gt;test.calc&lt;/code&gt; и видим&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-GUCuZiUzeEU/TrFWIX7bCYI/AAAAAAAAAGY/BF2r4EO7WNE/s1600/02-macro-stepper.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://4.bp.blogspot.com/-GUCuZiUzeEU/TrFWIX7bCYI/AAAAAAAAAGY/BF2r4EO7WNE/s320/02-macro-stepper.png" width="251" /&gt;&lt;/a&gt;&lt;/div&gt;Парсер успешно отработал и выдал в качестве результата набор конструкций вида&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(assign X 3)&lt;br /&gt;(assign Y (plus (value-of X) 1))&lt;br /&gt;&lt;/pre&gt;Что не&amp;nbsp;является валидным кодом на&amp;nbsp;Racket, что подтверждается сообщением &amp;laquo;unbound identifier in&amp;nbsp;module&amp;raquo;. Но&amp;nbsp;по&amp;nbsp;замыслу&amp;nbsp;&amp;mdash; полученный код и&amp;nbsp;не&amp;nbsp;должет являться кодом на&amp;nbsp;Racket! Для исполнения этого кода мы&amp;nbsp;должны создать набор макросов, преобразующих его в&amp;nbsp;код на&amp;nbsp;Racket, а&amp;nbsp;точнее&amp;nbsp;&amp;mdash; в&amp;nbsp;синтаксические объекты. Декларация &lt;code&gt;#:language&lt;/code&gt; в &lt;code&gt;calc/lang/reader.rkt&lt;/code&gt; &amp;mdash; именно об&amp;nbsp;этом. Мы&amp;nbsp;написали &lt;code&gt;#:language 'racket&lt;/code&gt; исключительно потому, что совсем ничего не&amp;nbsp;писать система не&amp;nbsp;позволяет. На&amp;nbsp;самом деле, в этом месте нужно указать путь к модулю с&amp;nbsp;макросами:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(module reader syntax/module-reader&lt;br /&gt;  &lt;b&gt;#:language 'calc/language&lt;/b&gt;&lt;br /&gt;  #:read calc-read&lt;br /&gt;  #:read-syntax calc-read-syntax&lt;br /&gt;  #:whole-body-readers? #t&lt;br /&gt;  #:language-info '#(calc/lang/lang-info get-info #f)&lt;br /&gt;  (require calc/parser))&lt;/pre&gt;каковой модуль мы сейчас и создадим (&lt;code&gt;calc/language.rkt&lt;/code&gt;):&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;#lang racket&lt;br /&gt;&lt;br /&gt;(provide&lt;br /&gt; ; Системные вещи, на которых мы не будем заострять внимание.&lt;br /&gt; ; Достаточно знать, что #%module-begin и #%datum из racket&lt;br /&gt; ; годятся и для calc, поэтому мы переэкспортируем их без изменений&lt;br /&gt; #%module-begin #%datum&lt;br /&gt; ; Макросы для выполнения операций языка calc&lt;br /&gt; assign plus minus divide multiply negate value-of print)&lt;br /&gt;&lt;br /&gt;; Окружение = хеш-таблица со значениями переменных&lt;br /&gt;(define current-env (make-hash))&lt;br /&gt;&lt;br /&gt;; Присваивание переменной = запись в хеш-таблицу&lt;br /&gt;(define-syntax-rule (assign name value)&lt;br /&gt;  ; Обратите внимание на апостроф перед name: он делает из имени переменной,&lt;br /&gt;  ; переданной в макрос, символ. Таким образом, из (assign X 3) получается&lt;br /&gt;  ; (hash-set! current-env 'X 3), в то время как&lt;br /&gt;  ; (hash-set! current-env X 3) выдало бы ошибку&lt;br /&gt;  (hash-set! current-env 'name value))&lt;br /&gt;&lt;br /&gt;; Арифметические операции&lt;br /&gt;&lt;br /&gt;(define-syntax-rule (plus a b)&lt;br /&gt;  (+ a b))&lt;br /&gt;&lt;br /&gt;(define-syntax-rule (minus a b)&lt;br /&gt;  (- a b))&lt;br /&gt;&lt;br /&gt;(define-syntax-rule (divide a b)&lt;br /&gt;  (/ a b))&lt;br /&gt;&lt;br /&gt;(define-syntax-rule (multiply a b)&lt;br /&gt;  (* a b))&lt;br /&gt;&lt;br /&gt;(define-syntax-rule (negate a)&lt;br /&gt;  (- a))&lt;br /&gt;&lt;br /&gt;; Получение значения переменной по имени&lt;br /&gt;(define-syntax-rule (value-of name)&lt;br /&gt;  ; То же самое с апострофом, см. макрос assign&lt;br /&gt;  (hash-ref current-env 'name))&lt;br /&gt;&lt;br /&gt;; Печать&lt;br /&gt;(define-syntax-rule (print value)&lt;br /&gt;  (printf "~v\n" value))&lt;/pre&gt;Теперь можно наконец выполнить (Run) наш &lt;code&gt;test.calc&lt;/code&gt;:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-KVW4_ToSpvM/TrFdqINNnaI/AAAAAAAAAGo/1NZn-ce6Ko0/s1600/03-language.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="233" src="http://4.bp.blogspot.com/-KVW4_ToSpvM/TrFdqINNnaI/AAAAAAAAAGo/1NZn-ce6Ko0/s320/03-language.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;Запустив Macro stepper снова и&amp;nbsp;выключив в&amp;nbsp;нём опцию &amp;laquo;macro hiding&amp;raquo;, мы&amp;nbsp;можем наблюдать, во&amp;nbsp;что в&amp;nbsp;конечном итоге раскрылся наш код:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-KXC-_WRIHQE/TrGWzjkXslI/AAAAAAAAAGw/1FwwnRboWBk/s1600/04-macro-stepper-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/-KXC-_WRIHQE/TrGWzjkXslI/AAAAAAAAAGw/1FwwnRboWBk/s320/04-macro-stepper-2.png" width="186" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;На&amp;nbsp;этом этапе можно считать, что у&amp;nbsp;нас есть парсер и&amp;nbsp;компилятор. Всё это занимает 110&amp;nbsp;строк, не&amp;nbsp;считая пустых строк и&amp;nbsp;комментариев.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Этап 2: необходимые примочки&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Если мы&amp;nbsp;сейчас попробуем запустить отладчик (Debug) и&amp;nbsp;поставить брекпоинт в&amp;nbsp;код программы, нас ждёт неудача. Объясняется это просто: парсер в &lt;code&gt;calc/parser.rkt&lt;/code&gt; возвращает не&amp;nbsp;синтаксические объекты, а&amp;nbsp;простые списки, т.е. datum-ы. Они преобразуются в&amp;nbsp;синтаксические объекты позже, автоматически, но&amp;nbsp;дела это в&amp;nbsp;принципе не&amp;nbsp;меняет, т.к. информации о&amp;nbsp;местоположении синтаксических единиц в&amp;nbsp;исходном коде не&amp;nbsp;появляется. Эта информация известна только парсеру. Он&amp;nbsp;её&amp;nbsp;&amp;laquo;забывает&amp;raquo; везде, кроме функции &lt;code&gt;on-error&lt;/code&gt;. И&amp;nbsp;поставить брекпоинт невозможно, т.к. система просто не&amp;nbsp;знает, какой синтаксический объект к&amp;nbsp;какому месту исходного кода относится. Сейчас мы&amp;nbsp;это исправим (&lt;code&gt;calc/parser.rkt&lt;/code&gt;):&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(define-syntax (build-so stx)&lt;br /&gt;  (syntax-case stx ()&lt;br /&gt;    ((_ value start end)&lt;br /&gt;     ; вытаскиваем из контекста (stx) $i-start-pos и $j-end-pos, где i и j --&lt;br /&gt;     ; числа, переданные в макрос как start и end; а также source-name&lt;br /&gt;     (with-syntax ((start-pos (datum-&amp;gt;syntax&lt;br /&gt;                               stx&lt;br /&gt;                               (string-&amp;gt;symbol &lt;br /&gt;                                (format "$~a-start-pos"&lt;br /&gt;                                        (syntax-&amp;gt;datum #'start)))))&lt;br /&gt;                   (end-pos (datum-&amp;gt;syntax&lt;br /&gt;                             stx&lt;br /&gt;                             (string-&amp;gt;symbol &lt;br /&gt;                              (format "$~a-end-pos"&lt;br /&gt;                                      (syntax-&amp;gt;datum #'end)))))&lt;br /&gt;                   (source (datum-&amp;gt;syntax&lt;br /&gt;                            stx&lt;br /&gt;                            'source-name)))&lt;br /&gt;       (syntax&lt;br /&gt;        (datum-&amp;gt;syntax&lt;br /&gt;         #f&lt;br /&gt;         value&lt;br /&gt;         ; конструируем уже знакомую по on-error пятёрку значений&lt;br /&gt;         (list source &lt;br /&gt;               (position-line start-pos)&lt;br /&gt;               (position-col start-pos)&lt;br /&gt;               (position-offset start-pos)&lt;br /&gt;               (- (position-offset end-pos)&lt;br /&gt;                  (position-offset start-pos)))))))))&lt;br /&gt;&lt;br /&gt;(define (calc-parser source-name)&lt;br /&gt;  (parser&lt;br /&gt;   (src-pos)&lt;br /&gt;   (start start)&lt;br /&gt;   (end EOF)&lt;br /&gt;   (tokens value-tokens op-tokens)&lt;br /&gt;   (error (on-error source-name))&lt;br /&gt;   &lt;br /&gt;   (grammar&lt;br /&gt;    (start&lt;br /&gt;     ; Числа 1 и 1, переданные в макрос build-so, раскрываются соответственно в&lt;br /&gt;     ; $1-start-pos и $1-end-pos, каковые переменные доступны благодаря указанию&lt;br /&gt;     ; (src-pos) выше и содержат начальную и конечную позиции первого нетерминала&lt;br /&gt;     ; в списке (в данном случае он всего один и есть). &lt;br /&gt;     &lt;b&gt;((statements) (build-so $1 1 1)))&lt;/b&gt;&lt;br /&gt;    &lt;br /&gt;    (statements&lt;br /&gt;     (() '())&lt;br /&gt;     ((statement statements) (list* $1 $2)))&lt;br /&gt;    &lt;br /&gt;    (statement&lt;br /&gt;     ((assignment) $1)&lt;br /&gt;     ((printing) $1))&lt;br /&gt;    &lt;br /&gt;    (constant&lt;br /&gt;     ((NUMBER) $1))&lt;br /&gt;    &lt;br /&gt;    (expression&lt;br /&gt;     ((term) $1)&lt;br /&gt;     ; Числа 1 и 3 раскрываются макросом build-so в $1-start-pos и $3-end-pos&lt;br /&gt;     ; соответственно, т.е. от начала первого нетерминала до конца третьего.&lt;br /&gt;     &lt;b&gt;((expression PLUS term) (build-so (list 'plus  $1 $3) 1 3))&lt;/b&gt;&lt;br /&gt;     &lt;b&gt;((expression MINUS term) (build-so (list 'minus $1 $3) 1 3)))&lt;/b&gt;&lt;br /&gt;    &lt;br /&gt;    (term&lt;br /&gt;     ((factor) $1)&lt;br /&gt;     &lt;b&gt;((term MULTIPLY factor) (build-so (list 'multiply $1 $3) 1 3))&lt;/b&gt;&lt;br /&gt;     &lt;b&gt;((term DIVIDE factor) (build-so (list 'divide $1 $3) 1 3)))&lt;/b&gt;&lt;br /&gt;    &lt;br /&gt;    (factor&lt;br /&gt;     ((primary-expression) $1)&lt;br /&gt;     &lt;b&gt;((MINUS primary-expression) (build-so (list 'negate $2) 1 2))&lt;/b&gt;&lt;br /&gt;     &lt;b&gt;((PLUS primary-expression) $2))&lt;/b&gt;&lt;br /&gt;    &lt;br /&gt;    (primary-expression&lt;br /&gt;     ((constant) $1)&lt;br /&gt;     &lt;b&gt;((IDENTIFIER) (build-so (list 'value-of $1) 1 1))&lt;/b&gt;&lt;br /&gt;     &lt;b&gt;((LEFT-PAREN expression RIGHT-PAREN) (build-so $2 1 3)))&lt;/b&gt;&lt;br /&gt;    &lt;br /&gt;    (assignment&lt;br /&gt;     &lt;b&gt;((IDENTIFIER ASSIGN expression) (build-so (list 'assign $1 $3) 1 3)))&lt;/b&gt;&lt;br /&gt;    &lt;br /&gt;    (printing&lt;br /&gt;     &lt;b&gt;((PRINT expression) (build-so (list 'print $2) 1 2))))&lt;/b&gt;))&lt;/pre&gt;И о чудо, теперь работает отладчик, можно ставить брекпоинты, ходить по шагам, даже стек вызовов имеется:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-LXtp5x-6GYk/TrGhMfMkk9I/AAAAAAAAAG4/pMTmjsEmhs8/s1600/05-debugger.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="300" src="http://4.bp.blogspot.com/-LXtp5x-6GYk/TrGhMfMkk9I/AAAAAAAAAG4/pMTmjsEmhs8/s320/05-debugger.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Racket позволяет отладчику заходить в&amp;nbsp;реализацию вашего языка в&amp;nbsp;лисповых функциях, если в&amp;nbsp;момент отладки модули с&amp;nbsp;этими функциями открыты. Но&amp;nbsp;нашего &lt;code&gt;calc/language.rkt&lt;/code&gt; это не&amp;nbsp;касается, поскольку ни&amp;nbsp;одной функции у&amp;nbsp;нас нет&amp;nbsp;&amp;mdash; мы&amp;nbsp;обошлись макросами. То&amp;nbsp;есть ближайший уровень, в&amp;nbsp;который может попасть отладчик после кода на&amp;nbsp;calc&amp;nbsp;&amp;mdash; это реализация &lt;code&gt;hash-set&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt; и&amp;nbsp;прочих примитивных вызовов. На&amp;nbsp;такой уровень нам нет необходимости опускаться.&lt;br /&gt;&lt;br /&gt;Обратим теперь внимание на&amp;nbsp;то, что для языка calc не&amp;nbsp;работает REPL! Об&amp;nbsp;этом честно собщается после выполнения программы (Run) чёрным текстом на&amp;nbsp;жёлтом фоне. REPL не&amp;nbsp;работает потому, что в&amp;nbsp;реализации (&lt;code&gt;calc/language.rkt&lt;/code&gt;) не&amp;nbsp;определён макрос &lt;code&gt;#%top-interaction&lt;/code&gt;, который собственно и&amp;nbsp;должен раскрывать полученные в&amp;nbsp;REPL синтаксические объекты. Стандартный макрос из&amp;nbsp;Racket не&amp;nbsp;годится, так что мы&amp;nbsp;напишем свой, очень простой:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;#lang racket&lt;br /&gt;&lt;br /&gt;(provide&lt;br /&gt; #%module-begin #%datum&lt;br /&gt; ; Переопределять #%top-interaction внутри модуля нельзя, поэтому мы&lt;br /&gt; ; сделаем макрос top-interaction и переименуем его при экспорте.&lt;br /&gt; &lt;b&gt;(rename-out (top-interaction #%top-interaction))&lt;/b&gt;&lt;br /&gt; assign plus minus divide multiply negate value-of print)&lt;br /&gt;&lt;br /&gt;; Макрос действительно такой -- с троеточиями. Это часть синтаксиса.&lt;br /&gt;(define-syntax-rule (top-interaction body ...)&lt;br /&gt;    (begin body ...))&lt;/pre&gt;Но&amp;nbsp;этого недостаточно. Проблема в&amp;nbsp;том, что декларации #:read и #:read-syntax в &lt;code&gt;calc/reader.rkt&lt;/code&gt; по&amp;nbsp;какой-то причине не&amp;nbsp;распространяются на&amp;nbsp;REPL. REPL в&amp;nbsp;DrRacket принадлежит самой DrRacket и&amp;nbsp;подлежит настройке отдельно, по&amp;nbsp;строго определённым правилам. Нужно добавить декларацию #:language-info в &lt;code&gt;calc/reader.rkt&lt;/code&gt;:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(module reader syntax/module-reader&lt;br /&gt;  #:language 'calc/language&lt;br /&gt;  #:read calc-read&lt;br /&gt;  #:read-syntax calc-read-syntax&lt;br /&gt;  #:whole-body-readers? #t&lt;br /&gt;  &lt;b&gt;#:language-info '#(calc/lang/lang-info get-info #f)&lt;/b&gt;&lt;br /&gt;  (require calc/parser))&lt;br /&gt;&lt;/pre&gt;Затем нужно создать модуль &lt;code&gt;calc/lang/lang-info.rkt&lt;/code&gt; такого содержания:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;#lang racket/base&lt;br /&gt;&lt;br /&gt;(provide get-info)&lt;br /&gt;&lt;br /&gt;(define (get-info data)&lt;br /&gt;  (lambda (key default)&lt;br /&gt;    (case key&lt;br /&gt;      ((configure-runtime)&lt;br /&gt;       '(#(calc/lang/configure-runtime configure #f)))&lt;br /&gt;      (else&lt;br /&gt;       default))))&lt;br /&gt;&lt;/pre&gt;Наконец, нужно создать модуль &lt;code&gt;calc/lang/configure-runtime.rkt&lt;/code&gt;, содержащий функцию конфигурации рантайма:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;#lang racket/base&lt;br /&gt;&lt;br /&gt;(require calc/parser)&lt;br /&gt;(provide configure)&lt;br /&gt;&lt;br /&gt;(define (configure data)&lt;br /&gt;  ; Конфигурация заключается в установке &lt;a href="http://docs.racket-lang.org/reference/parameters.html"&gt;параметра&lt;/a&gt; current-read-interaction&lt;br /&gt;  ; (Параметры в Racket -- что-то вроде динамических переменных в Common Lisp.)&lt;br /&gt;  ; Мы меняем этот параметр на нашу функцию, которая вызывает наш парсер.&lt;br /&gt;  (current-read-interaction even-read))&lt;br /&gt;&lt;br /&gt;(define (even-read source-name input-port)&lt;br /&gt;  (begin0&lt;br /&gt;    (parse-calc-port input-port source-name)&lt;br /&gt;    ; Почему-то нужно делать так, чтобы последующий вызов&lt;br /&gt;    ; current-read-interaction вернул EOF. С этой целью производится&lt;br /&gt;    ; нижеследующий трюк с заменой параметра на odd-read. &lt;br /&gt;    (current-read-interaction odd-read)))&lt;br /&gt;&lt;br /&gt;; Вторая часть трюка заключается в замене параметра обратно на even-read.&lt;br /&gt;; Среди разработчиков &lt;a href="http://www.mail-archive.com/dev@racket-lang.org/msg00164.html"&gt;нет согласия&lt;/a&gt; по поводу того, правильно ли так делать;&lt;br /&gt;; оставим этот вопрос на их совести, в любом случае все имеющиеся примеры&lt;br /&gt;; работают именно так.&lt;br /&gt;(define (odd-read src ip)&lt;br /&gt;  (current-read-interaction even-read)&lt;br /&gt;  eof)&lt;br /&gt;&lt;/pre&gt;И вот, у нас работает REPL:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-iS7stwmMuEc/TrGpZw8uoCI/AAAAAAAAAHA/ptJlCTo60Gg/s1600/06-repl.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="266" src="http://1.bp.blogspot.com/-iS7stwmMuEc/TrGpZw8uoCI/AAAAAAAAAHA/ptJlCTo60Gg/s320/06-repl.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Благодаря умной работе механизма модулей, два и более открытых одновременно окна с программами на calc не конфликтуют и не засоряют окружение друг друга:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-xej3AoAB86g/TrGqGW37LnI/AAAAAAAAAHI/V-P8CVMZQ6c/s1600/07-two-repls.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-xej3AoAB86g/TrGqGW37LnI/AAAAAAAAAHI/V-P8CVMZQ6c/s320/07-two-repls.png" width="275" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Самое время заметить, что ошибки с&amp;nbsp;необъявленными переменными возникают на&amp;nbsp;этапе выполнения программы, тогда как во&amp;nbsp;всех нормальных средах они должны отлавливаться на&amp;nbsp;этапе компиляции. Полноценный компилятор мы&amp;nbsp;писать не&amp;nbsp;будем, ограничимся проверкой того, что все используемые переменные встретились перед использованием в&amp;nbsp;левой части. Файл &lt;code&gt;calc/compiler.rkt&lt;/code&gt;:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;#lang racket&lt;br /&gt;&lt;br /&gt;(provide compile-program&lt;br /&gt;         compile-statement)&lt;br /&gt;&lt;br /&gt;; Хеш-таблица имён переменных&lt;br /&gt;; (не путать с current-env в language.rkt, это разные этапы)&lt;br /&gt;(define variables (make-hash))&lt;br /&gt;&lt;br /&gt;(define (compile-program p)&lt;br /&gt;  ; проверить все подвыражения (syntax-e раскрывает синтаксический&lt;br /&gt;  ; объект-список в список синтаксических объектов)&lt;br /&gt;  (for-each check-syntax (syntax-e p))&lt;br /&gt;  ; вернуть исходную синтаксическую конструкцию как результат&lt;br /&gt;  ; (приличный компилятор вернул бы новую, оптимизированную конструкцию)&lt;br /&gt;  p)&lt;br /&gt;&lt;br /&gt;(define (compile-statement s)&lt;br /&gt;  (check-syntax s)&lt;br /&gt;  s)&lt;br /&gt;&lt;br /&gt;(define (check-syntax s)&lt;br /&gt;  ; Что за объект? Преобразуем в данные и посмотрим.&lt;br /&gt;  (match (syntax-&amp;gt;datum s)&lt;br /&gt;    ; Команда присваивания&lt;br /&gt;    ((list 'assign a b)&lt;br /&gt;     ; Сначала проверим правую часть, чтобы поймать ошибки вида X = X&lt;br /&gt;     (check-syntax (third (syntax-e s)))&lt;br /&gt;     ; Теперь занесём левую часть в словарь&lt;br /&gt;     (hash-set! variables a a))&lt;br /&gt;    ; Доступ к переменной&lt;br /&gt;    ((list 'value-of a)&lt;br /&gt;     ; Есть ли она в словаре?&lt;br /&gt;     (unless (hash-has-key? variables a)&lt;br /&gt;       ; Если нет -- ошибка со ссылкой на соответствующий синтаксический объект&lt;br /&gt;       (raise-syntax-error&lt;br /&gt;        #f "access to unassigned variable" (second (syntax-e s)))))&lt;br /&gt;    ; Все прочие команды&lt;br /&gt;    ((list x ...)&lt;br /&gt;     ; проверяются рекурсивно&lt;br /&gt;     (for-each check-syntax (syntax-e s)))&lt;br /&gt;    ; Константы не подлежат проверке&lt;br /&gt;    (_ #f)))&lt;br /&gt;&lt;/pre&gt;Попросим парсер вызывать "компилятор" перед возвращением результата (&lt;code&gt;calc/parser.lisp&lt;/code&gt;):&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(require parser-tools/yacc&lt;br /&gt;         syntax/readerr&lt;br /&gt;         calc/lexer&lt;br /&gt;         &lt;b&gt;calc/compiler&lt;/b&gt;)&lt;br /&gt;&lt;br /&gt;(define (calc-read-syntax source-name input-port)&lt;br /&gt;  &lt;b&gt;(compile-program&lt;/b&gt;&lt;br /&gt;   (parse-calc-port input-port source-name)))&lt;br /&gt;&lt;/pre&gt;Насладимся результатом:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-zskhpVpgdsI/TrGvZSGE4KI/AAAAAAAAAHQ/jRnR3_qzuEs/s1600/08-compiler.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="266" src="http://2.bp.blogspot.com/-zskhpVpgdsI/TrGvZSGE4KI/AAAAAAAAAHQ/jRnR3_qzuEs/s320/08-compiler.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Попросим REPL делать то же самое (&lt;code&gt;calc/lang/configure-runtime.rkt&lt;/code&gt;):&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(require calc/parser&lt;br /&gt;         &lt;b&gt;calc/compiler&lt;/b&gt;)&lt;br /&gt;&lt;br /&gt;(define (even-read source-name input-port)&lt;br /&gt;  (begin0&lt;br /&gt;    &lt;b&gt;(compile-statement (parse-calc-port input-port source-name))&lt;/b&gt;&lt;br /&gt;    (current-read-interaction odd-read)))&lt;br /&gt;&lt;/pre&gt;Насладимся результатом и здесь:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-XHLRZda_8Qs/TrGvcT6OtnI/AAAAAAAAAHY/IX-dl7kmpyA/s1600/09-compiler-repl.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="266" src="http://3.bp.blogspot.com/-XHLRZda_8Qs/TrGvcT6OtnI/AAAAAAAAAHY/IX-dl7kmpyA/s320/09-compiler-repl.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Отметим, что ошибки времени выполнения всё же случаются, и снабжены бектрейсом:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-dcmtELuVDJM/TrGwMWmhc0I/AAAAAAAAAHg/nEnoFKwIkwE/s1600/10-runtime.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="258" src="http://2.bp.blogspot.com/-dcmtELuVDJM/TrGwMWmhc0I/AAAAAAAAAHg/nEnoFKwIkwE/s320/10-runtime.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;На данный момент у нас есть всё необходимое, а написали мы всего 208 строк кода.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Этап 3: элементы роскоши&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Такая беда: REPL не позволяет переводить строку. Нажатие Enter ведёт к немедленному отправлению строки на синтаксический анализ. Мы собирались дописать команду на следующей строке, но REPL безжалостен.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-CrbDECvUync/TrGybSOc4sI/AAAAAAAAAHo/-IpugZSKzbI/s1600/11-submit-error.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="258" src="http://3.bp.blogspot.com/-CrbDECvUync/TrGybSOc4sI/AAAAAAAAAHo/-IpugZSKzbI/s320/11-submit-error.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;Хорошая новость: поведение REPL можно настроить. Открываем снова &lt;code&gt;calc/lang/lang-info.rkt&lt;/code&gt;:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(define (get-info data)&lt;br /&gt;  (lambda (key default)&lt;br /&gt;    (case key&lt;br /&gt;      ((configure-runtime)&lt;br /&gt;       '(#(calc/lang/configure-runtime configure #f)))&lt;br /&gt;      &lt;b&gt;((drracket:submit-predicate)&lt;br /&gt;       (dynamic-require 'calc/tool/submit 'repl-submit?))&lt;/b&gt;&lt;br /&gt;      (else&lt;br /&gt;       default))))&lt;br /&gt;&lt;/pre&gt;и&amp;nbsp;создаём модуль &lt;code&gt;calc/tool/submit.rkt&lt;/code&gt; с&amp;nbsp;функцией &lt;code&gt;repl-submit?&lt;/code&gt;, которая возвращает &lt;code&gt;#t&lt;/code&gt;, если перевод строки завершающий, и &lt;code&gt;#f&lt;/code&gt;, если промежуточный. Определить это не&amp;nbsp;так просто, учитывая, что пользователь может вводить в&amp;nbsp;REPL всё, что ему заблагорассудится. Будем считать, что строка готова к&amp;nbsp;синтаксическому анализу, если она не&amp;nbsp;пустая, не&amp;nbsp;завершается оператором (&lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;print&lt;/code&gt;) и&amp;nbsp;если скобки в&amp;nbsp;ней сбалансированы. Весьма уместно использовать уже готовый лексический анализатор для выполнения этой задачи:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;#lang racket&lt;br /&gt;&lt;br /&gt;(require calc/lexer&lt;br /&gt;         parser-tools/lex)&lt;br /&gt;&lt;br /&gt;(provide repl-submit?)&lt;br /&gt;&lt;br /&gt;(define (repl-submit? ip has-white-space?)&lt;br /&gt;  (let loop ((blank? #t)      ; строка пустая?     &lt;br /&gt;             (pending-op? #f) ; строка завершается оператором?&lt;br /&gt;             (paren-count 0)) ; баланс скобок&lt;br /&gt;    ; Все ошибки лексического анализатора мы обязаны пропускать&lt;br /&gt;    (with-handlers ((exn:fail:read?&lt;br /&gt;                     (lambda (e)&lt;br /&gt;                       #t)))&lt;br /&gt;      (let ((token (position-token-token (calc-lexer ip))))&lt;br /&gt;        (case token&lt;br /&gt;          ((EOF)&lt;br /&gt;           (and (zero? paren-count)&lt;br /&gt;                (not blank?)&lt;br /&gt;                (not pending-op?)))&lt;br /&gt;          ((PLUS MINUS MULTIPLY DIVIDE ASSIGN PRINT)&lt;br /&gt;           (loop #f #t paren-count))&lt;br /&gt;          ((LEFT-PAREN)&lt;br /&gt;           (loop #f #f (+ paren-count 1)))&lt;br /&gt;          ((RIGHT-PAREN)&lt;br /&gt;           (loop #f #f (- paren-count 1)))&lt;br /&gt;          (else&lt;br /&gt;           (loop #f #f paren-count)))))))&lt;br /&gt;&lt;/pre&gt;Вроде работает:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-gUmFmlQ3bdA/TrG1dJej8kI/AAAAAAAAAHw/mh8D1EOfl6s/s1600/12-submit-ok.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="258" src="http://1.bp.blogspot.com/-gUmFmlQ3bdA/TrG1dJej8kI/AAAAAAAAAHw/mh8D1EOfl6s/s320/12-submit-ok.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Почти всё. Осталось только добавить подсветку синтаксиса. Ну&amp;nbsp;и, например, комментарии, чтобы было что подсвечивать. Пусть комментарии будут в&amp;nbsp;стиле bash&amp;nbsp;&amp;mdash; от&amp;nbsp;символа &amp;laquo;#&amp;raquo; до&amp;nbsp;конца строки. Обновляем &lt;code&gt;calc/lexer.rkt&lt;/code&gt;, попутно экспортируя из&amp;nbsp;него некоторые вещи, которые скоро понадобятся:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(provide value-tokens op-tokens&lt;br /&gt;         position-line position-col position-offset&lt;br /&gt;         calc-lexer&lt;br /&gt;         &lt;b&gt;lex:comment lex:identifier lex:number lex-ci&lt;/b&gt;)&lt;br /&gt;&lt;br /&gt;(define-lex-abbrevs&lt;br /&gt;  (lex:letter (:or (:/ #\a #\z) (:/ #\A #\Z)))&lt;br /&gt;  (lex:digit (:/ #\0 #\9))&lt;br /&gt;  &lt;b&gt;(lex:comment (:: "#" (:* (:: (char-complement #\newline))) (:? #\newline)))&lt;/b&gt;&lt;br /&gt;  (lex:whitespace (:or #\newline #\return #\tab #\space #\vtab))&lt;br /&gt;  &lt;b&gt;(lex:identifier (:: lex:letter (:* (:or lex:letter lex:digit))))&lt;/b&gt;&lt;br /&gt;  &lt;b&gt;(lex:number (:: (:? #\-) (:+ lex:digit) (:? (:: #\. (:* lex:digit))))&lt;/b&gt;))&lt;br /&gt;&lt;br /&gt;(define calc-lexer&lt;br /&gt;  (lexer-src-pos&lt;br /&gt;   ((:+ lex:whitespace) (return-without-pos (calc-lexer input-port)))&lt;br /&gt;   ; комментарии пропускаем так же, как и пробелы&lt;br /&gt;   &lt;b&gt;((:+ lex:comment) (return-without-pos (calc-lexer input-port)))&lt;/b&gt;&lt;br /&gt;   ("=" (token-ASSIGN))&lt;br /&gt;   ("+" (token-PLUS))&lt;br /&gt;   ("-" (token-MINUS))&lt;br /&gt;   ("*" (token-MULTIPLY))&lt;br /&gt;   ("/" (token-DIVIDE))&lt;br /&gt;   ("(" (token-LEFT-PAREN))&lt;br /&gt;   (")" (token-RIGHT-PAREN))&lt;br /&gt;   ((lex-ci "print") (token-PRINT))&lt;br /&gt;   &lt;b&gt;(lex:identifier (token-IDENTIFIER (string-&amp;gt;symbol lexeme)))&lt;/b&gt;&lt;br /&gt;   &lt;b&gt;(lex:number (token-NUMBER (string-&amp;gt;number lexeme)))&lt;/b&gt;&lt;br /&gt;   ((eof) 'EOF)))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Информация о подсветке синтаксиса задаётся определённым образом в директиве &lt;code&gt;#:info&lt;/code&gt; файла &lt;code&gt;calc/lang/reader.rkt:&lt;/code&gt;&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;(module reader syntax/module-reader&lt;br /&gt;  #:language 'calc/language&lt;br /&gt;  #:read calc-read&lt;br /&gt;  #:read-syntax calc-read-syntax&lt;br /&gt;  #:whole-body-readers? #t&lt;br /&gt;  #:language-info '#(calc/lang/lang-info get-info #f)&lt;br /&gt;  &lt;b&gt;#:info (lambda (key defval default)&lt;br /&gt;           (case key&lt;br /&gt;             ((color-lexer)&lt;br /&gt;              (dynamic-require 'calc/tool/syntax-color 'get-syntax-token))&lt;br /&gt;             (else (default key defval)))&lt;/b&gt;)&lt;br /&gt;  (require calc/parser))&lt;br /&gt;&lt;/pre&gt;Собственно подсветку осуществляет отдельный модуль &lt;code&gt;calc/tool/syntax-color.rkt&lt;/code&gt;. В&amp;nbsp;нём находится, по&amp;nbsp;сути, ещё один лексический анализатор, но&amp;nbsp;такой, который выдаёт не&amp;nbsp;токены, а&amp;nbsp;особые &amp;laquo;пятёрки&amp;raquo; значений: лексема, её&amp;nbsp;тип (комментарий/константа/ключевое слово/символ/etc), &amp;laquo;скобочность&amp;raquo;, а&amp;nbsp;также начало и&amp;nbsp;конец лексемы в&amp;nbsp;исходном файле:&lt;br /&gt;&lt;pre class="prettyprint lang-lisp"&gt;#lang racket&lt;br /&gt;&lt;br /&gt;(require parser-tools/lex&lt;br /&gt;         (prefix-in : parser-tools/lex-sre)&lt;br /&gt;         calc/lexer)&lt;br /&gt;&lt;br /&gt;(provide get-syntax-token)&lt;br /&gt;&lt;br /&gt;(define (syn-val lexeme type paren start end)&lt;br /&gt;  (values lexeme type paren (position-offset start) (position-offset end)))&lt;br /&gt;&lt;br /&gt;(define get-syntax-token&lt;br /&gt;  (lexer&lt;br /&gt;   ((:+ whitespace)&lt;br /&gt;    (syn-val lexeme 'whitespace #f start-pos end-pos))&lt;br /&gt;   (lex:comment&lt;br /&gt;    (syn-val lexeme 'comment #f start-pos end-pos))&lt;br /&gt;   (lex:number&lt;br /&gt;    (syn-val lexeme 'constant #f start-pos end-pos))&lt;br /&gt;   ; "Print" у нас будет единственным ключевым словом&lt;br /&gt;   ((lex-ci "print")&lt;br /&gt;    (syn-val lexeme 'keyword #f start-pos end-pos))&lt;br /&gt;   ; Имена переменных у нас будут идентфикаторами&lt;br /&gt;   (lex:identifier&lt;br /&gt;    (syn-val lexeme 'symbol #f start-pos end-pos))&lt;br /&gt;   ; Арифметические операции и "=" будут считаться за скобки&lt;br /&gt;   ; (Операций кроме скобок Racket, похоже, не знает)&lt;br /&gt;   ((:or #\+ #\- #\/ #\* #\=)&lt;br /&gt;    (syn-val lexeme 'parenthesis #f start-pos end-pos))&lt;br /&gt;   ; Сами скобки тоже считаются за скобки, и вдобавок к тому обладают свойством&lt;br /&gt;   ; "скобочности", чтобы редактор Racket подсвечивал открывающие и закрывающие,&lt;br /&gt;   ; опять же, скобки.&lt;br /&gt;   (#\( (syn-val lexeme 'parenthesis '|(| start-pos end-pos))&lt;br /&gt;   (#\) (syn-val lexeme 'parenthesis '|)| start-pos end-pos))&lt;br /&gt;   ((eof) (syn-val lexeme 'eof #f start-pos end-pos))&lt;br /&gt;   (any-char (syn-val lexeme 'error #f start-pos end-pos))))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Внимание: чтобы подсветка синтаксиса заработала, надо перезапустить Racket! Если вдуматься, это значит, что при выполнении всех предыдущих операций Racket не&amp;nbsp;надо было перезапускать! И&amp;nbsp;даже test.calc можно было не&amp;nbsp;закрывать. При том, что мы&amp;nbsp;там &amp;laquo;резали по&amp;nbsp;живому&amp;raquo; во&amp;nbsp;многих местах. Racket истинно динамическая платформа. Вот что получится после перезапуска:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-B7YDc8oyok0/TrG6zRYeEzI/AAAAAAAAAH4/3B-vjY2Nydo/s1600/13-colors.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="308" src="http://4.bp.blogspot.com/-B7YDc8oyok0/TrG6zRYeEzI/AAAAAAAAAH4/3B-vjY2Nydo/s320/13-colors.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Подсветка синтаксиса в&amp;nbsp;действии. В&amp;nbsp;REPL, правда, она не&amp;nbsp;работает (то&amp;nbsp;есть работает, но&amp;nbsp;не&amp;nbsp;наша, а&amp;nbsp;стандартная), но&amp;nbsp;не&amp;nbsp;будем придираться к&amp;nbsp;мелочам. Racket&amp;nbsp;&amp;mdash; прекрасная вещь. Всё вышеописанное мы&amp;nbsp;сделали, написав 268 строк кода. То, что получилось, можно скачать &lt;a href="https://github.com/kugelblitz/calc"&gt;здесь&lt;/a&gt;. Вообще-то можно ещё сделать так, чтобы язык calc включался без &lt;code&gt;#lang calc&lt;/code&gt;, и&amp;nbsp;ещё можно научить DrRacket смотреть переменные в&amp;nbsp;отладчике, и&amp;nbsp;ещё можно собрать DrRacket, кастомизированный под calc и&amp;nbsp;готовый к&amp;nbsp;распространению, но&amp;nbsp;обо всём этом как-нибудь в&amp;nbsp;другой раз.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-9055413247363830659?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/9055413247363830659/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=9055413247363830659' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/9055413247363830659'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/9055413247363830659'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2011/11/racket.html' title='Разработка языка программирования на Racket'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-BHqJTmEIES0/TrFTN6745mI/AAAAAAAAAGQ/9WT2PK2zucY/s72-c/01-syntax-error.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-3864408041400861951</id><published>2011-09-27T12:12:00.000+04:00</published><updated>2011-09-27T12:12:22.016+04:00</updated><title type='text'>Как создавать DSL</title><content type='html'>Необходимость создания предметно-ориентированных языков (domain-specific languages, DSL) может быть продиктована разными причинами. Например, появлением новой технологической ниши, как было с&amp;nbsp;HTML и&amp;nbsp;SQL. Или крайней неуклюжестью давно занявших свои ниши языков общего назначения... не&amp;nbsp;будем их&amp;nbsp;называть. Ещё бывают причины чисто исторические. Неважно. Пусть вам пришла в&amp;nbsp;голову идея сделать DSL. Сочинили вы&amp;nbsp;язык, описали грамматику, продумали семантику, а&amp;nbsp;дальше что? Как получить транслятор, отладчик, где взять удобную среду для написания кода на&amp;nbsp;этом DSL? Не&amp;nbsp;самому&amp;nbsp;же всё делать с&amp;nbsp;нуля. Вот об&amp;nbsp;этом и&amp;nbsp;речь.&lt;br /&gt;&lt;br /&gt;Прежде всего надо определиться: DSL или EDSL? Второй вариант, который расшифровывается как «встроенный предметно-ориентированный язык» (embedded DSL, или internal DSL), сделает вашу жизнь значительно легче. Вы&amp;nbsp;выбираете язык общего назначения, для которого все средства разработки уже сделаны до&amp;nbsp;вас, и&amp;nbsp;создаёте свой язык как надстройку над первым, оставаясь в&amp;nbsp;рамках его грамматики. Фактически, вы&amp;nbsp;делаете библиотеку. Само собой, если изначальный язык (хост-язык) недостаточно гибок в&amp;nbsp;плане грамматических конструкций, то&amp;nbsp;и&amp;nbsp;от&amp;nbsp;DSL особенного удобства ждать не&amp;nbsp;приходится. Тем не&amp;nbsp;менее, можно на&amp;nbsp;&lt;a href="http://www.khelll.com/blog/ruby/ruby-and-internal-dsls/"&gt;Ruby&lt;/a&gt;, &lt;a href="http://fcs.codeplex.com/SourceControl/changeset/view/23139#320921"&gt;C#&lt;/a&gt;, &lt;a href="http://debasishg.blogspot.com/2008/05/designing-internal-dsls-in-scala.html"&gt;Scala &lt;/a&gt;создавать вполне удобные EDSL. Даже на&amp;nbsp;C++&amp;nbsp;с шаблонами можно создавать &lt;a href="http://weegen.home.xs4all.nl/eelis/analogliterals.xhtml"&gt;совершенно потрясающие вещи&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Вообще говоря, если вы&amp;nbsp;выбрали EDSL&amp;nbsp;— т.е. готовы жертвовать грамматикой языка ради готовых средств разработки&amp;nbsp;— то&amp;nbsp;почему&amp;nbsp;бы не&amp;nbsp;жертвовать до&amp;nbsp;конца. Откажитесь от&amp;nbsp;грамматики и задавайте программу сразу как экземпляр метамодели (что принято называть &lt;a href="http://ru.wikipedia.org/wiki/AST"&gt;абстрактным синтаксическим деревом&lt;/a&gt;, хотя оно не&amp;nbsp;абстрактное, да&amp;nbsp;и&amp;nbsp;к&amp;nbsp;синтаксису отношения не&amp;nbsp;имеет). Возьмите платформу, которая заточена под работу с&amp;nbsp;такими программами. Речь, конечно, о&amp;nbsp;семействе лиспов (&lt;b&gt;Common Lisp, Scheme, &lt;a href="http://clojure.org/"&gt;Clojure&lt;/a&gt;&lt;/b&gt;). Никакого синтаксиса, кроме простейших &lt;a href="http://ru.wikipedia.org/wiki/S-%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5"&gt;S-выражений&lt;/a&gt;. Динамическая типизация. Система компилируемых макросов. Культура инкрементальной разработки. Лиспам нет равных в&amp;nbsp;быстроте и&amp;nbsp;удобстве создания EDSL. В&amp;nbsp;знаменитой книге &lt;a href="http://mitpress.mit.edu/sicp/full-text/book/book.html"&gt;SICP&lt;/a&gt; мини-языки в&amp;nbsp;рамках Scheme создаются практически под каждую задачу, и&amp;nbsp;даже без использования макросов.&lt;br /&gt;&lt;br /&gt;Мы&amp;nbsp;подошли к&amp;nbsp;месту, когда нельзя не&amp;nbsp;упомянуть &lt;a href="http://www.jetbrains.com/mps/"&gt;JetBrains &lt;b&gt;MPS&lt;/b&gt;&lt;/a&gt;. Это весьма оригинальная вещь. В&amp;nbsp;компании, где пишут среды разработки для мейнстримных языков программирования, зародилась идея создания IDE для создания IDE для создания DSL. По&amp;nbsp;качеству аналогичных мейнстримным. С&amp;nbsp;автодополнениями, рефакторингами, удобными отладчиками. Сами посудите, какой тут может быть лисп. В&amp;nbsp;лиспе отсутствует культура автодополнений и&amp;nbsp;рефакторинга, не&amp;nbsp;развит статический анализ кода, зато повсюду &lt;a href="http://votez.livejournal.com/203771.html?thread=646139#t646139"&gt;кошмарные&lt;/a&gt; скобки. Лисп не&amp;nbsp;годился. Пара вещей, однако, была заимствована из&amp;nbsp;него: отказ от&amp;nbsp;синтаксиса и&amp;nbsp;макросы. Первое было даже не&amp;nbsp;просто заимствовано, а&amp;nbsp;доведено до&amp;nbsp;абсолюта. В&amp;nbsp;MPS нет ни&amp;nbsp;парсера S-выражений, ни&amp;nbsp;какого-либо другого парсера. Потому что в&amp;nbsp;MPS нет кода. Редактор MPS работает не&amp;nbsp;с&amp;nbsp;кодом, а&amp;nbsp;непосредственно с&amp;nbsp;экземпляром метамодели. В&amp;nbsp;целях облегчения перехода разработчиков на&amp;nbsp;данную технологию редактор &lt;i&gt;напоминает&lt;/i&gt; текстовый, декорируя и&amp;nbsp;отображая дерево так, что оно выглядит как код, но&amp;nbsp;им&amp;nbsp;не&amp;nbsp;является. Инструментов для импорта кода MPS не&amp;nbsp;предоставляет (за&amp;nbsp;исключением &lt;a href="http://forum.jetbrains.net/thread/Meta-Programming-System-625"&gt;кода на&amp;nbsp;Java&lt;/a&gt;). Что касается макросов для кодогенерации в&amp;nbsp;MPS, то&amp;nbsp;они там завязаны на&amp;nbsp;строгую систему типов, что позволяет автоматически делать статическую проверку типов для создаваемого DSL.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://forum.jetbrains.net/thread/Meta-Programming-System-289"&gt;Те&lt;/a&gt;, кто говорят, что MPS это переизобретение Лиспа, неправы. Скорее, MPS&amp;nbsp;— это скачок к&amp;nbsp;&lt;a href="http://is.ifmo.ru/works/VisualConstructionOfApplications_june.pdf"&gt;визуальному метапрограммированию будущего&lt;/a&gt;. Что не&amp;nbsp;означает, однако, что этот скачок удачный. Время покажет. Возможно, молодое поколение будет очаровано визуальной средой MPS и&amp;nbsp;автодополнениями. С&amp;nbsp;другой стороны, им&amp;nbsp;может помешать изолированность MPS от&amp;nbsp;мира текстовых программ, т.к. сейчас практически все языки текстовые. Их&amp;nbsp;также может отпугнуть &lt;a href="http://www.jetbrains.com/mps/docs/tutorial.html"&gt;тотальная бюрократизация&lt;/a&gt; всего процесса программирования (что неудивительно при хост-платформе Java). В&amp;nbsp;общем, выбор делать&amp;nbsp;им, а&amp;nbsp;мы&amp;nbsp;идём дальше, рассматривая тот случай, когда вам нужен именно текстовый DSL, со&amp;nbsp;своей уникальной грамматикой.&lt;br /&gt;&lt;br /&gt;Итак, вы&amp;nbsp;желаете реализовать настоящий DSL. С&amp;nbsp;транслятором проблем не&amp;nbsp;будет&amp;nbsp;— связка lex+yacc, портированная, кажется, на&amp;nbsp;все платформы в&amp;nbsp;мире, уже лет 40&amp;nbsp;выполняет задачу автоматического построения парсеров. Для грамматик, которым не&amp;nbsp;хватает yacc, есть другие инструменты. Но&amp;nbsp;отладчик и&amp;nbsp;IDE придётся писать самостоятельно. Если только&amp;nbsp;не... найти хост-язык с&amp;nbsp;настраиваемой грамматикой и&amp;nbsp;взять существующую IDE для него. Если она не&amp;nbsp;сломается от&amp;nbsp;введения новой грамматики. Фактически, получится EDSL, но&amp;nbsp;без ограничений на&amp;nbsp;синтаксис, которые накладывались&amp;nbsp;бы нерасширяемым хост-языком. Этот подход называется «&lt;a href="http://en.wikipedia.org/wiki/Extensible_programming"&gt;extensible programming&lt;/a&gt;»; он&amp;nbsp;был довольно популярен в&amp;nbsp;&lt;nobr&gt;1960-х,&lt;/nobr&gt; потом заглох, и&amp;nbsp;ожил только в&amp;nbsp;XXI&amp;nbsp;веке. Поэтому живых языков с&amp;nbsp;настраиваемой грамматикой не&amp;nbsp;очень много.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Forth&lt;/b&gt;&amp;nbsp;— старейший из&amp;nbsp;таких языков. Отличается тем, что «своей» грамматики практически не&amp;nbsp;имеет. Таков&amp;nbsp;же его преемник &lt;a href="http://factorcode.org/"&gt;&lt;b&gt;Factor&lt;/b&gt;&lt;/a&gt;. Программирование на&amp;nbsp;Forth, однако, может оказаться &lt;a href="http://www.yosefk.com/blog/my-history-with-forth-stack-machines.html"&gt;не&amp;nbsp;таким простым&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Common Lisp&lt;/b&gt; отметился и&amp;nbsp;здесь. Помимо &lt;a href="http://www.gigamonkeys.com/book/macros-defining-your-own.html"&gt;defmacro&lt;/a&gt;, которые используются для создания «скобочных» EDSL, о&amp;nbsp;которых было сказано выше, в&amp;nbsp;CL есть т.н. reader macros, которые позволяют изменить поведение reader-а, т.е. расширить синтаксис. Чтобы не&amp;nbsp;конфликтовать со&amp;nbsp;стандартным синтаксисом&amp;nbsp;CL, reader macros обычно начинаются с «#», хотя могут начинаться и&amp;nbsp;с&amp;nbsp;любого другого символа. Например, #c(0, 1)&amp;nbsp;— запись мнимой единицы &lt;a href="http://oopweb.com/LISP/Documents/cltl/Volume/node20.html#SECTION00614000000000000000"&gt;одним&lt;/a&gt; из&amp;nbsp;&lt;a href="http://www.ida.liu.se/imported/cltl/clm/node191.html"&gt;стандартных макросов CL&lt;/a&gt;. Есть примеры более развесистых макросов: &lt;a href="https://github.com/jfm3/shellshock"&gt;запуск shell-команд прямо из&amp;nbsp;CL&lt;/a&gt;, &lt;a href="http://trac.clozure.com/ccl/wiki/OpenMclFfi"&gt;вызов &lt;nobr&gt;C-функций&lt;/nobr&gt;&lt;/a&gt;, &lt;a href="http://frank.kank.net/essays/hash.html"&gt;синтаксис для хеш-таблиц&lt;/a&gt;, &lt;a href="http://weitz.de/cl-interpol/"&gt;расширенный синтаксис строк&lt;/a&gt;, и&amp;nbsp;даже &lt;a href="http://www.cs.colorado.edu/~ralex/papers/PDF/X-expressions.pdf"&gt;встраивание XML&lt;/a&gt; непосредственно в&amp;nbsp;лисповый код. Однако, если, синтаксис вашего DSL конфликтует с&amp;nbsp;синтаксисом S-выражений, то&amp;nbsp;дело может оказаться труднее. В&amp;nbsp;теории, ничто не&amp;nbsp;мешает &lt;a href="http://www.linux.org.ru/forum/development/6763155/page2?#comment-6791789"&gt;изменить стандартный reader&lt;/a&gt; на&amp;nbsp;собственный, но&amp;nbsp;как это будет на&amp;nbsp;практике, и&amp;nbsp;как на&amp;nbsp;это отреагирует ваша IDE (например &lt;a href="http://common-lisp.net/project/slime/"&gt;SLIME&lt;/a&gt;), пока не&amp;nbsp;ясно. Похоже, никто серьёзно не&amp;nbsp;занимался этим вопросом в&amp;nbsp;CL.&lt;/li&gt;&lt;li&gt;&lt;b&gt;&lt;a href="http://racket-lang.org/"&gt;Racket&lt;/a&gt;&lt;/b&gt;&amp;nbsp;— бывшая PLT Scheme&amp;nbsp;— совсем другое дело. Платформа «из&amp;nbsp;коробки» поддерживает концепцию &lt;a href="http://docs.racket-lang.org/guide/Module_Syntax.html#(part._hash-lang)"&gt;переключения&lt;/a&gt; между разными языками, и&amp;nbsp;позволяет &lt;a href="http://docs.racket-lang.org/guide/languages.html"&gt;создавать новые&lt;/a&gt; языки, используя при этом &lt;a href="http://docs.racket-lang.org/parser-tools/Parsers.html"&gt;генератор парсеров в&amp;nbsp;стиле yacc&lt;/a&gt;. Есть примеры «не-скобочных» языков, созданных на&amp;nbsp;Racket, по&amp;nbsp;мотивам &lt;a href="http://docs.racket-lang.org/datalog/Tutorial.html"&gt;Prolog&lt;/a&gt;, &lt;a href="http://hashcollision.org/brainfudge/"&gt;Brainfuck&lt;/a&gt; и&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/Racket_features#Algol"&gt;Algol&amp;nbsp;60&lt;/a&gt;. Среда (DrRacket) и&amp;nbsp;её&amp;nbsp;отладчик работают с&amp;nbsp;этими языками. Более того, DrRacket написан на&amp;nbsp;Racket&amp;nbsp;же, что открывает возможности для модификации среды под язык и&amp;nbsp;распространения её&amp;nbsp;в&amp;nbsp;качестве IDE для вашего DSL. Словом, Racket&amp;nbsp;— очень перспективная платформа для создания DSL, и&amp;nbsp;поддерживается в&amp;nbsp;прекрасном состоянии.&lt;/li&gt;&lt;li&gt;&lt;b&gt;&lt;a href="http://nemerle.org/"&gt;Nemerle&lt;/a&gt;&lt;/b&gt;&amp;nbsp;— статически типизированный язык для .NET с&amp;nbsp;макросами и&amp;nbsp;расширяемым синтаксисом. Возможности расширения, однако, ограничены. Есть проект &lt;a href="http://code.google.com/p/nemerle-2/wiki/Nemerle2"&gt;Nemerle&amp;nbsp;2&lt;/a&gt;, в&amp;nbsp;котором ограничения будут сняты, и&amp;nbsp;который обещает стать чем-то вроде MPS для .NET (но&amp;nbsp;без отказа от&amp;nbsp;грамматик). Пока только обещает, впрочем.&lt;/li&gt;&lt;li&gt;&lt;b&gt;&lt;a href="http://www-sop.inria.fr/mimosa/fp/Bigloo/"&gt;Bigloo&lt;/a&gt;&amp;nbsp;&lt;/b&gt;— ещё одна реализация Scheme, с&amp;nbsp;упором на&amp;nbsp;быстродействие. Так&amp;nbsp;же, как и&amp;nbsp;Racket, обладает средствами &lt;a href="http://www-sop.inria.fr/mimosa/fp/Bigloo/doc/bigloo-12.html"&gt;генерации парсеров&lt;/a&gt;, и&amp;nbsp;поддерживает макросы, однако не&amp;nbsp;имеет такого удобного механизма переключения языков и&amp;nbsp;создания новых. Кроме того, не&amp;nbsp;факт, что отладчик Bigloo и&amp;nbsp;его IDE (основанное на&amp;nbsp;Emacs) будут нормально работать с&amp;nbsp;новыми языками, ибо, как и&amp;nbsp;с&amp;nbsp;CL, вряд&amp;nbsp;ли кто-то специально занимался этим вопросом, а&amp;nbsp;само собой ничего не&amp;nbsp;делается.&lt;/li&gt;&lt;li&gt;&lt;a href="http://scg.unibe.ch/research/helvetia" style="font-weight: bold;"&gt;Helvetia&lt;/a&gt;&amp;nbsp;— уникальная в&amp;nbsp;своём роде разработка на&amp;nbsp;базе языка Smalltalk, в&amp;nbsp;которой используется то&amp;nbsp;обстоятельство, что вся среда Smalltalk (включая парсер) поддаётся изменению в&amp;nbsp;рантайме. Helvetia&amp;nbsp;— инструмент для произвольного расширения синтаксиса Smalltalk. С&amp;nbsp;сохранением отладки. И&amp;nbsp;даже с&amp;nbsp;подсветкой и&amp;nbsp;автодополнением! Что делает MPS не&amp;nbsp;совсем уникальной средой. &lt;a href="http://scg.unibe.ch/research/helvetia/examples"&gt;Примеры&lt;/a&gt; включают SQL, Brainfuck и&amp;nbsp;что-то похожее на&amp;nbsp;CSS. Единственная неприятность&amp;nbsp;— автор Helvetia защитил по&amp;nbsp;ней PhD, ушёл работать в&amp;nbsp;Google и&amp;nbsp;&lt;a href="http://www.mail-archive.com/pharo-project@lists.gforge.inria.fr/msg48239.html"&gt;забросил&lt;/a&gt; своё творение. Helvetia работает только на&amp;nbsp;Pharo Smalltalk версии 1.1, и&amp;nbsp;не&amp;nbsp;портирована ни&amp;nbsp;на&amp;nbsp;современную версию Pharo (1.3), ни&amp;nbsp;на&amp;nbsp;Squeak. Однако автор &lt;a href="https://twitter.com/#!/renggli/status/90487645372825601"&gt;иногда что-то делает&lt;/a&gt;, всё-таки. Жаль, что кроме него, никто в&amp;nbsp;разработке Helvetia не&amp;nbsp;участвует. Кстати, автор в&amp;nbsp;2009&amp;nbsp;г. &lt;a href="http://scg.unibe.ch/archive/papers/Reng09bLanguageShootout.pdf"&gt;выбрал&lt;/a&gt; в&amp;nbsp;качестве хост-языка Smalltalk (а&amp;nbsp;не&amp;nbsp;Lisp) по&amp;nbsp;причине «однородности» языка и&amp;nbsp;среды. Среды разработки Smalltalk написаны на&amp;nbsp;Smalltalk, а&amp;nbsp;про Scheme или&amp;nbsp;CL такого сказать было нельзя. DrRacket в&amp;nbsp;то&amp;nbsp;время был наполовину написан на&amp;nbsp;C++, и&amp;nbsp;его переписали на&amp;nbsp;чистом Racket только &lt;a href="http://blog.racket-lang.org/2011/02/racket-v51.html"&gt;в&amp;nbsp;феврале 2011&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Как видите, в&amp;nbsp;принципе есть инструменты на&amp;nbsp;любой вкус и&amp;nbsp;под любые требования. В&amp;nbsp;наше время создавать DSL стало не&amp;nbsp;только полезно, но&amp;nbsp;и&amp;nbsp;приятно.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;Автор признателен &lt;a href="http://www.linux.org.ru/forum/development/6763155"&gt;LOR&lt;/a&gt;-у за подсказки и Ф.А.Новикову за вычитывание черновика.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-3864408041400861951?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/3864408041400861951/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=3864408041400861951' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/3864408041400861951'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/3864408041400861951'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2011/09/dsl.html' title='Как создавать DSL'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-7798290274888812111</id><published>2011-09-20T13:05:00.001+04:00</published><updated>2011-09-20T13:05:46.069+04:00</updated><title type='text'>Горькая правда о Питоне и его Global Interpreter Lock (цитата)</title><content type='html'>&lt;blockquote&gt;&lt;span class="Apple-style-span" style="color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 15px; line-height: 21px;"&gt;&lt;a href="http://asvetlov.blogspot.com/2011/07/gil.html"&gt;GIL не уберут никогда.&lt;/a&gt; Или, по крайней мере, в ближайший десяток лет. Сейчас никаких работ на эту тему не ведется. Если некий гений предъявит работающую реализацию без GIL, ничего не ломающую и работающую не медленней, чем существующая версия — предложению будет открыт зеленый свет. Пока же «убрать GIL» проходит по части благих, но невыполнимых пожеланий.&lt;/span&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;span class="Apple-style-span" style="color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 15px; line-height: 21px;"&gt;В Java и C# никакого GIL нет. Потому что у них иначе устроен garbage collector. Если хотите, он более прогрессивный. Переделать GC Питона, не сломав обратной совместимости со всеми существующими библиотеками, использующими Python C API — невозможно. Сообщество и так уже который год лихорадит в связи с переходом на Python 3.x. Разработчики не желают выкатывать второе революционное изменение, не разобравшись с первым. Ждите Python 4.x (которого нет даже в планах) — до тех пор ничего не поменяется.&lt;/span&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-7798290274888812111?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/7798290274888812111/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=7798290274888812111' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/7798290274888812111'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/7798290274888812111'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2011/09/global-interpreter-lock.html' title='Горькая правда о Питоне и его Global Interpreter Lock (цитата)'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-461408419143634067</id><published>2011-09-10T12:49:00.000+04:00</published><updated>2011-09-10T12:49:06.385+04:00</updated><title type='text'>Искал работу</title><content type='html'>Для кого-то поиск работы&amp;nbsp;— это унылое листание унылых вакансий, слащавые письма от&amp;nbsp;слащавых рекрутеров, пробивание стен HR-отделов и&amp;nbsp;т.п. Можно, конечно, и&amp;nbsp;так. Но&amp;nbsp;по&amp;nbsp;факту&amp;nbsp;— существуют хорошие работы, но&amp;nbsp;не&amp;nbsp;существует рынка хороших работ. Хорошие работы появляются тогда, когда кто-то хочет сделать что-то новое и&amp;nbsp;крутое, вне устоявшегося положения вещей (нет, не&amp;nbsp;веб-стартап, вы&amp;nbsp;меня не&amp;nbsp;так поняли). Из&amp;nbsp;чего следует, что вакансии нерелевантны, рекрутеры не&amp;nbsp;в&amp;nbsp;теме, а&amp;nbsp;HR вообще по&amp;nbsp;жизни не&amp;nbsp;в&amp;nbsp;теме.&lt;br /&gt;&lt;br /&gt;Общаться надо с&amp;nbsp;теми самыми, которые (см. выше).&lt;br /&gt;&lt;br /&gt;И&amp;nbsp;вы&amp;nbsp;никогда не&amp;nbsp;пожалеете об&amp;nbsp;этом, вне зависимости от&amp;nbsp;своих карьерных планов.&lt;br /&gt;&lt;br /&gt;Вам рассказывают об&amp;nbsp;актуальных проблемах, преграждающих путь в&amp;nbsp;светлое технологическое будущее. Вам показывают невиданные девайсы. Вы&amp;nbsp;общаетесь с&amp;nbsp;корифеями, которые своей волей определяют направления развития науки и&amp;nbsp;техники. Это выставка-конференция-презентация, на&amp;nbsp;которой вы&amp;nbsp;— единственный зритель и&amp;nbsp;слушатель.&lt;br /&gt;&lt;br /&gt;Программисты сейчас нужны почти везде, так что для данной профессии «правильный» поиск работы немало расширяет горизонты. В&amp;nbsp;одном Питере, не&amp;nbsp;говоря уже о&amp;nbsp;других городах и&amp;nbsp;странах, делают:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://transas.ru/"&gt;Военные симуляторы; составление трёхмерных карт местности по&amp;nbsp;снимкам с&amp;nbsp;беспилотников; трёхмерную карту города для городских служб&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://vniira.ru/"&gt;Программные комплексы для авиадиспетчеров; тренажёры для них&amp;nbsp;же&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.ioffe.ru/astro/HEA/index.html"&gt;Программы для симуляции плазмы; анализ гамма-всплесков&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.jetbrains.com/"&gt;Передовые средства разработки&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://lamm.spbstu.ru/"&gt;Программу для расчёта оптимальной конструкции крыла самолёта&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.speechpro.ru/"&gt;Обработку и&amp;nbsp;синтез речи&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.ipa.nw.ru/PAGE/rusipa.htm"&gt;Расчёт движения небесных тел; обработку огромных потоков данных с&amp;nbsp;радиотелескопов&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;И&amp;nbsp;это только те&amp;nbsp;места, в&amp;nbsp;которых мне ответили. Для полноты следует упомянуть&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://ggasoftware.com/"&gt;Софт для фармацевтических компаний и&amp;nbsp;институтов&lt;/a&gt; (старое место работы)&lt;/li&gt;&lt;/ul&gt;Вывод такой: можно выбирать область, которая нравится. Выбор есть. Спасибо сформировавшейся индустрии.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-461408419143634067?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/461408419143634067/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=461408419143634067' title='Комментарии: 3'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/461408419143634067'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/461408419143634067'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2011/09/blog-post.html' title='Искал работу'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-2777005178829820027</id><published>2011-06-24T04:07:00.014+04:00</published><updated>2011-07-22T12:45:18.857+04:00</updated><title type='text'>О краткости языка (On the Conciseness of Language)</title><content type='html'>Английский язык более ёмкий, чем русский. Каждый русскоговорящий гражданин когда-то узнаёт это в&amp;nbsp;первый раз. Лично я&amp;nbsp;об&amp;nbsp;этом прочёл в&amp;nbsp;&lt;nobr&gt;8-м&lt;/nobr&gt; классе в&amp;nbsp;детективе одной современной отечественной писательницы детективов (кажется, в нём же я&amp;nbsp;узнал об асимметричной схеме шифрования с открытым ключом, которая подавалась авторшей как личное изобретение одного из&amp;nbsp;преследуемых милицией злодеев). Впоследствии я&amp;nbsp;не&amp;nbsp;испытывал недостатка в&amp;nbsp;подкрепляющих примерах. Многие английские слова короче своих аналогов на&amp;nbsp;русском, а&amp;nbsp;ещё короче комбинации «прилагательное+существительное». Чего стоят одни только «exit polls»&amp;nbsp;— тележурналисты бросили пытаться их&amp;nbsp;переводить и&amp;nbsp;так и&amp;nbsp;говорят, по-английски. Английский язык гибче и&amp;nbsp;выразительнее, это был очевидный факт.&lt;br /&gt;&lt;br /&gt;Шли годы.&lt;br /&gt;&lt;br /&gt;Я&amp;nbsp;узнавал больше о&amp;nbsp;языке, и&amp;nbsp;во&amp;nbsp;мне росло сомнение.&lt;br /&gt;&lt;br /&gt;Сейчас я&amp;nbsp;убеждён в&amp;nbsp;том, что мнение о&amp;nbsp;ёмкости английского языка по&amp;nbsp;сравнению с&amp;nbsp;русским происходит от&amp;nbsp;недостаточного знания английского. Или русского. Языки по&amp;nbsp;ёмкости не&amp;nbsp;различаются.&lt;br /&gt;&lt;h2&gt;Тексты&lt;/h2&gt;Часто так бывает: пишет человек письмо или статью на&amp;nbsp;русском, а&amp;nbsp;потом самостоятельно переводит на&amp;nbsp;английский, и&amp;nbsp;второй вариант получается компактнее. Потом оказывается, что у&amp;nbsp;него «is» вместо «is&amp;nbsp;being», «was» вместо «has been», «I will» вместо&amp;nbsp;«I am going to»,&amp;nbsp;«for» вместо&amp;nbsp;«in order to»,&amp;nbsp;«it’s» вместо «it&amp;nbsp;is» (вариант с&amp;nbsp;апострофом, вообще-то, является разговорным, и&amp;nbsp;допускается только в&amp;nbsp;прямой речи или сильно неформальном письме), артикли забыты в&amp;nbsp;половине мест, и&amp;nbsp;вообще такой текст носитель языка прочтёт лишь изрядно поморщившись, потому что фразы построены неверно. Или ещё бывает наоборот, над английским текстом автор постарается, дабы не&amp;nbsp;уронить лицо, и&amp;nbsp;напишет ясно и&amp;nbsp;лаконично; а&amp;nbsp;в&amp;nbsp;руссском варианте оставит разные там «исходя из&amp;nbsp;вышеизложенного, совершенно очевидно, что».&lt;br /&gt;&lt;br /&gt;Тексты, переведённые любителями с&amp;nbsp;английского на&amp;nbsp;русский, тоже зачастую длиннее оригиналов. И&amp;nbsp;большинство их&amp;nbsp;выглядит настолько нелепо, что сразу видно, что это перевод. Читаешь и&amp;nbsp;видишь: по-русски так никогда не&amp;nbsp;написали&amp;nbsp;бы.&lt;br /&gt;&lt;br /&gt;Короче, плохие переводы = плохая репутация, не&amp;nbsp;более того. Чтобы составить мнение о&amp;nbsp;языке, надо смотреть на&amp;nbsp;хорошие переводы хороших текстов. Например: краткая история Талибана (&lt;a href="http://zharov.com/afgan/taliban.html"&gt;на&amp;nbsp;русском&lt;/a&gt;, &lt;a href="http://zharov.com/afghan/taliban.html"&gt;на&amp;nbsp;английском&lt;/a&gt;). Оригинал и&amp;nbsp;перевод чуть-чуть отличаются в&amp;nbsp;деталях, которые делают каждый из&amp;nbsp;двух текстов доступнее для носителей соответствующего языка, но&amp;nbsp;это закон стиля, иначе было&amp;nbsp;бы не&amp;nbsp;по-настоящему. К&amp;nbsp;тому&amp;nbsp;же, детали там уравновешивают друг друга, так что можно считать тексты равными по&amp;nbsp;количеству информации.&lt;br /&gt;&lt;br /&gt;Так вот: русский вариант содержит 15181 букв и&amp;nbsp;цифр (пробелы и&amp;nbsp;знаки препинания не&amp;nbsp;в&amp;nbsp;счёт), а&amp;nbsp;английский&amp;nbsp;— 16239. Разница в&amp;nbsp;пользу русского, но&amp;nbsp;в&amp;nbsp;пределах статистической погрешности.&lt;br /&gt;&lt;br /&gt;Возьмём фрагмент поменьше, из&amp;nbsp;повести Пелевина «&lt;a href="http://pelevin.nov.ru/pov/pe-zombi/1.html"&gt;Зомбификация&lt;/a&gt;», глава «Бульдозер»:&lt;br /&gt;&lt;blockquote&gt;Представим себе небольшое село, стоящее на&amp;nbsp;холме&amp;nbsp;— некоторые дома уже очень стары, другие, наоборот, построены по&amp;nbsp;самым последним проектам, а&amp;nbsp;большинство&amp;nbsp;— нечто среднее между первым и&amp;nbsp;вторым. Бок о&amp;nbsp;бок стоят полузаброшенная церковь и&amp;nbsp;недостроенный клуб. В&amp;nbsp;одних окнах мигает керосиновая лампа, в&amp;nbsp;других горит электричество, где-то чуть слышно играет балалайка, которую перекрывает радиомузыка со&amp;nbsp;столба. Словом, обычная жизнь, остатки нового и&amp;nbsp;старого, переплетенные самым причудливым образом. &lt;/blockquote&gt;&lt;blockquote&gt;Теперь представим себе бульдозериста, который, начитавшись каких-то брошюр, решил смести всю эту отсталость и&amp;nbsp;построить новый поселок на&amp;nbsp;совершенно гладком месте. Сырой октябрьской ночью он&amp;nbsp;садится в&amp;nbsp;бульдозер и&amp;nbsp;в&amp;nbsp;несколько приемов срезает всю верхнюю часть холма с&amp;nbsp;деревней и&amp;nbsp;жителями. И&amp;nbsp;вот, когда бульдозер крутится в&amp;nbsp;грязи, разравнивая будущую стройплощадку, происходит нечто совершенно неожиданное: бульдозер вдруг проваливается в&amp;nbsp;подземную пустоту&amp;nbsp;— вокруг оказываются какие-то полусгнившие бревна, человеческие и&amp;nbsp;лошадиные скелеты, черепки и&amp;nbsp;куски ржавчины. Бульдозер оказался в&amp;nbsp;могиле. Ни&amp;nbsp;бульдозерист, ни&amp;nbsp;авторы вдохновивших его брошюр не&amp;nbsp;учли, что когда они сметут все, что по&amp;nbsp;их&amp;nbsp;мнению устарело, обнажится&amp;nbsp;то, что было под этим, то&amp;nbsp;есть нечто куда более древнее. &lt;/blockquote&gt;&lt;blockquote&gt;Психика человека точно так&amp;nbsp;же имеет множество культурных слоев. Если срезать верхний слой психической культуры, объявив его набором предрассудков, заблуждений и&amp;nbsp;классово чуждых точек зрения, обнажится темное бессознательное с&amp;nbsp;остатками существовавших раньше психических образований. Все преемственно, вчерашнее вложено в&amp;nbsp;сегодняшнее, как матрешка в&amp;nbsp;матрешку, и&amp;nbsp;тот, кто попробует снять с&amp;nbsp;настоящего стружку, чтобы затем раскрасить его под будущее, в&amp;nbsp;результате провалится в&amp;nbsp;очень далекое прошлое.&lt;/blockquote&gt;Перевод, выполненный вашим покорным слугой, был отредактирован и&amp;nbsp;заверен расовым носителем британского английского. Перевод точный, ничего не&amp;nbsp;упущено и&amp;nbsp;не&amp;nbsp;добавлено.&lt;br /&gt;&lt;blockquote&gt;Let&amp;nbsp;us picture a&amp;nbsp;village standing on&amp;nbsp;a&amp;nbsp;hill, with some houses being very old and some others designed along up-to-date lines, while the majority is&amp;nbsp;somewhere between the two. A&amp;nbsp;semi-abandoned church stands side by&amp;nbsp;side with an&amp;nbsp;unfinished workers’ club. In&amp;nbsp;some windows, kerosene lamps flicker, while in&amp;nbsp;some others, electric light burns. Barely audible, a&amp;nbsp;balalaika plays, drowned by&amp;nbsp;music from a&amp;nbsp;radio on&amp;nbsp;a&amp;nbsp;street pole. In&amp;nbsp;sum, that is&amp;nbsp;a&amp;nbsp;piece of&amp;nbsp;ordinary life, odds and ends of&amp;nbsp;old and new, twisted around each other in&amp;nbsp;a&amp;nbsp;most cranky way.&lt;/blockquote&gt;&lt;blockquote&gt;Now, let&amp;nbsp;us picture a&amp;nbsp;bulldozer driver who, having overdosed on&amp;nbsp;some brochures or&amp;nbsp;other, has taken the decision to&amp;nbsp;slice away all that backwardness and build up&amp;nbsp;a&amp;nbsp;new village from scratch. So&amp;nbsp;in&amp;nbsp;the middle of&amp;nbsp;a&amp;nbsp;raw October night he&amp;nbsp;sits behind the wheel of&amp;nbsp;his bulldozer and, bit by&amp;nbsp;bit, cuts off the entire top of&amp;nbsp;the hill along with the village and its inhabitants. And now, while the bulldozer is&amp;nbsp;spinning in&amp;nbsp;the mud, leveling out the new building site, something very unexpected happens: the bulldozer suddenly falls into a&amp;nbsp;void and finds itself among half-rotten logs, skeletons of&amp;nbsp;men and horses, some shards and rusty smithers. The bulldozer is&amp;nbsp;caught in&amp;nbsp;a&amp;nbsp;grave. Neither the driver nor the authors of&amp;nbsp;those brochures that inspired him had taken into account that as&amp;nbsp;soon as&amp;nbsp;they wipe away everything that is—in their opinion—obsolete, something deep crops out, something much more ancient.&lt;/blockquote&gt;&lt;blockquote&gt;Human psyche—in exactly the same way—has a&amp;nbsp;lot of&amp;nbsp;cultural layers. If&amp;nbsp;one cuts off the upper layer of&amp;nbsp;the culture, declaring it&amp;nbsp;a&amp;nbsp;pile of&amp;nbsp;superstitions, delusions and class-alien points of&amp;nbsp;view, then the darkness of&amp;nbsp;the unconscious crops out together with some preexistent psychic traits. Everything is&amp;nbsp;successive, yesterday nests into today, like one Russian doll inside another, and whoever takes the liberty of&amp;nbsp;planing away the present in&amp;nbsp;order to&amp;nbsp;paint it&amp;nbsp;with colors of&amp;nbsp;the future, will only fall into a&amp;nbsp;very distant past.&lt;/blockquote&gt;В&amp;nbsp;оригинале 1469 букв, в&amp;nbsp;переводе 1576. Разница опять в&amp;nbsp;пользу русского и&amp;nbsp;в&amp;nbsp;пределах статистической погрешности.&lt;br /&gt;&lt;br /&gt;Но&amp;nbsp;это просто примеры. Может быть, есть какое-то теоретическое обоснование ёмкости английского языка перед русским? Мне о&amp;nbsp;таком неизвестно. В&amp;nbsp;английском нет падежей&amp;nbsp;— зато есть частичка «to» и артикли, которые в&amp;nbsp;русском очень редко являются обязательными, а&amp;nbsp;в&amp;nbsp;английском&amp;nbsp;— очень часто. В&amp;nbsp;английском проще словообразование&amp;nbsp;— но&amp;nbsp;при этом жёсткий порядок слов в&amp;nbsp;предложении. В&amp;nbsp;английском нет сложных окончаний&amp;nbsp;— но&amp;nbsp;есть довольно запутанная система «времён». Английский язык аналитический , а&amp;nbsp;русский синтетический&amp;nbsp;— но&amp;nbsp;это ничего не&amp;nbsp;говорит о&amp;nbsp;ёмкости.&lt;br /&gt;&lt;h2&gt;Речь&lt;/h2&gt;В&amp;nbsp;английской речи допустимы сокращения, такие как «it’s», «I’ll», «you’d», «can’t». С&amp;nbsp;другой стороны, английская речь должна звучать длинно. Чем длиннее, тем вежливее, это закон. В&amp;nbsp;русском для вежливости полезна интонация, обращение на «вы»&amp;nbsp;и&amp;nbsp;перестановка слов&amp;nbsp;— в&amp;nbsp;английском интонация не&amp;nbsp;играет большой роли,&amp;nbsp;«вы» отсутствует,&amp;nbsp;а&amp;nbsp;перестановки слов редки. Когда на&amp;nbsp;русском говорят «[сделайте ч.-л.], пожалуйста», на&amp;nbsp;английском то&amp;nbsp;же самое должно звучать как «could you please [do&amp;nbsp;smth]». Короткое, но&amp;nbsp;вежливо сказанное «нет» переводится как «I&amp;nbsp;don’t think&amp;nbsp;so». И&amp;nbsp;так далее. Короткие фразы уместны в&amp;nbsp;армии, &amp;nbsp;больнице и&amp;nbsp;т.п. местах, где проволочки ни к чему. Бросаться же короткими фразами в&amp;nbsp;бытовом общении&amp;nbsp;— значит быть невежливым (в&amp;nbsp;частности из-за этого хорошо воспитанных русских за&amp;nbsp;границей считают плохо воспитанными).&lt;br /&gt;&lt;h2&gt;Софт&lt;/h2&gt;Интерфейсы программ на&amp;nbsp;русском всегда длиннее аналогичных им&amp;nbsp;на&amp;nbsp;английском, но&amp;nbsp;при этом они более корректные. Для англоговорящего человека любое меню в&amp;nbsp;любой программе выглядит неграмотным и&amp;nbsp;местами написанным по-хамски. Все слова очень короткие и&amp;nbsp;не&amp;nbsp;вполне отражающие суть (фактически, это просто некие условные знаки), артиклей нет, фразы выглядят как армейские команды.&lt;br /&gt;&lt;br /&gt;При желании русский интерфейс можно сделать таким&amp;nbsp;же кратким (например, Cut/Copy/Paste переводить как Режь/Множь/Клей). Это вопрос традиции, а&amp;nbsp;не&amp;nbsp;языка. Сложилась традиция писать длинно, вот и&amp;nbsp;всё.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-2777005178829820027?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/2777005178829820027/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=2777005178829820027' title='Комментарии: 5'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/2777005178829820027'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/2777005178829820027'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2011/06/on-conciseness-of-language.html' title='О краткости языка (On the Conciseness of Language)'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-5154038588099120518</id><published>2010-12-16T00:42:00.015+03:00</published><updated>2010-12-16T14:40:19.769+03:00</updated><title type='text'>О плагинах и линковке</title><content type='html'>&lt;p&gt;В какой-то момент развития &lt;a href="http://ggasoftware.com/opensource/indigo"&gt;Indigo&lt;/a&gt; — кроссплатформенной C++ библиотеки для решения задач химической информатики — мы осознали, что любая нормальная библиотека такого рода должна позволять third-party расширения. Сказано-сделано. Indigo обрела поддержку плагинов. Обёртки на Java, Python и C# также расширяемы, при этом плагин на соответствующем языке является обёрткой бинарного модуля-плагина на C++. Всё это работает на Windows, Linux и Mac OS X. Дальше речь пойдёт о проблемах, с которыми мы столкнулись при линковке бинарных модулей, и о том как эти проблемы решить.&lt;/p&gt;&lt;h2&gt;Постановка задачи&lt;/h2&gt;&lt;p&gt;Есть библиотека на C++, назовём её «Core». В ней есть какое-то количество классов и некоторый интерфейс на C, который используется для «оборачивания» библиотеки в Java/Python/C# модули.&lt;/p&gt;&lt;p&gt;Есть ещё одна библиотека на C++, назовём её «Plugin». В ней тоже есть классы и &lt;nobr&gt;C-интерфейс.&lt;/nobr&gt; Plugin имеет доступ к C++ классам Core и её глобальным функциям, тогда как Core не в курсе о существовании Plugin.&lt;/p&gt;&lt;p&gt;Core линкуется в динамическую библиотеку, назовём её libcore. Plugin тоже линкуется в динамическую библиотеку, назовём её libplugin. Эта библиотека не содержит в себе libcore.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Задача 1:&lt;/b&gt; libcore должна загружаться в адресное пространство программы на Java/Python/C# при инициализации соответствующей обёртки Core. Тип операционной системы (Windows/Linux/Mac OS X) и «битность» (32/64) должны определяться автоматически, т.е. нужно выбрать из коллекции сборок libcore для всех платформ правильную, и загрузить именно её. Сразу оговоримся, что обёртки поставляются в комплекте со всеми вариантами библиотеки, чтобы таким образом не нарушать «переносимость» и не причинять головную боль разработчикам, которые будут использовать обёртку.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Задача 2:&lt;/b&gt; libplugin должен загружаться в адресное пространство программы на Java/Python/C# при инициализации соответствующей обёртки Plugin. Само собой, и в этом случае ОС и битность определяются автоматически. Кроме того, libplugin должна «увидеть» уже загруженную libcore и подцепить из неё нужные C++ классы и глобальные функции.&lt;/p&gt;&lt;h2&gt;Решение задачи 1&lt;/h2&gt;&lt;p&gt;В Linux и gcc помогут ключи -m32 и -m64, с которыми собирается соответственно &lt;nobr&gt;32-битный&lt;/nobr&gt; и &lt;nobr&gt;64-битный&lt;/nobr&gt; код. Для кросс-компиляции, т.е. сборки под платформу, не соответствующую той, на которой выполняется компиляция, надо установить пакет gcc-multilib.&lt;/p&gt;&lt;p&gt;В Windows и Visual Studio дело делается установкой платформы Win32 или x64 в свойствах проекта. Чтобы платформа 64 появилась на &lt;nobr&gt;32-битных&lt;/nobr&gt; версиях VS, надо при инсталляции VS поставить галочку напротив «x64 Compilers and Tools», ну или доустановить потом отдельно.&lt;/p&gt;&lt;p&gt;На Mac OS X «битность» не является проблемой благодаря технологии &lt;a href="http://en.wikipedia.org/wiki/Universal_binary"&gt;универсальных бинарников&lt;/a&gt;. Не иначе как для компенсации этого удобства, бинарники, собранные для версии Mac OS X 10.6, не запускаются на 10.5, так что имеет смысл собирать для 10.5 и 10.6 отдельно. Или собирать только для 10.5 и рассчитывать, что они подойдут для 10.6, но мы это не пробовали.&lt;/p&gt;&lt;p&gt;Теперь пара слов о том, как определить платформу на этапе выполнения программы на Java/Python/C# и загрузить нужный модуль libcore.&lt;/p&gt;&lt;h3&gt;Java&lt;/h3&gt;&lt;p&gt;Узнать тип операционной системы можно с помощью &lt;a href="http://download.oracle.com/javase/1.4.2/docs/api/java/lang/System.html"&gt;System.getProperty&lt;/a&gt;("os.name"). «Битность», соответственно, через System.getProperty("os.arch"). Версия Mac OS X находится в System.getProperty("os.version").&lt;/p&gt;&lt;p&gt;После того, как платформа определена и путь к подходящему бинарнику libcore получен, загрузить бинарник можно вызовом &lt;a href="http://download.oracle.com/javase/1.4.2/docs/api/java/lang/System.html#load(java.lang.String)"&gt;System.load&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Python&lt;/h3&gt;&lt;p&gt;Константа &lt;a href=""&gt;os.name&lt;/a&gt; равна "nt", если дело происходит на Windows, и "posix", если это Linux или Mac OS X. Последняя отличается тем, что &lt;a href=" org=" library=""&gt;platform.mac_ver()&lt;/a&gt; возвращает определённую структуру данных, из которой как раз можно узнать версию системы. Битность можно узнать с помощью &lt;a href="http://docs.python.org/library/platform.html#platform.architecture"&gt;platform.architecture()&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Загрузку бинарного модуля в Python, как и дальнейшую работу с ним, проще всего осуществлять с помощью &lt;a href="http://docs.python.org/library/ctypes.html"&gt;ctypes&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;C#&lt;/h3&gt;&lt;p&gt;Операционная система в данном случае однозначно Windows. (Нет, до Mono мы ещё не добрались.) Битность можно определить, например, проверкой &lt;a href="http://msdn.microsoft.com/en-us/library/system.intptr.size.aspx"&gt;IntPtr.Size&lt;/a&gt;. Для вызова &lt;nobr&gt;C-функций&lt;/nobr&gt; из C# используется &lt;a href="http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.dllimportattribute.aspx"&gt;DllImport&lt;/a&gt;("core.dll"). При вызове любой функции, объявленной с таким атрибутом, CLR попытается загрузить core.dll из системных директорий в память, если библиотека с таким именем ещё не загружена. Поскольку наша core.dll лежит не в системной директории, и вообще существует в двух вариантах (32 и 64 бита), то мы должны загрузить её до того, как будет вызвана любая функция из Core. Для того, чтобы загрузить core.dll из произольной директории, нужно воспользоваться системным вызовом LoadLibrary, который, в свою очередь, доступен через &lt;a href="http://www.pinvoke.net/default.aspx/kernel32.loadlibrary"&gt;DllImport("kernel32")&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;Решение задачи 2&lt;/h2&gt;&lt;p&gt;Функции, а также методы C++ классов, которые то же самое что функции, в терминологии линковщика называются символами. Символы, которые библиотека «показывает» наружу, называются экспортируемыми символами. Символы, которые библиотека берёт из других библиотек, называются импортируемыми символами. Надо сделать так, чтобы символы libcore, которые используются в libplugin, экспортировались из libcore и импортировались в libplugin. В каждой ОС &lt;a href="http://en.wikipedia.org/wiki/Dynamic_linker"&gt;динамический линковщик&lt;/a&gt; работает по-своему, и рецепты соответственно для каждой ОС свои.&lt;/p&gt;&lt;h3&gt;Linux&lt;/h3&gt;&lt;p&gt;С экспортом и импортом принципиальных сложностей нет. линковщик (ld), который собирает объектные файлы в .so-модуль, все неопределённые (undefined) в библиотеке глобальные символы считает импортируемыми, а все определённые символы считает экспортируемыми (есть возможность экспортировать &lt;a href="http://gcc.gnu.org/wiki/Visibility"&gt;только избранные символы&lt;/a&gt;, но нам это не нужно). Динамический линковщик (который не ld, а часть операционной системы) при загрузке libcore запишет все экспортированные из неё символы, а при загрузке libplugin обнаружит в ней символы, ждущие импорта, и импортирует их в libplugin из libcore.&lt;/p&gt;&lt;p&gt;Есть ещё одна особенность. Символы могут быть помечены импортируемыми «откуда угодно», или импортируемыми из конкретной библиотеки.&lt;/p&gt;&lt;h4&gt;Импорт символов из конкретной библиотеки&lt;/h4&gt;&lt;p&gt;ld помечает символ импортируемым из конкретной библиотеки если при линковке указать .so-файл, из которого этот самый символ экспортируется. То есть, в libplugin.so будет чётко прописано, что такой-то символ импортируется из библиотеки libcore.so и только из неё. Кроме того, динамический линковщик будет пытаться при загрузке libplugin.so загрузить libcore.so из директорий, записанных в:&lt;ol&gt;&lt;li&gt;Переменной окружения LD_LIBRARY_PATH. При этом, учитывается только значение этой переменной на этапе запуска программы. Фокусы с подменой LD_LIBRARY_PATH по ходу пьесы не работают. Для программ с &lt;a href="http://en.wikipedia.org/wiki/Setuid"&gt;setuid/setgid&lt;/a&gt; LD_LIBRARY_PATH и вовсе игнорируется.&lt;/li&gt;&lt;li&gt;Кэше /etc/ld.so.cache&lt;/li&gt;&lt;li&gt;/lib&lt;/li&gt;&lt;li&gt;/usr/lib&lt;/li&gt;&lt;/ol&gt;(&lt;a href="http://unixhelp.ed.ac.uk/CGI/man-cgi?ld.so+8"&gt;man &lt;nobr&gt;ld-linux&lt;/nobr&gt;&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;Если libcore.so не будет найдена ни в одной из указанных директорий, то загрузка libplugin.so не пройдёт успешно. Нетрудно понять, что для наших целей такой подход не годится, т.к. libcore мы распространяем в двух вариантах (32 и 64 бита) и обязательно вместе с самой программой, чтобы разработчики на Java и Python не терпели неудобств с непереносимостью своих программ из-за бинарных модулей.&lt;/p&gt;&lt;h4&gt;Импорт символов из любой библиотеки&lt;/h4&gt;&lt;p&gt;Если в ld при линковке libplugin.so не передавать libcore.so, то он пометит отсутвующие символы как импортируемые, но не укажет откуда именно. Динамический линковщик затем при загрузке libplugin.so не станет пытаться загрузить libcore.so, а попытается найти отсутствующий символ среди всех загруженных на данный момент библиотек (+в самой программе). Конечно, libcore.so будет среди них, т.к. мы инициализировали Core до того, как начали инициализацию Plugin. Всё очень хорошо, но есть ещё одна деталь.&lt;/p&gt;&lt;p&gt;На самом деле, ld перебирает не все загруженные на данный момент библиотеки, а только те из них, которые были загружены с флагом RTLD_GLOBAL (&lt;a href="http://linux.die.net/man/3/dlopen"&gt;man dlopen&lt;/a&gt;). Те библиотеки, которые загружены с флагом RTLD_LOCAL, подходят только для импорта символов конкретно из них (см. предыдущий заголовок). Виртуальная машина Java и её System.load(), что бы вы думали, конечно загружает все библиотеки с RTLD_LOCAL, без вариантов. Но есть обходной путь! Он появился в glibc 2.2 (2000 г.) не иначе как специально для Java: это флаг RTLD_NOLOAD. После вызова System.load("/path/to/libcore.so") можно вызвать (уже не из Java, а из C):&lt;pre&gt;dlopen("/path/to/libcore.so", RTLD_NOLOAD | RTLD_GLOBAL);&lt;/pre&gt;и символы из libcore.so, ранее «закреплённые» за libcore.so, станут доступны для импорта по схеме «откуда угодно». Динамический линковщик отдаст их в нужный момент в libplugin. Дополнительная изюминка заключается в том, что вызов dlopen для libcore.so с RTLD_NOLOAD можно оформить в самой libcore.so, в какой-нибудь функции инициализации. Будет работать.&lt;/p&gt;&lt;p&gt;Что касается загрузки .so-файлов в Python, то с ним всё гораздо проще, т.к. ctypes поддерживает произвольные флаги для загрузки библиотек, в т.ч. и RTLD_GLOBAL:&lt;pre&gt;lib = CDLL("/path/to/libcore.so", mode=RTLD_GLOBAL);&lt;/pre&gt;&lt;/p&gt;&lt;h3&gt;Mac OS X&lt;/h3&gt;&lt;p&gt;Правила линковки на Mac OS X такие же, как и в линуксе, есть только небольшие различия в терминологии и опциях линковщика. Схема с привязкой символов к библиотеке, описанная в предыдущем разделе, здесь имеет название: two-level namespace (&lt;a href="http://developer.apple.com/library/mac/#DOCUMENTATION/Darwin/Reference/ManPages/man1/ld.1.html"&gt;man ld&lt;/a&gt;). Альтернативная схема, которая нам и нужна, называется flat namespace. На этапе компиляции libplugin надо выбрать, по какой схеме испортировать в неё символы. Для нас это означает, что надо передать в ld ключ "-flat_namespace".&lt;/p&gt;&lt;p&gt;Использование flat namespace, по замыслу разработчиков, не отменяет необходимости задания в командной строке ld всех зависимых .dylib-файлов (т.е. в нашем случае libcore.dylib). Это странно, но оправдано для более сложных случаев с косвенными зависимостями (indirect dynamic libraries), которые к нашей задаче не имеют отношения. Можно, тем не менее, попросить ld закрыть глаза на неразрешённые зависимости, передав ему ключ "-undefined suppress". В этом случае динамический линковщик будет разрешать их в рантайме, как в Linux.&lt;/p&gt;&lt;p&gt;Трюк с RTLD_NOLOAD | RTLD_GLOBAL в Mac OS X тоже актуален.&lt;/p&gt;&lt;h3&gt;Windows&lt;/h3&gt;&lt;p&gt;В Windows нет динамического линковщика.&lt;/p&gt;&lt;p&gt;Его там не&amp;nbsp;может быть в&amp;nbsp;принципе из-за отсутствия поддержки &lt;a href="http://en.wikipedia.org/wiki/Position-independent_code#Windows_DLLs"&gt;position-independent code&lt;/a&gt;, но&amp;nbsp;от&amp;nbsp;этого не&amp;nbsp;легче. В&amp;nbsp;Windows есть только загрузчик динамических библиотек (loader), который, мягко говоря, не&amp;nbsp;совсем в&amp;nbsp;курсе про динамическое связывание. Вся работа по&amp;nbsp;динамическому связыванию делается самой загружаемой DLL&amp;nbsp;на этапе инициализации. Компилятор и&amp;nbsp;линковщик (link.exe) должны позаботиться о&amp;nbsp;том, чтобы DLL сделала эту работу правильно. Программист, в&amp;nbsp;свою очередь, должен позаботиться о&amp;nbsp;том, чтобы компилятор и&amp;nbsp;линковщик правильно поняли свою задачу.&lt;/p&gt;&lt;h4&gt;Экспортируемые и&amp;nbsp;импортируемые символы&lt;/h4&gt;&lt;p&gt;По&amp;nbsp;указанным выше причинам, в&amp;nbsp;DLL&amp;nbsp;не может быть неопределённых (undefined) символов. Никто не&amp;nbsp;проверит на&amp;nbsp;этапе загрузки DLL, какие символы в&amp;nbsp;ней &amp;laquo;defined&amp;raquo;, а&amp;nbsp;какие &amp;laquo;undefined&amp;raquo;. Такого вопроса просто не&amp;nbsp;стоит. Все символы должны быть определены. В&amp;nbsp;том числе и&amp;nbsp;символы, которые импортируются из&amp;nbsp;другой DLL.&lt;/p&gt;&lt;p&gt;Этот парадокс разрешается следующим образом: при компиляции модулей DLL, в&amp;nbsp;которую импортируются символы (в&amp;nbsp;нашем случае plugin.dll) компилятор на&amp;nbsp;месте импортируемых символов создаёт функцию-&amp;quot;прослойку&amp;quot;, которая &lt;ol&gt;&lt;li&gt;Загружает в&amp;nbsp;память DLL, в&amp;nbsp;которой находится нужная функция (&lt;a href="http://msdn.microsoft.com/en-us/library/ms684175(v=vs.85).aspx"&gt;LoadLibrary&lt;/a&gt;(&amp;quot;core.dll&amp;quot;)). Большая удача, нет мороки с&amp;nbsp;путями. Есди загрузить core.dll из&amp;nbsp;нужной директори заранее (LoadLibrary("\path\to\core.dll")), то&amp;nbsp;plugin.dll не&amp;nbsp;станет искать core.dll в&amp;nbsp;системных директориях, а&amp;nbsp;просто &amp;laquo;подхватит&amp;raquo; уже загруженную копию.&lt;/li&gt;&lt;li&gt;Получив указатель (handle) на&amp;nbsp;загруженную DLL, ищет в&amp;nbsp;ней нужную функцию (&lt;a href="http://msdn.microsoft.com/en-us/library/ms683212(v=vs.85).aspx"&gt;GetProcAddress&lt;/a&gt;) и&amp;nbsp;вызывает её&amp;nbsp;с&amp;nbsp;теми параметрами, которые были переданы в&amp;nbsp;неё саму, т.е. в&amp;nbsp;прослойку.&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;p&gt;link.exe&amp;nbsp;не&amp;nbsp;догадается сделать такую прослойку для всех C++ функций, которые не&amp;nbsp;определёны в&amp;nbsp;DLL (хотя догадается сделать это для &lt;nobr&gt;C-функций,&lt;/nobr&gt; слабое утешение). Перед объявлением каждой из&amp;nbsp;C++&amp;nbsp;функций, которую вы&amp;nbsp;хотите импортировать в&amp;nbsp;plugin.dll, надо писать &lt;a href="http://msdn.microsoft.com/en-us/library/8fskxacy(v=vs.80).aspx"&gt;__declspeс(dllimport)&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Более того, link.exe&amp;nbsp;не&amp;nbsp;догадается экспортировать из&amp;nbsp;DLL&amp;nbsp;символы, которые в&amp;nbsp;ней определены. Перед каждой функцией, которую вы&amp;nbsp;хотите экспортировать из&amp;nbsp;core.dll, надо писать &lt;a href="http://msdn.microsoft.com/en-us/library/a90k134d(v=vs.80).aspx"&gt;__declspec(dllexport)&lt;/a&gt;. Это касается и&amp;nbsp;C, и&amp;nbsp;C++&amp;nbsp;функций. При линковке любой DLL помимо собственно DLL возникает маленький файл с&amp;nbsp;расширением .lib&amp;nbsp;&amp;mdash; он&amp;nbsp;и&amp;nbsp;содержит вышеуказанные &amp;laquo;прослойки&amp;raquo; для всех экспортированных функций.&lt;/p&gt;&lt;p&gt;Получается, что одни и те же C++ функции должны объявляться в Core как __declspec(dllexport), а в Plugin — как __declspec(dllimport). Объявляются эти функции в заголовочных файлах, которые одинаково включаются в Core и в Plugin. Самое время воспользоваться &lt;a href="http://stackoverflow.com/questions/2301469/what-does-the-code-decldir-declspecdllexport-really-do"&gt;препроцессором&lt;/a&gt;:&lt;pre&gt;#ifdef _WIN32&lt;br /&gt;#ifdef PLUGIN&lt;br /&gt;#define DLLEXPORT __declspec(dllimport)&lt;br /&gt;#else&lt;br /&gt;#define DLLEXPORT __declspec(dllexport)&lt;br /&gt;#endif&lt;br /&gt;#else&lt;br /&gt;#define DLLEXPORT&lt;br /&gt;#endif&lt;/pre&gt;и перед всеми функциями Core, которые нужны в Plugin, писать макрос DLLEXPORT. При сборке plugin.dll надо указать компилятору макрос PLUGIN.&lt;/p&gt;&lt;h4&gt;Экспортирование C++ классов&lt;/h4&gt;&lt;p&gt;Физически, C++ класс в скомпилированных модулях — это набор его методов, т.е. функций. Экспортирование класса означает экспортирование всех его методов, кроме приватных. Экспорт и импорт классов осуществляется тем же образом, что экспорт и импорт функций. Можно использовать тот же макрос DLLEXPORT, что и для функций.&lt;/p&gt;&lt;p&gt;Написав DLLEXPORT класса, у которого есть суперкласс, или неприватные поля — объекты каких-либо классов, или неприватные методы, возвращающие объекты каких-либо классов, то при компиляции вы заметите следующий варнинг:&lt;pre&gt;warning C4251: *** : class *** needs to have dll-interface to be used by clients of class ***&lt;/pre&gt;Это, в принципе, правильное предупреждение. Если Plugin импортирует класс «CoreClass», определённый в Core, то он конечно будет использовать его публичные (public) методы и публичные поля. Или отнаследуется и будет использовать защищённые (protected) методы и поля. Все классы, возникающие в процессе работы с CoreClass, должны быть доступны через импорт так же, как и сам CoreClass. И всех их надо тоже пометить как DLLEXPORT, о том и речь в варнинге C4251. Иначе Plugin ждёт ошибка линковки.&lt;/p&gt;&lt;p&gt;Если бы в проектах Core и Plugin не было бы классов-шаблонов, то на этом бы наше повествование благополучно закончилось. Но шаблоны у нас есть. Нет, сами классы, которые надо экспортировать, не являются шаблонами, но среди их методов есть такие, которые принимают или возвращают объекты шаблонных классов. Вот на тему этих шаблонных классов и возникает C4251.&lt;/p&gt;Можно, конечно, попытаться писать DLLEXPORT при объявлении шаблонов. В простых случаях это даже будет работать. Но на самом деле это лишено всякой логики. Шаблон — это не класс, он никогда не экспортируется. Экспортируется конкретный класс, который создаётся в тот момент, когда компилятор встречает инстанцированный шаблон. Выйдет так, что все классы-экземпляры «экспортируемого» шаблона будут экспортироваться из Core и импортироваться в Plugin. Это хорошо до тех пор, пока Plugin не воспользуется одним из шаблонов Core с параметром, которого нет в Core. (То есть, из Core попросту не экспортируется данный экземпляр шаблона). И даже тогда всё может быть в порядке; но рано или поздно окажется, что компилятор не в состоянии понять, что не надо импортировать данный экземпляр шаблона из Core, а надо его создать. В нашем случае это произошло, когда один «экспортированный» шаблон с неэкспортированным экземпляром инстанцировался в другом, тоже с неэкспортированным экземпляром. Дело закончилось ошибкой вида&lt;pre&gt;error LNK2001: unresolved external symbol "__declspec(dllimport) ..."&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;Есть ещё один способ обойти C4251, под названием «explicit template instantiation», т.е. явное экспортирование экземпляра шаблона. Он подробно разбирается в &lt;a href="http://www.unknownroad.com/rtfm/VisualStudio/warningC4251.html"&gt;этой статье&lt;/a&gt;, в применении к STL, с которой кстати приписать DLLEXPORT к объявлению шаблона нет возможности. Код получается немыслимо громоздким, а результаты — неутешительными. В статье отмечен тот факт, что экспортирование шаблонных классов в случае линковки нескольких (независимых друг от друга) DLL, содержащих одни и те же шаблонные классы, приведёт к ошибкам линковки вида&lt;pre&gt;LNK1169: one or more multiply defined symbols found&lt;/pre&gt;Да, link.exe, в отличие своего коллеги ld, не умеет определять и отбрасывать дубликаты функций в разных библиотеках.&lt;/p&gt;&lt;p&gt;Короче говоря, экспортировать шаблоны нет смысла и вообще нельзя. Есть два выхода из ситуации с C4251:&lt;ol&gt;&lt;li&gt;Игнорировать, благо это варнинг, а не ошибка. core.dll и plugin.dll просто будут иметь по пачке одинаковых методов для шаблонных классов. Конфликта при загрузке не будет, поскольку классы не экспортированы. Ошибки линковки тоже не будет, если все не-шаблонные классы имеют в своём заголовке DLLEXPORT.&lt;/li&gt;&lt;li&gt;Не экспортировать свои классы, а экспортировать только их методы, с помощью того же DLLEXPORT в объявлении каждого метода. Как ни странно, варнинг при этом исчезает. Странно, потому что опасность-то остаётся. Опасность заключается в том, что core.dll и plugin.dll всё равно будут иметь независимые реализации одних и тех же шаблонных классов; и если окажется так, что plugin.dll была собрана с иной версией этих классов, бинарно несовместимой с той, с которой была собрана core.dll, то они не смогут вместе работать с этими классами. В статье по ссылке выше сказано, что именно это может произойти с классами STL, когда одна библиотека собрана под VS7, а другая под VS8.&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;h2&gt;Прочие проблемы с Windows&lt;/h2&gt;&lt;p&gt;Ещё пара неприятностей, по числу которых Windows и так лидирует с большим отрывом:&lt;ol&gt;&lt;li&gt;Чтобы собрать plugin.dll, надо поставить в проекте Plugin зависимость на проект Core. Однако, если появилась необходимость завести в проектах Plugin и Core конфигурации сборки «Static», и собирать их в plugin-static.lib и core-static.lib, то зависимость останется, и её не убрать. core-static.lib будет вкомпиливаться в plugin-static.lib, вместе со своими глобальными переменными. Проект, который зависит от обеих библиотке Core и Plugin, при сборке обретёт две копии Core, и работать скорее всего не будет из-за наличия двух копий глобальных данных. Придётся создавать отдельные проекты CoreStatic и PluginStatic. Даже тогда, не получится оставить Core и Plugin пустыми и просто поставить им зависимости соответственно от CoreStatic и PluginStatic: при линковке Core и Plugin &lt;a href="http://stackoverflow.com/questions/3014474/not-all-symbols-of-an-dll-exported-class-is-exported-vs9"&gt;не будут экспортироваться символы&lt;/a&gt;. Придётся дублировать в Core/Plugin и CoreStatic/PluginStatic одинаковые наборы файлов.&lt;/li&gt;&lt;li&gt;В компиляторе VS есть опция &lt;a  href="http://msdn.microsoft.com/en-us/library/2kzt1wy3(v=vs.71).aspx"&gt;/MT&lt;/a&gt;, что означает, что рантайм (CRT) будет «вкомпилен» в библиотеку или программу. Это очень удобно, с учётом того что даже в самые современные версии Windows эта CRT не включена! В Windows есть только очень старая версия, а VS линкует с новой, которая доступна для Windows либо в составе VS, либо в виде &lt;a href="http://www.microsoft.com/downloads/en/details.aspx?FamilyID=9b2da534-3e03-4391-8a4d-074b9f2bc1bf&amp;amp;displaylang=en"&gt;отдельного пакета&lt;/a&gt;. Так вот, опция /MT в случае двух связанных между собой DLL приведёт к &lt;a href="http://msdn.microsoft.com/en-us/library/ms235460(VS.80).aspx"&gt;ошибке&lt;/a&gt; во времени выполнения. Она неизбежно случится, когда например память выделена в одной DLL, а освобождается (или даже копируется) другой DLL. Остаётся использовать опцию /MD и обеспечивать затем присутствие «Redistributable Package» на машине пользователя.&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-5154038588099120518?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/5154038588099120518/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=5154038588099120518' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/5154038588099120518'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/5154038588099120518'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2010/12/blog-post.html' title='О плагинах и линковке'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-7095664718616776816</id><published>2010-07-13T18:36:00.019+04:00</published><updated>2010-07-28T19:02:13.249+04:00</updated><title type='text'>Навигация в мире органических соединений</title><content type='html'>&lt;div style="text-align: left;"&gt;Сколько органических соединений вы знаете? А сколько вы знаете лекарств? Каждое лекарство, не считая тех, что производятся из растений, преставляет собой комплект из действующего вещества и оболочки/растворителя, в которой/ом оно проходит свой путь до усваивания организмом пациента. Действующее вещество в лекарственном препарате — это одно конкретное органическое соединение&lt;a href="http://shmat-razum.blogspot.com/2010/07/blog-post.html#fn1" name="fn1b"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;. Количество известных органических соединений, которые можно добыть или синтезировать, превышает 30 миллионов, а количество лекарств на рынке — всего несколько тысяч. Создание любого нового лекарства занимает от 10 до 15 лет и является очень дорогостоящим. Расходы на программное обеспечение составляют в этой индустрии (как и почти в любой другой) весьма скромную долю.&lt;/div&gt;&lt;p&gt; Для программистов это не беда, а большая удача: средства на разработку программ выделяются фармакологическими компаниями так щедро, как только возможно, ибо если случится так, что какая-нибудь программа, созданная например за два года, сократит 10-15-летний цикл создания лекарства &lt;i&gt;хотя бы&lt;/i&gt; на две недели, то траты окажутся более чем оправданы. &lt;/p&gt; &lt;p&gt; Роль компьютеров в этом процессе за последние два десятилетия стремительно возросла, и вот почему. Производство лекарственного средства — комплексная задача, в которой есть место пробам и ошибкам. &lt;/p&gt; &lt;p&gt; Представим, что усилиями биологов в организме выявлен «нездоровый» белок, вызывающий болезнь или болезненные ощущения. Дело за малым — найти вещество, которое разрушит или заблокирует белок, не причинив вреда организму в процессе. Затруднение состоит в том, что на эту роль может годиться одна молекула из 30 миллионов. Или ещё не открытая молекула. Современные технологии массового синтеза (т.н. комбинаторная, или &lt;a href="http://wsyachina.narod.ru/chemistry/compatible_chemistry.html"&gt;сочетательная химия&lt;/a&gt;) и массовых биохимических опытов (&lt;a href="http://en.wikipedia.org/wiki/High-throughput_screening"&gt;HTS&lt;/a&gt;), позволяют за короткие сроки получать сотни тысяч новых молекул и гигабайты экспериментальных данных. &lt;/p&gt; &lt;p&gt; Опишем карьеру лекарственного вещества от конца к началу. До того, как попасть на прилавки аптек, лекарство должно пройти клинические испытания (clinical trials) на пациентах, под присмотром врачей. Это представляет определённый риск, поэтому до пациентов доходят лишь немногие вещества, прошедшие доклинические тесты (preclinical testing) на животных. Их тоже берегут, поэтому доклиническим испытаниям предшествуют массовые опыты на отдельных живых клетках (in vitro, лат. «в стекле», т.е. в пробирке). Но и в пробирки не бросаются все молекулы подряд. Люди должны выбирать нужные (перспективные для лечения данной болезни) вещества и отбрасывать заведомо неподходящие, и без компьютера им этого не сделать&lt;a href="http://shmat-razum.blogspot.com/2010/07/blog-post.html#fn2" name="fn2b"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;. На самом деле, и с ним не очень удобно. Эффективная система навигации по химическим содинениям пока ещё не создана; и о перспективах создания таковой сейчас пойдёт речь&lt;a href="http://shmat-razum.blogspot.com/2010/07/blog-post.html#fn3" name="fn3b"&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;. &lt;/p&gt; &lt;h2&gt;Поиск химических соединений в базах данных&lt;/h2&gt; &lt;p&gt; Состояние систем поиска химических соединений в наши дни, увы, примерно соответствует состоянию поисковых систем во всемирной паутине в &lt;nobr&gt;90-е&lt;/nobr&gt; годы&lt;a href="http://shmat-razum.blogspot.com/2010/07/blog-post.html#fn4" name="fn4b"&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;. Да, именно так. Примитивные алгоритмы поиска (и поиск ведётся далеко не по всем имеющимся источникам); весьма вялая поддержка языковой грамматики; никакого ранжирования результатов. &lt;/p&gt;&lt;p&gt; Допустим, стало известно какое-то вещество, эффективно подавляющее проблемный белок&lt;a href="http://shmat-razum.blogspot.com/2010/07/blog-post.html#fn5" name="fn5b"&gt;&lt;sup&gt;5&lt;/sup&gt;&lt;/a&gt;. Пилюлю с веществом скормили крысе; та пошла зелёными пятнами и спустя час сдохла. Есть основания полагать, что данное вещество токсично и людям его давать нельзя. Но можно попробовать найти вещества, близкие ему по структуре. Если повезёт, они окажутся менее токсичными при той же эффективности. &lt;/p&gt;&lt;p&gt; Например, амфетамин является подструктурой мезокарба, и оба препарата подавляют реакцию обратного захвата дофамина (dopamin reuptake) в мозге, что ведёт к повышению активности. Но мезокарб, в отличие от амфетамина, не вызывает тахикардии и повышения артериального давления. &lt;/p&gt;&lt;p&gt;&lt;a href="http://3.bp.blogspot.com/_BaonBoyqin8/TDx7KMZsl4I/AAAAAAAAADU/Qmk1aeTqulQ/s1600/amphetamine.png"&gt;&lt;img src="http://3.bp.blogspot.com/_BaonBoyqin8/TDx7KMZsl4I/AAAAAAAAADU/Qmk1aeTqulQ/s400/amphetamine.png" alt="" id="BLOGGER_PHOTO_ID_5493401060543076226" border="0" style="margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; display: block; text-align: center; cursor: pointer; width: 183px; height: 90px; " /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://2.bp.blogspot.com/_BaonBoyqin8/TDx7XzGg9gI/AAAAAAAAADc/g4ufOAQyR4Q/s1600/mesocarb.png"&gt;&lt;img src="http://2.bp.blogspot.com/_BaonBoyqin8/TDx7XzGg9gI/AAAAAAAAADc/g4ufOAQyR4Q/s400/mesocarb.png" alt="" id="BLOGGER_PHOTO_ID_5493401294269904386" style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 163px;" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style="text-align: center;"&gt;&lt;i&gt;Амфетамин и мезокарб&lt;/i&gt;&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt; Вообще говоря, нередко случается так, что добавление или удаление небольшого фрагмента идёт молекуле (точнее, пациентам) на пользу. Чем добавлять и удалять всевозможные фрагменты вручную, проще запустить поиск по базе данных и найти все молекулы, содержащие данную как подструктуру или все молекулы, содержащиеся в данной. Соответствующие виды поиска называются «подструктурный поиск» (substructure search) и «надструктурный поиск» (superstructure search). &lt;/p&gt;&lt;a href="http://1.bp.blogspot.com/_BaonBoyqin8/TDx7oNPGZ8I/AAAAAAAAADk/6zTziTjeZi8/s1600/substructure_search.png"&gt;&lt;img src="http://1.bp.blogspot.com/_BaonBoyqin8/TDx7oNPGZ8I/AAAAAAAAADk/6zTziTjeZi8/s400/substructure_search.png" alt="" id="BLOGGER_PHOTO_ID_5493401576163141570" style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 280px; height: 400px;" border="0" /&gt;&lt;/a&gt;&lt;div&gt;&lt;div style="text-align: center;"&gt;&lt;i&gt;Подструктурный поиск в сервисе &lt;a href="http://bingo-demo.scitouch.net/"&gt;Bingo&lt;/a&gt; с амфетамином в качестве запроса.&lt;/i&gt;&lt;/div&gt; &lt;p&gt;Более общий критерий структурного сходства молекул основан на количестве различных фрагментов, которые присутствуют одновременно в обеих молекулах. Поиск молекул по такому критерию назывется &lt;a href="http://ru.wikipedia.org/wiki/%D0%9C%D0%BE%D0%BB%D0%B5%D0%BA%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D0%BE%D0%B5_%D0%BF%D0%BE%D0%B4%D0%BE%D0%B1%D0%B8%D0%B5"&gt;поиском по сходству&lt;/a&gt; (similarity search).&lt;/p&gt;&lt;p&gt;&lt;a href="http://3.bp.blogspot.com/_BaonBoyqin8/TDx72W47eNI/AAAAAAAAADs/_tApzVH1csM/s1600/salicylamide.png"&gt;&lt;img src="http://3.bp.blogspot.com/_BaonBoyqin8/TDx72W47eNI/AAAAAAAAADs/_tApzVH1csM/s400/salicylamide.png" alt="" id="BLOGGER_PHOTO_ID_5493401819272673490" style="margin: 0px auto 10px; text-align: left; display: block; cursor: pointer; width: 136px; height: 148px;" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;a href="http://4.bp.blogspot.com/_BaonBoyqin8/TDx8HOAFTAI/AAAAAAAAAD0/Lh1W5heuQFg/s1600/aspirin.png"&gt;&lt;img src="http://4.bp.blogspot.com/_BaonBoyqin8/TDx8HOAFTAI/AAAAAAAAAD0/Lh1W5heuQFg/s400/aspirin.png" alt="" id="BLOGGER_PHOTO_ID_5493402108944534530" style="margin: 0px auto 10px; text-align: left; display: block; cursor: pointer; width: 214px; height: 148px;" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;i&gt;Салициламид и ацетилсалициловая кислота — схожие по химическим свойствам соединения, использующиеся в медицине. Последнее более известно под названием «аспирин».&lt;/i&gt;&lt;/div&gt; &lt;p&gt; Найденные в базе данных молекулы не очень интересны сами по себе; они интересны в контексте. В каких медицинских и химических статьях упомянуто данное соединение? Есть ли на него патент? Представлено ли оно в коммерческих каталогах? Известны ли его свойства, такие как растворимость, кислотность, токсичность, внутренняя абсорбция и другие? Известна ли химическая реакция его синтеза? Доступны ли исходные компоненты этой химической реакции? На эти вопросы и на многие другие должна давать ответ система поиска. &lt;/p&gt; &lt;p&gt; Перечислим наиболее популярные поисковики химических соединений: &lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://pubchem.ncbi.nlm.nih.gov/"&gt;PubChem&lt;/a&gt; — база данных из 27 миллионов соединений с богатыми возможностями для поиска: по номеру, по названию, по структурной формуле, по подструктуре и по сходству. Химические свойства также можно задавать в качестве дополнительных критериев поиска (например, ограничиться только молекулами, молекулярная масса которых не превышает 120). Базу постоянно пополняют &lt;a href="http://pubchem.ncbi.nlm.nih.gov/sources/sources.cgi"&gt;более 80 организаций&lt;/a&gt;.&lt;/li&gt; &lt;li&gt;&lt;a href="http://chemspider.com/"&gt;ChemSpider&lt;/a&gt; содержит 25 миллионов соединений и имеет важное отличие от PubChem: добавлять молекулы и обновлять информацию о них здесь могут не только избранные организации, но и простые пользователи. Вместе с последними, список источников ChemSpider составляет &lt;a href="http://www.chemspider.com/DataSources.aspx"&gt;почти 300 пунктов&lt;/a&gt;. Поиск соединений в ChemSpider не имеет такого количества опций, как в PubChem; в частности, отсутствует поиск по сходству.&lt;/li&gt; &lt;li&gt;&lt;a href="http://emolecules.com/"&gt;eMolecules&lt;/a&gt; — компиляция из 7 миллионов соединений, собранных в &lt;a href="http://www.emolecules.com/cgi-bin/sources"&gt;150 коммерческих каталогах&lt;/a&gt;. Возможности поиска минимальны; никакой информации о соединениях, кроме ссылок на каталоги, сайт не показывает. Это скорее платформа для продавцов химических веществ, нежели поисковая система для исследователей.&lt;/li&gt; &lt;/ul&gt; &lt;h2&gt;Поиск химических соединений по научным публикациям.&lt;/h2&gt; &lt;p&gt; Пионерами поиска в научных работах по химии были создатели химической реферативной службы (Chemical Abstracts Service, &lt;a href="http://cas.org/"&gt;CAS&lt;/a&gt;), существующей с 1907 года. В этой службе ведётся учёт всех известных химических соединений. Тысячи людей в течение десятков лет вручную составляют библиографические справки и заполняют базу данных &lt;a href="http://www.cas.org/products/scifindr/index.html"&gt;SciFinder&lt;/a&gt;, отдельного продукта CAS для поиска публикаций. Аналогичная база данных, поддерживаемая издательством Elsevier, называется &lt;a href="http://www.info.crossfirebeilstein.com/"&gt;«Crossfire Beilstein»&lt;/a&gt;. Сервисы PubChem и ChemSpider также выдают пользователю вместе с каждой найденной молекулой список публикаций, к которым данная молекула может иметь отношение; но возможности для поиска собственно публикаций в этих сервисах не очень развиты. &lt;/p&gt;&lt;a href="http://2.bp.blogspot.com/_BaonBoyqin8/TDx8pA0T11I/AAAAAAAAAD8/R_hx3T4Tins/s1600/scifinder.png"&gt;&lt;img src="http://2.bp.blogspot.com/_BaonBoyqin8/TDx8pA0T11I/AAAAAAAAAD8/R_hx3T4Tins/s400/scifinder.png" alt="" id="BLOGGER_PHOTO_ID_5493402689521047378" style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 387px;" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="text-align: center;"&gt;&lt;i&gt;SciFinder&lt;/i&gt;&lt;/div&gt; &lt;p&gt; Как же, наконец, отправить на отдых тысячи «индексаторов», от рассвета до заката читающих статьи и заполняющих библиографические базы? Эта задача несколько сложнее, чем найти слово «парацетамол» по текстам статей. Во-первых, само вещество может иметь несколько названий (пример альтернативного названия парацетамола — «N-(4-гидроксифенил)ацетамид»). Во-вторых, лекарство, содержащие это вещество, может упоминаться под разными торговыми марками (в данном случае «Панадол», «Эффералган» и десяток других). В третьих, вещество может быть не написано, а &lt;i&gt;нарисовано&lt;/i&gt; в статье. В растровом виде (в старых отсканированных статьях), или в векторном (начиная с &lt;nobr&gt;90-х&lt;/nobr&gt; годов). Программы по автоматическому распознаванию рисунков с молекулами сегодня находятся в плачевном состоянии. Вот наиболее известные из них:&lt;/p&gt;&lt;ul&gt; &lt;li&gt;&lt;a href="http://www.simbiosys.ca/clide/"&gt;CLiDE&lt;/a&gt; канадской фирмы SimBioSys&lt;/li&gt; &lt;li&gt;&lt;a href="http://cactus.nci.nih.gov/osra/"&gt;OSRA&lt;/a&gt; — проект с открытыми исходниками нашего соотечественника, работающего в США&lt;/li&gt; &lt;li&gt;&lt;a href="http://www.scai.fraunhofer.de/en/business-research-areas/bioinformatics/products/chemocr.html"&gt;ChemoCR&lt;/a&gt;— проект Марка Циммермана из института Фраунгофера в Германии&lt;/li&gt; &lt;/ul&gt; CLiDE — наиболее развитая программа из перечисленных, но она нередко ошибается, требует вмешательства человека и «не знает» многих особенностей молекул. OSRA активно развивается, но обладает на данный момент худшим качеством распознавания. ChemoCR, похоже, находится в перманентной закрытой разработке: эту программу никто никогда не видел, тем не менее доступно немалое количество публикаций по алгоритмам, используемых в ней. Указанные программы ещё менее пригодны к распознаванию более сложных химических объектов, как-то: химических реакций, таблиц с заместителями. Комбинированный семантический анализ текста и рисунков (например, «молекула на рис. 10a имеет показатель LD50 равный 5.6 г/кг для взрослых крыс») вообще нигде не реализован. &lt;h2&gt;Планирование синтеза&lt;/h2&gt; &lt;p&gt; Схема синтеза химического соединения представляет собой цепочку химических реакций. &lt;/p&gt;&lt;a href="http://1.bp.blogspot.com/_BaonBoyqin8/TDx8161rEBI/AAAAAAAAAEE/gFFdBJI0n6k/s1600/paracetamol_synthesis.png"&gt;&lt;img src="http://1.bp.blogspot.com/_BaonBoyqin8/TDx8161rEBI/AAAAAAAAAEE/gFFdBJI0n6k/s400/paracetamol_synthesis.png" alt="" id="BLOGGER_PHOTO_ID_5493402911254450194" style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 211px;" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="text-align: center;"&gt;&lt;i&gt;Многоступенчатая реакция синтеза парацетамола&lt;/i&gt;&lt;/div&gt; &lt;p&gt; Стало быть, если соединение нельзя заказать через каталоги, можно попытаться осуществить синтез самостоятельно, имея схему реакции, где в правой части стоит искомое вещество. Исходные компоненты реакции (прекурсоры, или предшественники) придётся всё же раздобыть. Или синтезировать. &lt;/p&gt; &lt;p&gt; Базы данных с органическими реакциями имеют размер на три порядка меньший, чем с молекулами; однако, многие записи в них задают на самом деле не одну реакцию, а группу реакций, объединённых некоей неподвижной частью, на месте которой может быть всё что угодно. &lt;/p&gt;&lt;a href="http://3.bp.blogspot.com/_BaonBoyqin8/TDx9dSIvx_I/AAAAAAAAAEU/Y1dt_uB1H0g/s1600/brook_rearrangement.png"&gt;&lt;img src="http://3.bp.blogspot.com/_BaonBoyqin8/TDx9dSIvx_I/AAAAAAAAAEU/Y1dt_uB1H0g/s400/brook_rearrangement.png" alt="" id="BLOGGER_PHOTO_ID_5493403587523364850" style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 144px;" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="text-align: center;"&gt;&lt;i&gt;Перегруппировка Брука&lt;/i&gt;&lt;/div&gt;&lt;a href="http://2.bp.blogspot.com/_BaonBoyqin8/TDx9njEdQeI/AAAAAAAAAEc/IU7IKdevUH4/s1600/claisen_rearrangement.png"&gt;&lt;img src="http://2.bp.blogspot.com/_BaonBoyqin8/TDx9njEdQeI/AAAAAAAAAEc/IU7IKdevUH4/s400/claisen_rearrangement.png" alt="" id="BLOGGER_PHOTO_ID_5493403763867468258" style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 141px;" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="text-align: center;"&gt;&lt;i&gt;Перегруппировка Кляйзена&lt;/i&gt;&lt;/div&gt; Благодаря этому обстоятельству, значительно увеличивается разнообразие синтезируемых веществ, и в то же время усложняется поиск. Вкупе с «многоступенчатостью», планирование синтеза становится сложной задачей. В некоторой степени эта задача решена в сервисе &lt;a href="http://reaxys.com/"&gt;Reaxys&lt;/a&gt;, который, увы, доступен только по подписке. &lt;p&gt;&lt;/p&gt;&lt;a href="http://3.bp.blogspot.com/_BaonBoyqin8/TDx9zGcgCmI/AAAAAAAAAEk/vYmQk_eL09Y/s1600/reaxys.png"&gt;&lt;img src="http://3.bp.blogspot.com/_BaonBoyqin8/TDx9zGcgCmI/AAAAAAAAAEk/vYmQk_eL09Y/s400/reaxys.png" alt="" id="BLOGGER_PHOTO_ID_5493403962342115938" style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 245px;" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="text-align: center;"&gt;&lt;i&gt;Reaxys&lt;/i&gt;&lt;/div&gt; &lt;h2&gt;Заключение&lt;/h2&gt; &lt;p&gt;Положение дел с поиском органических соединений не отвечает современным нуждам. Да, есть отдельные полезные сайты, успешно решающие отдельные части задачи, но не существует пока ни химического аналога Google в сфере поиска, ни аналога Википедии, позволившего бы учёным со всего мира объединить свои усилия по описанию свойств миллионов органических веществ.&lt;/p&gt; &lt;p&gt;В настоящий момент наиболее перспективная система, которая может в будущем стать «Гуглом и Википедией» химиков — это ChemSpider. Её создатели явно принимают в расчёт ключевые элементы успеха глобальных сервисов: кросс-платформенность (работает через броузер и даже с мобильных устройств), доступность для каждого, богатые возможности, «дружба» с многими другими сервисами (включая PubChem), право пользователей публиковать свой контент. ChemSpider имеет недостатки, как общие для индустрии, так и свои собственные, но движется в правильном направлении. &lt;/p&gt;&lt;a href="http://4.bp.blogspot.com/_BaonBoyqin8/TDx9MC29tVI/AAAAAAAAAEM/wDwe5P8e6dc/s1600/chemspider.png"&gt;&lt;img src="http://4.bp.blogspot.com/_BaonBoyqin8/TDx9MC29tVI/AAAAAAAAAEM/wDwe5P8e6dc/s400/chemspider.png" alt="" id="BLOGGER_PHOTO_ID_5493403291364472146" style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 286px;" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="text-align: center;"&gt;&lt;i&gt;ChemSpider&lt;/i&gt;&lt;/div&gt; &lt;p&gt; Попытки создания химических поисковиков также предпринимались и предпринимаются в стенах фармацевтических компаний, для внутреннего пользования. Сказать о них нечего, кроме того, что любая ценная информация рано или поздно выходит на свет, и там, находясь в общем доступе, постепенно очищается и повышается в качестве; а информация «только для своих» обречена стать бесполезной. Когда вы в последний раз находили что-нибудь дельное в локальной сети своей организации? &lt;/p&gt; &lt;h2&gt;Благодарности&lt;/h2&gt; Автор признателен Н. Велецкому и Д. Лушникову за ценные замечания по тексту статьи. &lt;h2&gt;Сноски&lt;/h2&gt; &lt;p style="font-size: 80%;"&gt; &lt;a href="http://shmat-razum.blogspot.com/2010/07/blog-post.html#fn1b" name="fn1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; В редких лекарствах их два, например в бисептоле. &lt;/p&gt; &lt;p style="font-size: 80%;"&gt; &lt;a href="http://shmat-razum.blogspot.com/2010/07/blog-post.html#fn2b" name="fn2"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; Следует заметить, что без компьютеров бы не появилось такое количество данных, которое не под силу обработать вручную; возможно, компьютеры в этой истории продвигают сами себя. &lt;/p&gt; &lt;p style="font-size: 80%;"&gt; &lt;a href="http://shmat-razum.blogspot.com/2010/07/blog-post.html#fn3b" name="fn3"&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; На прочих стадиях роль вычислительных машин не менее важна; однако возникающие там задачи лежат за рамками данной статьи. &lt;/p&gt; &lt;p style="font-size: 80%;"&gt; &lt;a href="http://shmat-razum.blogspot.com/2010/07/blog-post.html#fn4b" name="fn4"&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt; (до появления Google). Тогдашних «королей» информационного поиска (Altavista, Lycos, Rambler) мало кто помнит; в том числе оттого, что они были практически бесполезны. &lt;/p&gt;&lt;p style="font-size: 80%;"&gt; &lt;a href="http://shmat-razum.blogspot.com/2010/07/blog-post.html#fn5b" name="fn5"&gt;&lt;sup&gt;5&lt;/sup&gt;&lt;/a&gt; Взаимодействие молекулы с белком тоже можно моделировать на компьютере. Этому посвящена отдельная область вычислительной химии под английским названием «docking». &lt;/p&gt; &lt;/div&gt;&lt;h2&gt;Примеры неудовлетворительной работы OSRA&lt;/h2&gt; (Картинки выложены по просьбе Игоря в комментариях). На первых двух и на последней OSRA не выдаёт никакого результата, на 3-й, 4-й и 5-й выдаёт неверный результат.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_BaonBoyqin8/TECQlKiQV-I/AAAAAAAAAEs/fs2071fAbpQ/s1600/osra_fail_1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 114px; height: 111px;" src="http://3.bp.blogspot.com/_BaonBoyqin8/TECQlKiQV-I/AAAAAAAAAEs/fs2071fAbpQ/s400/osra_fail_1.png" alt="" id="BLOGGER_PHOTO_ID_5494550513549596642" border="0" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_BaonBoyqin8/TECRrSGbSXI/AAAAAAAAAFE/BElbQ50CS-A/s1600/osra_fail_3.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 200px; height: 84px;" src="http://3.bp.blogspot.com/_BaonBoyqin8/TECRrSGbSXI/AAAAAAAAAFE/BElbQ50CS-A/s200/osra_fail_3.png" alt="" id="BLOGGER_PHOTO_ID_5494551718171199858" border="0" /&gt;&lt;/a&gt;&lt;div&gt;&lt;div style="text-align: center;"&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_BaonBoyqin8/TECQ6KJuaxI/AAAAAAAAAE0/e6fBUJZK2Xw/s1600/osra_fail_2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 172px; height: 86px;" src="http://1.bp.blogspot.com/_BaonBoyqin8/TECQ6KJuaxI/AAAAAAAAAE0/e6fBUJZK2Xw/s200/osra_fail_2.png" alt="" id="BLOGGER_PHOTO_ID_5494550874223962898" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_BaonBoyqin8/TECReAMjgaI/AAAAAAAAAE8/zxOn_iJyOJI/s1600/osra_incorrect_1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 131px; height: 136px;" src="http://2.bp.blogspot.com/_BaonBoyqin8/TECReAMjgaI/AAAAAAAAAE8/zxOn_iJyOJI/s200/osra_incorrect_1.png" alt="" id="BLOGGER_PHOTO_ID_5494551490026766754" border="0" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_BaonBoyqin8/TECR_I9buPI/AAAAAAAAAFM/LfLPk5xHLY4/s1600/osra_incorrect_2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 128px; height: 123px;" src="http://2.bp.blogspot.com/_BaonBoyqin8/TECR_I9buPI/AAAAAAAAAFM/LfLPk5xHLY4/s200/osra_incorrect_2.png" alt="" id="BLOGGER_PHOTO_ID_5494552059314944242" border="0" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="text-align: center;"&gt;&lt;span class="Apple-style-span"  style="color:#0000EE;"&gt;&lt;u&gt;&lt;span class="Apple-style-span" style="-webkit-text-decorations-in-effect: none; "&gt;&lt;u&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 0); -webkit-text-decorations-in-effect: none; "&gt;&lt;a href="http://1.bp.blogspot.com/_BaonBoyqin8/TEIWJ6Z1OuI/AAAAAAAAAFU/I6-ksJlZIv8/s1600/osra_fail_3.png"&gt;&lt;img src="http://1.bp.blogspot.com/_BaonBoyqin8/TEIWJ6Z1OuI/AAAAAAAAAFU/I6-ksJlZIv8/s200/osra_fail_3.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5494978854897662690" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 200px; height: 182px; " /&gt;&lt;/a&gt;&lt;/span&gt;&lt;/u&gt;&lt;/span&gt;&lt;/u&gt;&lt;/span&gt;&lt;/div&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_BaonBoyqin8/TECR_I9buPI/AAAAAAAAAFM/LfLPk5xHLY4/s1600/osra_incorrect_2.png"&gt;&lt;/a&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-7095664718616776816?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/7095664718616776816/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=7095664718616776816' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/7095664718616776816'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/7095664718616776816'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2010/07/blog-post.html' title='Навигация в мире органических соединений'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_BaonBoyqin8/TDx7KMZsl4I/AAAAAAAAADU/Qmk1aeTqulQ/s72-c/amphetamine.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-918563591801477920</id><published>2010-05-30T15:13:00.024+04:00</published><updated>2011-01-09T21:02:25.392+03:00</updated><title type='text'>Symbolic Integration</title><content type='html'>Всем известно, что нахождение производной функции является тривиальной задачей, и многие не без оснований полагают, что нахождение неопределённого интеграла (первообразной) — задача нетривиальная и даже неразрешимая. На второй пункт, однако, есть &lt;a href="http://lj.rossia.org/users/tiphareth/457266.html"&gt;разные точки зрения&lt;/a&gt;, и сейчас мы их разберём в небольшой историко-математической справке. Но сначала определимся с тем, о каких функциях идёт речь.&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;&lt;h2&gt;Классы функций одной переменной&lt;/h2&gt;Чем шире класс функции, тем сложнее брать интеграл. Например, интегрирование полиномов одной переменной&lt;br /&gt;&lt;table border="0" width="100%"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;table align="center" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt;p(x) = a&lt;sub&gt;n&lt;/sub&gt; x&lt;sup&gt;n&lt;/sup&gt; + ... + a&lt;sub&gt;1&lt;/sub&gt; x + a&lt;sub&gt;0&lt;/sub&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;не составляет никакого труда. Уже некоторую сложность представляет интегрирование рациональной функции&lt;table border="0"width="100%"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;table align="center" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt;r(x) = &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt;a&lt;sub&gt;n&lt;/sub&gt; x&lt;sup&gt;n&lt;/sup&gt; + ... + a&lt;sub&gt;1&lt;/sub&gt; x + a&lt;sub&gt;0&lt;/sub&gt;&lt;div class="hrcomp"&gt;&lt;hr noshade="noshade" size="1"&gt;&lt;/div&gt;b&lt;sub&gt;m&lt;/sub&gt; x&lt;sup&gt;m&lt;/sup&gt; + ... + b&lt;sub&gt;1&lt;/sub&gt; x + b&lt;sub&gt;0&lt;/sub&gt;&lt;/td&gt;&lt;td nowrap="nowrap"align="center"&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;Если степень числителя n меньше степени знаменателя m, и коэффициенты лежат в поле вещественных чисел, то интегрирование прозводится следующим образом: знаменатель раскладывается на множители вида (x - x&lt;sub&gt;i&lt;/sub&gt;)&lt;sup&gt;k&lt;sub&gt;i&lt;/sub&gt;&lt;/sup&gt; и (x&lt;sup&gt;2&lt;/sup&gt; + b&lt;sub&gt;j&lt;/sub&gt; x + c&lt;sub&gt;j&lt;/sub&gt;)&lt;sup&gt;l&lt;sub&gt;j&lt;/sub&gt;&lt;/sup&gt;, где каждый x&lt;sub&gt;i&lt;/sub&gt; — корень кратности k&lt;sub&gt;i&lt;/sub&gt;, а b&lt;sub&gt;j&lt;/sub&gt; и c&lt;sub&gt;j&lt;/sub&gt; задают пару сопряжённых комплексных корней кратности l&lt;sub&gt;j&lt;/sub&gt;. Затем &lt;a href="http://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%BD%D0%B5%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D1%91%D0%BD%D0%BD%D1%8B%D1%85_%D0%BA%D0%BE%D1%8D%D1%84%D1%84%D0%B8%D1%86%D0%B8%D0%B5%D0%BD%D1%82%D0%BE%D0%B2"&gt;методом неопределённых коэффициентов&lt;/a&gt; дробь r(x) раскладывается на сумму конечного количества дробей вида &lt;table border="0" width="100%"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;table align="center" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt;&lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt;A&lt;sub&gt;i&lt;/sub&gt;&lt;div class="hrcomp"&gt;&lt;hr noshade="noshade" size="1"&gt;&lt;/div&gt;(x-x&lt;sub&gt;i&lt;/sub&gt;)&lt;sup&gt;k&lt;sub&gt;i&lt;/sub&gt;&lt;/sup&gt;&lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; и &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt;B&lt;sub&gt;j&lt;/sub&gt; x+C&lt;sub&gt;j&lt;/sub&gt; &lt;div class="hrcomp"&gt;&lt;hr noshade="noshade" size="1"&gt;&lt;/div&gt;(x&lt;sup&gt;2&lt;/sup&gt; +b&lt;sub&gt;j&lt;/sub&gt;x + c&lt;sub&gt;j&lt;/sub&gt;)&lt;sup&gt;l&lt;sub&gt;j&lt;/sub&gt;&lt;/sup&gt;&lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; Для каждого из трёх видов слагаемых (трёх, т.к. дробь с B&lt;sub&gt;j&lt;/sub&gt;x + C&lt;sub&gt;j&lt;/sub&gt; разбивается на отдельные слагаемые с B&lt;sub&gt;j&lt;/sub&gt;x и C&lt;sub&gt;j&lt;/sub&gt;) интеграл находится с помощью &lt;a href="http://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B0%D0%BB%D0%BE%D0%B2_%D0%BE%D1%82_%D1%80%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D1%85_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B9"&gt;формул из справочника&lt;/a&gt;. В этих формулах встречаются логарифмы, радикалы, модули и арктангенсы, поэтому интеграл рациональной функции в общем случае не является рациональной функцией.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;Если степень числителя n больше или равна степени знаменателя m, то мы можем разделить числитель на знаменатель и получить сумму полинома и «правильной» дроби. Если нас интересуют функции с комплексными коэффициентами, то всё делается точно так же, как и с вещественными, с тем отличием, что квадратичные члены из разложения знаменателя будут упразднены.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;Кажется, что интегрирование рациональных функций — уже чисто техническая задача? Это не совсем так. Если степень многочлена больше 4, то его корни в общем случае не записываются в радикалах. Это доказал &lt;a href="http://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%90%D0%B1%D0%B5%D0%BB%D1%8F_%E2%80%94_%D0%A0%D1%83%D1%84%D1%84%D0%B8%D0%BD%D0%B8"&gt;Абель&lt;/a&gt; в 1826 г. Например, вещественный корень уравнения «x&lt;sup&gt;5&lt;/sup&gt; + x + a = 0» невозможно записать, пользуясь арифметическими операциями и операцией извлечения корня.&lt;a href="#tthFtNtAAC" name="tthFrefAAC"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Числа, являющиеся корнями полиномов с рациональными коэффициентами, называются алгебраическими числами. Они приводят нас к обобщению класса рациональных функций — алгебраическим функциям.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Алгебраическая функция y от переменной x задаётся неявным образом, через полином от x и y:&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;&lt;table border="0" width="100%"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; &lt;table align="center" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt; p&lt;sub&gt;n&lt;/sub&gt;(x) y&lt;sup&gt;n&lt;/sup&gt; + ... + p&lt;sub&gt;1&lt;/sub&gt;(x) y + p&lt;sub&gt;0&lt;/sub&gt;(x) = 0&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Для примера преобразуем указанное выше уравнение 5-й степени в алгебраическую функцию y от переменной x:&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;&lt;table border="0" width="100%"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; &lt;table align="center" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt; y&lt;sup&gt;5&lt;/sup&gt;+y+x=0&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Алгебраические числа и функции тоже не являют собой предел выразительности математической записи. Хорошо известны трансцендентные числа (e, &amp;pi;) и трансцендентные функции (экспонента, логарифм, тригонометрические функции), которые невозможно представить через полиномиальные уравнения.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; К классу элементарных функций относятся алгебраические функции, экспонента, логарифм и любые их конечные композиции. Подразумевается оперирование над полем комплексных чисел, что означает, что элементарны и все тригонометрические функции, т.к. они выражаются через комбинации e&lt;sup&gt;ix&lt;/sup&gt; и e&lt;sup&gt;&lt;span style="font-family:symbol;"&gt;-&lt;/span&gt;ix&lt;/sup&gt;. Обратные тригонометрические функции, соответственно, выражаются через логарифмы. Показательная функция a&lt;sup&gt;x&lt;/sup&gt; выражается через экспоненту и логарифм: a&lt;sup&gt;x&lt;/sup&gt; = e&lt;sup&gt;x ln(a)&lt;/sup&gt;.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Среди элементарных функций иногда выделяют чисто трансцендентные функции — это элементарные функции, которые возможно определить без участия алгебраических функций (рациональные функции допускаются). В некоторых источниках «элементарными» функциями называют чисто трансцендентные.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Интегрирование в теории&lt;/h2&gt; С рациональными функциями, несмотря на указанные выше неприятности, принципиальных трудностей нет: интеграл всегда существует в виде элементарной функции, и есть процедура, которая его гарантированно найдёт.&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;&lt;br /&gt;С алгебраическими функциями дело обстоит сложнее.&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;&lt;br /&gt;Лапласом было сделано следующее наблюдение: при дифференцировании алгебраические функции превращаются в алгебраические функции, а логарифмы алгебраических функций превращаются тоже в алгебраические функции. Рассуждая в обратную сторону (и зная вместе с тем, что никакие другие известные функции, включая например экспоненту, не дают при дифференцировании алгебраических функций), Лаплас предположил, что интеграл алгебраической функции (если он существует как элементарная функция) выражается в виде суммы алгебраической функции и конечного числа логарифмов алгебраических функций с константными множителями.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Это даёт следующую общую запись для интегрируемых алгебраических функций:&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; &lt;a name="laplace"&gt; &lt;/a&gt; &lt;table border="0" width="100%"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; &lt;table align="center" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt; f(x) = v&lt;sub&gt;0&lt;/sub&gt;'(x) + &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;small&gt;m&lt;/small&gt;&lt;!--sup --&gt;&lt;br /&gt;&lt;span style="font-size:+3;"&gt;Σ&lt;br /&gt;&lt;/span&gt;&lt;small&gt;i=1&lt;/small&gt; &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; c&lt;sub&gt;i&lt;/sub&gt; &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; v&lt;sub&gt;i&lt;/sub&gt;'(x) &lt;div class="hrcomp"&gt;&lt;hr noshade="noshade" size="1"&gt;&lt;/div&gt;v&lt;sub&gt;i&lt;/sub&gt;(x) &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;/td&gt;&lt;td width="1%"&gt;(1)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;br /&gt; &lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Кроме того, Лаплас утверждал, что интеграл функции можно записать так, чтобы он содержал лишь те алгебраические величины, которые есть в исходном подынтегральном выражении.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Гипотезу Лапласа доказал Лиувилль в 1833 г., а ещё через два года он опубликовал доказательство аналогичного факта для некоторого обобщения класса алгебраических функций, т.н. элементарных функций по Лиувиллю. Они имеют форму алгебраических функций, но в роли переменных в них не только x, но ещё конечный набор логарифмов и экспонент от алгебраических функий от x:&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;&lt;br /&gt;y(x) = &lt;span style="font-family:symbol;"&gt;f&lt;/span&gt;(x, z&lt;sub&gt;1&lt;/sub&gt;(x), ... z&lt;sub&gt;r&lt;/sub&gt;(x)) — алгебраическая функция от x, z&lt;sub&gt;1&lt;/sub&gt;(x), ... z&lt;sub&gt;r&lt;/sub&gt;(x)&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; z&lt;sub&gt;i&lt;/sub&gt;(x) = ln(&lt;span style="font-family:symbol;"&gt;q&lt;/span&gt;&lt;sub&gt;i&lt;/sub&gt;(x)) или exp(&lt;span style="font-family:symbol;"&gt;q&lt;/span&gt;&lt;sub&gt;i&lt;/sub&gt;(x)), где &lt;span style="font-family:symbol;"&gt;q&lt;/span&gt;&lt;sub&gt;i&lt;/sub&gt; — алгебраическая функция от x.&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;&lt;br /&gt;Лиувилль показал, что если элементарная (по Лиувиллю) функция имеет элементарную первообразную, то эта первообразная представляется следующим образом:&lt;br /&gt;&lt;table border="0" width="100%"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; &lt;table align="center" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt;&lt;/td&gt;&lt;td align="left" class="cl"&gt;⌠&lt;br /&gt;⌡&lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; f(x, z&lt;sub&gt;1&lt;/sub&gt;(x), ... z&lt;sub&gt;r&lt;/sub&gt;(x)) dx = y&lt;sub&gt;0&lt;/sub&gt;(x, z&lt;sub&gt;1&lt;/sub&gt;(x), ... z&lt;sub&gt;r&lt;/sub&gt;(x)) + &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;small&gt;m&lt;/small&gt;&lt;!--sup --&gt;&lt;br /&gt;&lt;span style="font-size:+3;"&gt;Σ&lt;br /&gt;&lt;/span&gt;&lt;small&gt;i=1&lt;/small&gt;&lt;br /&gt;&lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; A&lt;sub&gt;i&lt;/sub&gt; ln(y&lt;sub&gt;i&lt;/sub&gt;(x, z&lt;sub&gt;1&lt;/sub&gt;(x), ... z&lt;sub&gt;r&lt;/sub&gt;(x)))&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Выражаясь менее формальным языком, первообразная функции y — это алгебраическая функция от того, что уже имеется в y, плюс конечное количество логарифмов (с коэффициентами) алгебраических функций того, что уже имеется в y. То есть, принцип Лапласа остаётся в силе.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Результаты Лиувилля, при всей своей значимости, совершенно не помогают в задаче нахождения первообразной, ибо процедуры приведения функции к интегрируемому виду автором предложено не было. Прошло более 80 лет до того, как английский математик Г. Х. Харди... &lt;a href="http://books.google.com/books?id=J3h46tbaPoIC"&gt;предположил&lt;/a&gt;, что такой процедуры не может существовать, и ошибся.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; В XX веке новая отрасль математики — дифференциальная теория Галуа — позволила обобщить результат Лиувилля на произвольные элементарные функции и, более того, сделать доказательство конструктивным, т.е. таким, что с его помощью стало возможно получить желанную первообразную. А во второй половине XX века очень кстати подоспели и вычислительные машины, на которых эти конструкции были незамедлительно запрограммированы.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Итак, дифференциальная теория Галуа. Ключевое понятие в ней — это т.н. элементарное расширение поля функций&lt;a href="#tthFtNtAAD" name="tthFrefAAD"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;, которое получается последовательным добавлением в него логарифмов, экспонент и алгебраических функций:&lt;br /&gt;&lt;ul&gt; &lt;li&gt; ln(f(x)), где f ∈ K &lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; &lt;/li&gt;&lt;li&gt; exp(f(x)), где f ∈ K &lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; &lt;/li&gt;&lt;li&gt; алгебраической функции, полученной уравнением вида P(f&lt;sub&gt;1&lt;/sub&gt;(x), ... f&lt;sub&gt;n&lt;/sub&gt;(x))=0, где f&lt;sub&gt;1&lt;/sub&gt;, ... f&lt;sub&gt;n&lt;/sub&gt; ∈ K. &lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; &lt;/li&gt; &lt;/ul&gt; (за K считается поле, расширенное предыдущим добавлением). Нетрудно видеть, что любая элементарная функция принадлежит какому-либо элементарному расширению поля рациональных функций.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Ключевая теорема дифференциальной теории Галуа является обобщением теоремы Лиувилля и говорит о том, что если функция f из поля F имеет первообразную g в поле G — элементарном расширении F, то f записывается в форме (&lt;a href="#laplace"&gt;1&lt;/a&gt;), где v&lt;sub&gt;i&lt;/sub&gt; ∈ F, а g соответственно содержит функцию из F и логарифмы функций из F с весами.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Если строить поле F «специально» под функцию f, т.е. подбирать минимальное расширение поля рациональных функций, содержащее F, то станет очевидно, что принцип Лапласа снова не нарушается: первообразная функции f содержит то, что уже имеется в f, а также логарифмы того, что уже имеется в f.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Роберт Риш из Калифорнийского университета в 1969-1970 годах (Харди к тому времени более двадцати лет как не было в живых) опубликовал &lt;a href="http://projecteuclid.org/DPubS?service=UI&amp;amp;version=1.0&amp;amp;verb=Display&amp;amp;handle=euclid.bams/1183531821"&gt;алгоритм&lt;/a&gt;, приводящий любую элементарную функцию к виду (&lt;a href="#laplace"&gt;1&lt;/a&gt;), или определяющий, что такое приведение невозможно (и следовательно, интеграла как элементарной функции нет). На этом вопрос отнюдь не был закрыт; напротив, началось самое интересное.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; &lt;h2&gt;Интегрирование на практике&lt;/h2&gt; Алгоритм Риша, вопреки распространённому мнению, являлся на время своего опубликования алгоритмом интегрирования лишь чисто трансцендентных функций. Для алгебраических составляющих Риш привёл в общих чертах доказательство существования алгоритма, но чтобы сделать из него алгоритм, потребовался ещё не один десяток лет.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Первая реализация алгоритма Риша была выполнена Джоэлом Мозесом в рамках знаменитого «Project MAC» в MIT в 1971 г. Программа под названием SIN &lt;a href="http://hugo.csie.ntu.edu.tw/~yjhsu/courses/u1740/online/papers/p548-moses.pdf"&gt;интегрировала&lt;/a&gt; чисто трансцендентные функции.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Дэвенпорт в 1981 г., основываясь на работе Риша и некоторых глубоких результатах дифференциальной алгебры и комплексного анализа, разработал &lt;a href="http://techlibrary.ru/b/2l2e1c1f1o1q1p1r1t_2l1h._2q1o1t1f1d1r1j1r1p1c1a1o1j1f_1a1m1d1f1b1r1a1j1y1f1s1l1j1w_1v1u1o1l1x1j1k._1985.djvu"&gt;алгоритм интегрирования чисто алгебраических функций&lt;/a&gt; и реализовал его в известной среде символьных вычислений REDUCE-2.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Программы Мозеса и Дэвенпорта служили для решения двух частных случаев одной задачи, но для общего случая — интегрирования произвольных элементарных функций — были одинаково непригодны.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Алгоритм Дэвенпорта ещё и обладал большой вычислительной сложностью, что его автор признавал и выражал надежду, что алгоритм можно упростить. Так и произошло: Барри Трагер из MIT в 1984 г. внёс серьёзные улучшения в алгоритм Дэвенпорта. Обновлённый алгоритм обладал гораздо большей практической ценностью и был реализован в математических программах Axiom и Maple.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Решающий шаг к практическому решению вопроса сделал Мануэль Бронштейн в 1990 г, обобщив алгоритм Трагера на &lt;a href="http://portal.acm.org/citation.cfm?id=78779"&gt;произвольные элементарные функции&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; В 1998 г. Бронштейн написал &lt;a href="http://www-sop.inria.fr/cafe/Manuel.Bronstein/publications/issac98.pdf"&gt;монографию&lt;/a&gt; по символьному интегрированию, в которой понятным языком изложил лучшие достижения в этой области, начиная с теоремы Лиувилля и заканчивая собственными результатами.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Бронштейн был одним из ведущих разработчиков Axiom. Вместе с Трагером он реализовал в Axiom свой алгоритм, но лишь частично. В 2005 году Бронштейн &lt;a href="http://www.mail-archive.com/axiom-math@nongnu.org/msg00014.html"&gt;скончался&lt;/a&gt;, спустя два года после смерти основателя Axiom &lt;a href="http://www.cis.udel.edu/~caviness/jenks/jenksbio/"&gt;Ричарда Дженкса&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; При своей неполноте, имплементация Бронштейна — наиболее полная из имеющихся в программах символьных вычислений. Автор &lt;a href="http://groups.google.com/group/comp.soft-sys.math.maple/msg/eaba5a228bc687c5"&gt;отмечал&lt;/a&gt;, что при возникновении нереализованного случая Axiom выдаёт ошибку, в отличие от процедур интегрирования Maple и Mathematica. То есть, если Axiom обнаруживает, что интеграл от данной функции не является элементарной функцией, то это правда. Если Axiom выдаёт ошибку, то интеграл может быть или не быть элементарной функцией.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;&lt;a href="http://groups.google.com/group/comp.soft-sys.math.maple/msg/eaba5a228bc687c5?dmode=source"&gt;Известны&lt;/a&gt; несложные примеры, показывающие несостоятельность процедур символьного интегрирования в современных математических пакетах. Первообразная функции&lt;table border="0" width="100%"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; &lt;table align="center" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; x &lt;div class="hrcomp"&gt;&lt;hr noshade="noshade" size="1"&gt;&lt;/div&gt;&lt;table border="0" cellspacing="0" cellpadding="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;/td&gt;&lt;td align="left" class="cl"&gt; &lt;span style="font-size:+2;"&gt;√&lt;/span&gt;&lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt;&lt;div class="hrcomp"&gt;&lt;hr noshade="noshade" size="1"&gt;&lt;/div&gt; &lt;div  class="norm"&gt;x&lt;sup&gt;4&lt;/sup&gt; + 10 x&lt;sup&gt;2&lt;/sup&gt; &lt;span style="font-family:symbol;"&gt;-&lt;/span&gt; 96 x &lt;span style="font-family:symbol;"&gt;-&lt;/span&gt; 71 &lt;/div&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;равна&lt;table border="0" width="100%"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; &lt;table align="center" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;span style="font-family:symbol;"&gt;-&lt;/span&gt;&lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; 1 &lt;div class="hrcomp"&gt;&lt;hr noshade="noshade" size="1"&gt;&lt;/div&gt;8 &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; ln((x&lt;sup&gt;6&lt;/sup&gt;+15 x&lt;sup&gt;4&lt;/sup&gt;&lt;span style="font-family:symbol;"&gt;-&lt;/span&gt;80 x&lt;sup&gt;3&lt;/sup&gt;+27 x&lt;sup&gt;2&lt;/sup&gt;&lt;span style="font-family:symbol;"&gt;-&lt;/span&gt;528 x+781) &lt;/td&gt;&lt;td align="left" class="cl"&gt; &lt;span style="font-size:+2;"&gt;√ &lt;/span&gt; &lt;div class="comb"&gt; &lt;/div&gt; &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt;&lt;div class="hrcomp"&gt;&lt;hr noshade="noshade" size="1"&gt;&lt;/div&gt; &lt;div class="norm"&gt;x&lt;sup&gt;4&lt;/sup&gt;+10 x&lt;sup&gt;2&lt;/sup&gt;&lt;span style="font-family:symbol;"&gt;-&lt;/span&gt;96 x&lt;span style="font-family:symbol;"&gt;-&lt;/span&gt;71 &lt;/div&gt; &lt;div class="comb"&gt; &lt;/div&gt; &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;  &lt;table border="0" width="100%"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; &lt;table align="center" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;span style="font-family:symbol;"&gt;-&lt;/span&gt; x&lt;sup&gt;8&lt;/sup&gt; &lt;span style="font-family:symbol;"&gt;-&lt;/span&gt; 20 x&lt;sup&gt;6&lt;/sup&gt; + 128 x&lt;sup&gt;5&lt;/sup&gt; &lt;span style="font-family:symbol;"&gt;-&lt;/span&gt; 54 x&lt;sup&gt;4&lt;/sup&gt; + 1408 x&lt;sup&gt;3&lt;/sup&gt; &lt;span style="font-family:symbol;"&gt;-&lt;/span&gt; 3124 x&lt;sup&gt;2&lt;/sup&gt; &lt;span style="font-family:symbol;"&gt;-&lt;/span&gt; 10001),&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; однако в таком виде её находит только Axiom. Прочие пакеты — Mathematica, Maple, Matlab, Maxima, Reduce — в лучшем случае выдают громоздкий ответ с участием эллиптических интегралов, т.е. неспособны выдать ответ в виде элементарной функции.&lt;a href="#tthFtNtAAE" name="tthFrefAAE"&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Первообразную функции e&lt;sup&gt;arcsin(x)&lt;/sup&gt;, равную ½(√&lt;span style="text-decoration:overline;"&gt; 1-x&lt;sup&gt;2&lt;/sup&gt; &lt;/span&gt;+x)e&lt;sup&gt;arcsin(x)&lt;/sup&gt;, способны найти Mathematica, Axiom и Reduce.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Неопределённый интеграл ∫√&lt;span style="text-decoration:overline;"&gt;arctan(x)&lt;/span&gt; dx невозможно найти (или определить его отсутствие) ни в одном из перечисленных пакетов; при этом Axiom выводит наиболее осмысленное сообщение: &lt;tt&gt;implementation incomplete (constant residues)&lt;/tt&gt;.&lt;br /&gt;&lt;h2&gt;Нерешённые вопросы&lt;/h2&gt; Полная имплементация алгоритма Риша-Трагера-Бронштейна ждёт своего автора. Однако, и это ещё не всё: в теории символьного интегрирования тоже остались открытые вопросы.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Пришло время сказать, что алгоритм Риша (даже для чисто трансцендентных функций) не является «честным» алгоритмом.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Дело в том, что в процессе работы алгоритм Риша выстраивает «башню» элементарных расширений поля рациональных функций, с тем чтобы «верхушка» башни содержала подынтегральное выражение. При этом не всегда понятно, готова ли уже «башня» или нужно добавить в неё ещё один логарифм, экспоненту, или алгебраическую функцию. Например, если в подынтегральном выражении встретилась экспонента e&lt;sup&gt;x&lt;/sup&gt;, она добавляется в поле (происходит элементарное расширение поля функцией e&lt;sup&gt;x&lt;/sup&gt;). Если при дальнейшем анализе в подынтегральном выражении встречается e&lt;sup&gt;x&lt;sup&gt;2&lt;/sup&gt;&lt;/sup&gt;, то эту функцию также следует добавить в поле, вырастив таким образом «башню» до двух этажей. Но если после этого попадётся функция e&lt;sup&gt;x+x&lt;sup&gt;2&lt;/sup&gt;&lt;/sup&gt;, то алгоритм должен определить, что она принадлежит сформированному на данный момент элементарному расширению, и не наращивать «башню». Да, в описанном случае это несложно сделать; чуть сложнее определить, например, на языке элементарных функций, что &lt;a href="http://groups.google.com/group/sci.math/msg/44bc07b8e271ac94?dmode=source"&gt;arctan(x) = -arctan(1/x)&lt;/a&gt;. Но в общем случае эта задача очень сложна&lt;a href="#tthFtNtAAF" name="tthFrefAAF"&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;, и нынешние реализации алгоритма Риша используют лишь эвристики для её решения, которые по определению ненадёжны.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Как указывает Дэвенпорт и следом за ним Бронштейн, при реализации алгоритма можно «сделать вид», что нам известен т.н. базис трансцендентности для поля констант, то есть например считать e и √&lt;span style="text-decoration:overline;"&gt; 2 &lt;/span&gt; алгебраически независимыми. Это значит, что мы принимаем на веру, что нет никакого полинома от e и √&lt;span style="text-decoration:overline;"&gt; 2 &lt;/span&gt;, который был бы равен 0. Такое решение нужно принимать о каждой новой константе, появляющейся в ходе работы алгоритма, которую эвристика не позволяет связать алгебраическим выражением с ранее учтёнными константами. Если полученный базис трансцендентности окажется неверным, это может отразиться на корректности выданного ответа в том случае, если ответ гласит, что «интеграл не является элементарной функцией» (да, это касается и Axiom, о которой выше было сказано противоположное). Если же интеграл найден, его можно считать правильным.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Известным частным случаем задачи об алгебраической независимости является т.н. Constant Problem: определение того, тождественно ли нулю некоторое математическое выражение. Задача тривиально решается для многочленов, но для более широкого класса функций, как показал Даниэль Ричардсон в 1968 г., эта задача &lt;a href="http://mathworld.wolfram.com/RichardsonsTheorem.html"&gt;неразрешима&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Не всё, однако, так безнадёжно: Ричардсон в набор «элементарных расширений» включил, помимо экспоненты и логарифма, функцию модуля. В оригинале теорема Ричардсона звучит так:&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;&lt;i&gt;Если есть функция f, получающаяся с помощью сложения, умножения и композиции из синуса, экспоненты и модуля, а коэффициенты f используют рациональные числа, π и ln(2), то задача проверки утверждения «f тождественно равна нулю» алгоритмически неразрешима.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;Функция модуля существенна в доказательстве Ричардсона, и есть основания полагать (да и сам автор теоремы надеется), что без неё задача Constant Problem всё таки разрешима, что даёт надежду на создание абсолютно честного алгоритма символьного интегрирования.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt;И последнее: класс элементарных функций кажется несколько надуманным, не так ли? Например, функция ошибки&lt;br /&gt;&lt;table border="0" width="100%"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt; &lt;table align="center" cellspacing="0" cellpadding="2"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;span class="roman"&gt;erf&lt;/span&gt;(x) = &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; 2 &lt;div class="hrcomp"&gt;&lt;hr noshade="noshade" size="1"&gt;&lt;/div&gt;&lt;table border="0" cellspacing="0" cellpadding="0"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;/td&gt;&lt;td align="left" class="cl"&gt;&lt;br /&gt;&lt;span style="font-size:+2;"&gt;√&lt;/span&gt; &lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt;&lt;div class="hrcomp"&gt;&lt;hr noshade="noshade" size="1"&gt;&lt;/div&gt; &lt;div class="norm"&gt;π&lt;br /&gt;&lt;/div&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;/td&gt;&lt;td align="left" class="cl"&gt;⌠&lt;br /&gt;⌡&lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; &lt;small&gt;x&lt;/small&gt;&lt;!--sup --&gt;&lt;br /&gt;&lt;br /&gt;&lt;small&gt;0&lt;/small&gt;&lt;br /&gt;&lt;/td&gt;&lt;td nowrap="nowrap" align="center"&gt; e&lt;sup&gt;-t&lt;sup&gt;2&lt;/sup&gt;&lt;/sup&gt; dt&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt; не является элементарной, как доказал Лиувилль. Но чем она хуже экспоненты и логарифма, которые мы относим к элементарным функциям? На компьютере она может быть вычислена с произвольной точностью, и даже, как экспонента и логарифм, обладает элементарной производной. Эту функцию вполне можно включить в «надэлементарные» расширения функций, так же как и эллиптические интегралы, ∫x&lt;sup&gt;x&lt;/sup&gt;dx и многие другие. Тема интегрируемости в «надэлементарных» функциях была затронута ещё автором SIN в 1970-х годах, и ныне представляет обширное поле для исследований.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; &lt;h2&gt;Благодарности&lt;/h2&gt; Автор признателен А. В. Демьянову, П. А. Мозоляко и Н. Н. Васильеву за вычитку черновиков этой статьи и высказанные замечания.&lt;br /&gt;&lt;h2&gt;Литература&lt;/h2&gt;J. Liouville. Premier memoire sur la determination des integrales dont la valeur est algebrique. Journal de l’Ecole Polytechnique, 14:124–148, 1833&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; J. Liouville. Second memoire sur la détermination des integrales dont la valeur est algebrique. Journal de l’Ecole Polytechnique, 14:149–193, 1833&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; J. Liouville. Mèmoire sur l’intégration d’une classe de fonctions transcendantes. J. Reine Angew. Math. Bd. 13, p. 93-118. (1835)&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; G. H. Hardy. The Integration of Functions of a Single Variable, 1916.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; R. H. Risch (1970). The solution of the Problem of Integration in Finite Terms. Bulletin of the American Mathematical Society 76 (3): 605–608.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Joel Moses. Symbolic integration the stormy decade. Proceedings of the second ACM symposium on Symbolic and algebraic manipulation (1971) 427-440.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Дж. Дэвенпорт. Интегрирование алгебраических функций, Москва «Мир» 1985.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; B. Trager. Integration of algebraic functions. PhD Thesis, MIT, 1984&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; M. Bronstein. Integration of elementary functions. Journal of Symbolic Computation, Volume 9, Issue 2 (1990). 117-173&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; M. Bronstein. Symbolic Integration Tutorial. ISSAC'98, Rostock.&lt;br /&gt;&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; Richardson, D. Some Unsolvable Problems Involving Elementary Functions of a Real Variable. J. Symbolic Logic 33, 514-520, 1968. &lt;hr /&gt;&lt;h3&gt;Сноски:&lt;/h3&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; &lt;a name="tthFtNtAAC"&gt;&lt;/a&gt;&lt;a href="#tthFrefAAC"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;Кстати, это число называется корнем Бринга Br(a), и с его помощью можно записать все комплексные корни более общего уравнения «x&lt;sup&gt;5&lt;/sup&gt; + px + q = 0». &lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; &lt;a name="tthFtNtAAD"&gt;&lt;/a&gt;&lt;a href="#tthFrefAAD"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;Формально говоря, дифференциальная теория Галуа оперирует дифференциальными полями характеристики 0, но мы в эти термины погружаться не станем и будем считать по-прежнему, что имеем дело с различными классами функций. &lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; &lt;a name="tthFtNtAAE"&gt;&lt;/a&gt;&lt;a href="#tthFrefAAE"&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;Проверялись следующие версии программ: Axiom 2010, Mathematica 7, Maple 13, Matlab R2007, Maxima 5.21, Reduce 2009.&lt;br /&gt;&lt;div class="p"&gt;&lt;!----&gt;&lt;/div&gt; &lt;a name="tthFtNtAAF"&gt;&lt;/a&gt;&lt;a href="#tthFrefAAF"&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;Например, до сих пор никто не знает, является ли e + π рациональным числом (хотя известно, что e и π по отдельности — трансцентентные числа). Известно только равенство e&lt;sup&gt;i π&lt;/sup&gt; = -1 и бесконечная &lt;a href="http://ru.wikipedia.org/wiki/%D0%A1%D1%80%D0%B8%D0%BD%D0%B8%D0%B2%D0%B0%D1%81%D0%B0_%D0%A0%D0%B0%D0%BC%D0%B0%D0%BD%D1%83%D0%B4%D0%B6%D0%B0%D0%BD_%D0%90%D0%B9%D0%B5%D0%BD%D0%B3%D0%BE%D1%80"&gt;дробь Рамануджана&lt;/a&gt;, связывающая e и π.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-918563591801477920?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/918563591801477920/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=918563591801477920' title='Комментарии: 6'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/918563591801477920'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/918563591801477920'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2010/05/blog-post.html' title='Symbolic Integration'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-6176731616395666323</id><published>2010-02-21T23:36:00.003+03:00</published><updated>2010-02-22T00:36:15.593+03:00</updated><title type='text'>Mark Tarver и Qi</title><content type='html'>&lt;a href="http://www.lambdassociates.org/"&gt;Qi&lt;/a&gt;&amp;nbsp;(произносится &amp;laquo;чи&amp;raquo;)&amp;nbsp;&amp;mdash; функциональный язык программирования, с&amp;nbsp;проверщиком типов полным по&amp;nbsp;Тьюрингу, как и&amp;nbsp;в&amp;nbsp;&lt;a href="http://www.lochan.org/keith/publications/undec.html"&gt;Haskell&lt;/a&gt; и&amp;nbsp;&lt;a href="http://homepage.mac.com/sigfpe/Computing/sk.html"&gt;C++&lt;/a&gt;.&amp;nbsp;Автор (Марк Тарвер, профессор в&amp;nbsp;отставке) при этом утверждает, что его творение затмевает все существующие разработки и&amp;nbsp;&lt;a href="http://www.lambdassociates.org/studies/study01.htm"&gt;останется непревзойдённым навсегда&lt;/a&gt;, по&amp;nbsp;причине использования &lt;a href="http://en.wikipedia.org/wiki/Sequent_calculus"&gt;наиболее гибкой нотации&lt;/a&gt; для описания типов. К&amp;nbsp;тому&amp;nbsp;же, оно &lt;a href="http://www.lambdassociates.org/faq2.htm"&gt;работает поверх Лиспа&lt;/a&gt; и&amp;nbsp;разрешает использовать функции на&amp;nbsp;Лиспе.&lt;br /&gt;&lt;br /&gt;О&amp;nbsp;практическом применении языка речи не&amp;nbsp;идёт; на&amp;nbsp;вопрос, а&amp;nbsp;что такого есть в&amp;nbsp;мире написанного на&amp;nbsp;Qi, автор гордо &lt;a href="http://www.lambdassociates.org/faq4.htm"&gt;отвечает&lt;/a&gt;, что сам&amp;nbsp;Qi на&amp;nbsp;себе и&amp;nbsp;написан, не&amp;nbsp;считая вспомогательных библиотек к&amp;nbsp;нему&amp;nbsp;же. Скорее, Qi&amp;nbsp;&amp;mdash; исследовательский инструмент для разработки новых систем типов; ну&amp;nbsp;и&amp;nbsp;для саморазвития, конечно.&lt;br /&gt;&lt;br /&gt;По&amp;nbsp;части последнего Марк вообще увлечённая личность: в&amp;nbsp;августе прошлого года помпезно &lt;a href="http://groups.google.com/group/Qilang/browse_thread/thread/592773c562017d87?pli=1"&gt;попрощался&lt;/a&gt; с&amp;nbsp;Qi и&amp;nbsp;вообще с&amp;nbsp;программированием, и&amp;nbsp;уехал в&amp;nbsp;Индию заниматься йогой. В&amp;nbsp;ноябре посетовал, что-де в&amp;nbsp;&lt;a href="http://groups.google.co.uk/group/qilang/browse_thread/thread/93706b9859734804#"&gt;ашраме туго с&amp;nbsp;интернетом&lt;/a&gt;, а&amp;nbsp;в&amp;nbsp;декабре (уже не&amp;nbsp;с&amp;nbsp;такой помпой) оповестил сообщество, что &lt;a href="http://groups.google.co.uk/group/qilang/browse_thread/thread/4829038ad72e0ead#"&gt;&amp;laquo;phase of&amp;nbsp;travels is&amp;nbsp;over&amp;raquo;&lt;/a&gt;, вернулся в&amp;nbsp;Англию и&amp;nbsp;продолжает развивать Qi.&lt;br /&gt;&lt;br /&gt;Как тут не&amp;nbsp;пожелать доктору Тарверу всяческих успехов.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-6176731616395666323?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/6176731616395666323/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=6176731616395666323' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/6176731616395666323'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/6176731616395666323'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2010/02/mark-tarver-qi.html' title='Mark Tarver и Qi'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-7503934790614129287</id><published>2009-10-29T01:54:00.008+03:00</published><updated>2011-05-15T16:10:18.638+04:00</updated><title type='text'>Indigo</title><content type='html'>Мы открыли &lt;a href="http://ggasoftware.com/opensource/indigo/"&gt;сайт&lt;/a&gt; с программными продуктами для органической химии, которые разрабатывались с 2004 года. Продукты реализованы на C++ и объединены в тулкит под названием Indigo.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Bingo: картридж (плагин) к СУБД Oracle с реализацией различных видов «химического» поиска молекул и реакций.&lt;/li&gt;&lt;li&gt;Dingo: библиотека для рендеринга молекул и реакций, с обёрткой на C# и command-line утилитой.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Cano: библиотека для подсчёта canonical SMILES — уникального представления молекулы, с обёрткой на C#.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Deco: библиотека и command-line утилита для разложения набора молекул на общую часть и заместители (R-Group deconvolution)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Nucleo: библиотека для работы с нуклеотидными цепочками, с обёрткой на Java.&lt;/li&gt;&lt;/ul&gt;Поддерживаемые платформы — Windows, Linux, Mac OS X, Solaris на SPARC.&lt;br /&gt;&lt;br /&gt;Дистрибутивы и исходники бесплатно выложены под GPL v3. В исходниках, кроме всего необходимого для работы с молекулами и реакциями, можно добыть немало реализаций общих алгоритмов на графах:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Остовное дерево, разбиение на компоненты, разбиение на двусвязные компоненты&lt;/li&gt;&lt;li&gt;Определение изоморфизма подграфу&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://ggasoftware.com/downloads/mcs_article.pdf"&gt;Наибольший общий подграф&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://shmat-razum.blogspot.com/2009/06/graph-automorphisms-canonical-labeling.html"&gt;Нахождение группы автоморфизмов и канонической нумерации графа&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://shmat-razum.blogspot.com/2008/09/blog-post.html"&gt;Канонический код дерева&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://shmat-razum.blogspot.com/2008/11/blog-post.html"&gt;Перебор поддеревьев графа&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Перебор циклов графа&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Краткий перечень этих и других реализованных алгоритмов находится на &lt;a href="http://ggasoftware.com/opensource/resources"&gt;странице ресурсов&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Мы открыли три публичные почтовые рассылки:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://groups.google.com/group/indigo-dev"&gt;http://groups.google.com/group/indigo-dev&lt;/a&gt; — для разработчиков&lt;/li&gt;&lt;li&gt;&lt;a href="http://groups.google.com/group/indigo-bugs"&gt;http://groups.google.com/group/indigo-bugs&lt;/a&gt; — для баг-репортов&lt;/li&gt;&lt;li&gt;&lt;a href="http://groups.google.com/group/indigo-general"&gt;http://groups.google.com/group/indigo-general&lt;/a&gt; — для всего остального&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Ещё раз адрес сайта: &lt;a href="http://ggasoftware.com/opensource/indigo/"&gt;http://ggasoftware.com/opensource/indigo/&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Команда разработчиков находится в Санкт-Петербурге. Будем очень рады любым комментариям, вопросам, сообщениям об ошибках.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-7503934790614129287?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/7503934790614129287/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=7503934790614129287' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/7503934790614129287'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/7503934790614129287'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2009/10/indigo.html' title='Indigo'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-4120439709718769644</id><published>2009-07-04T20:24:00.009+04:00</published><updated>2009-07-05T12:19:27.712+04:00</updated><title type='text'>HTML + SVG -&gt; PDF</title><content type='html'>Продолжаем тему &lt;a href="http://lj.rossia.org/users/ringill/12260.html"&gt;вики-документации с&amp;nbsp;векторной графикой&lt;/a&gt;. Вот у&amp;nbsp;нас есть Dokuwiki с&amp;nbsp;плагином svg_pureinsert, и&amp;nbsp;иллюстрации прекрасно выглядят в&amp;nbsp;броузерах, поддерживающих SVG. Добавив &lt;code&gt;&amp;amp;do=export_html&lt;/code&gt; в&amp;nbsp;конец адресной строки документа в&amp;nbsp;dokuwiki, можно получить &amp;laquo;чистый&amp;raquo; HTML, без элементов управления самой вики. Но&amp;nbsp;когда возникает необходимость преобразовать эту страницу в&amp;nbsp;PDF, то&amp;nbsp;оказывается, что сделать это, не&amp;nbsp;потеряв иллюстрации, практически невозможно. Есть два способа:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Сохранить в&amp;nbsp;PDF прямо из&amp;nbsp;браузера (Firefox 3&amp;nbsp;это умеет).&lt;/li&gt;&lt;li&gt;Открыть страницу в&amp;nbsp;OpenOffice и&amp;nbsp;сохранить PDF из&amp;nbsp;него.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;Если мы&amp;nbsp;хотим добавить в&amp;nbsp;документ оглавление с&amp;nbsp;номерами страниц, то&amp;nbsp;первый способ нам никак не&amp;nbsp;подойдёт. Второй способ имеет особенности, о&amp;nbsp;которых сейчас пойдёт речь.&lt;br /&gt;&lt;br /&gt;svg_pureinsert вставляет svg-иллюстрации в&amp;nbsp;&lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;. Это &lt;a href="http://www.carto.net/papers/svg/samples/svg_html.shtml"&gt;не&amp;nbsp;единственный&lt;/a&gt; способ вставки SVG в&amp;nbsp;HTML, но&amp;nbsp;по&amp;nbsp;случайности&amp;nbsp;&amp;#8212; единственный подходящий для OpenOffice; теги &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt; и&amp;nbsp;&lt;code&gt;&amp;lt;embed&amp;gt;&lt;/code&gt; он&amp;nbsp;не&amp;nbsp;замечает. Есть ещё одна неприятность: когда oowriter открывает HTML-страницу, все такие iframe преобразуются в&amp;nbsp;т.н. &amp;laquo;floating frames&amp;raquo;, в&amp;nbsp;которые помимо самой картинки втиснут интерфейс oodraw:&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_BaonBoyqin8/Sk-MQhxxMVI/AAAAAAAAACs/tWCtVypSdS8/s1600-h/floating-frame.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 388px; height: 384px;" src="http://1.bp.blogspot.com/_BaonBoyqin8/Sk-MQhxxMVI/AAAAAAAAACs/tWCtVypSdS8/s400/floating-frame.png" alt="" id="BLOGGER_PHOTO_ID_5354652697539719506" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Чтобы превратить каждую floating frame в&amp;nbsp;обычный embedded object, ссылающийся на&amp;nbsp;тот&amp;nbsp;же svg-файл, надо выполнить следующий макрос на&amp;nbsp;OpenOffice Basic:&lt;pre&gt;Sub ConvertFloatingFrames&lt;br /&gt;  doc = ThisComponent&lt;br /&gt;  found = True&lt;br /&gt;&lt;br /&gt;  While found&lt;br /&gt;    found = False&lt;br /&gt;    For i = 0 To doc.getDrawPage().getCount() - 1&lt;br /&gt;      If Not found Then&lt;br /&gt;        obj = doc.getDrawPage().getByIndex(0)&lt;br /&gt;        If obj.getImplementationName() = "SwXTextEmbeddedObject" then&lt;br /&gt;          emb = obj.getExtendedControlOverEmbeddedObject&lt;br /&gt;          url = emb.Component.frameUrl&lt;br /&gt;          size = obj.getSize()&lt;br /&gt;          oText = obj.getAnchor().getText()&lt;br /&gt;          EmbedGraphic(doc, obj.getAnchor(), url, size.Width, size.Height)&lt;br /&gt;          oText.removeTextContent(obj)&lt;br /&gt;          found = True&lt;br /&gt;        End If&lt;br /&gt;      End If&lt;br /&gt;    Next&lt;br /&gt;  Wend&lt;br /&gt;End Sub&lt;/pre&gt;Вспомогательный к&amp;nbsp;нему макрос был позаимствован из&amp;nbsp;известной &lt;a href="http://www.pitonyak.org/oo.php"&gt;книги&lt;/a&gt; про макросы OpenOffice.&lt;pre&gt;Sub EmbedGraphic (doc, anchor, url, width, height)&lt;br /&gt;  shape = doc.createInstance("com.sun.star.drawing.GraphicObjectShape")&lt;br /&gt;  obj = doc.createInstance("com.sun.star.text.GraphicObject")&lt;br /&gt;  doc.getDrawPage().add(shape)&lt;br /&gt;&lt;br /&gt;  provider = createUnoService("com.sun.star.graphic.GraphicProvider")&lt;br /&gt;&lt;br /&gt;  Dim props(0) as new com.sun.star.beans.PropertyValue&lt;br /&gt;  props(0).Name  = "URL"&lt;br /&gt;  props(0).Value = url&lt;br /&gt;&lt;br /&gt;  shape.Graphic = provider.queryGraphic(props())&lt;br /&gt;&lt;br /&gt;  obj.graphicurl = shape.graphicurl&lt;br /&gt;  obj.width = width&lt;br /&gt;  obj.height = height&lt;br /&gt;  obj.AnchorType = com.sun.star.text.TextContentAnchorType.AS_CHARACTER&lt;br /&gt;&lt;br /&gt;  anchor.getText().insertTextContent(anchor, obj, false)&lt;br /&gt;  doc.getDrawPage().remove(shape)&lt;br /&gt;End Sub&lt;/pre&gt;&lt;br /&gt;Проверял на&amp;nbsp;OpenOffice.org 3.1. Макрос работает всегда со&amp;nbsp;второго раза, из-за каких-то трудностей с&amp;nbsp;&lt;code&gt;ThisComponent&lt;/code&gt;, которые я&amp;nbsp;не&amp;nbsp;знаю как решить. Да,&amp;nbsp;и&amp;nbsp;поддерживаются только &lt;a href="http://wiki.services.openoffice.org/wiki/Supported_SVG_Features"&gt;самые примитивные SVG&lt;/a&gt;. Сторонний &lt;a href="http://haumacher.de/svg-import/"&gt;фильтр&lt;/a&gt; SVG для OpenOffice 2.0&amp;nbsp;поддерживает формат в&amp;nbsp;большей мере, но&amp;nbsp;при отрисовке embedded-объектов не&amp;nbsp;задействуется, к&amp;nbsp;сожалению.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-4120439709718769644?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/4120439709718769644/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=4120439709718769644' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/4120439709718769644'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/4120439709718769644'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2009/07/html-svg-pdf.html' title='HTML + SVG -&gt; PDF'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_BaonBoyqin8/Sk-MQhxxMVI/AAAAAAAAACs/tWCtVypSdS8/s72-c/floating-frame.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-5597241540471934092</id><published>2009-06-12T01:34:00.017+04:00</published><updated>2009-07-05T19:37:17.398+04:00</updated><title type='text'>Graph automorphisms, canonical labeling, nauty</title><content type='html'>Раскрываем тему канонической нумерации графов. О&amp;nbsp;деревьях была &lt;a href="http://shmat-razum.blogspot.com/2008/09/blog-post.html"&gt;отдельная заметка&lt;/a&gt;, теперь переключим внимание на&amp;nbsp;общий случай.&lt;br /&gt;&lt;br /&gt;Допустим, мы&amp;nbsp;храним и&amp;nbsp;поддерживаем базу данных &lt;a href="http://en.wikipedia.org/wiki/Molecule"&gt;молекул&lt;/a&gt;. Или &lt;a href="http://en.wikipedia.org/wiki/Signal_transduction_pathways"&gt;путей метаболизма&lt;/a&gt; в&amp;nbsp;биологических клетках, или вовсе абстрактных графов, для какой-либо &lt;a href="http://en.wikipedia.org/wiki/Graph_enumeration"&gt;задачи по&amp;nbsp;комбинаторике&lt;/a&gt;. В&amp;nbsp;любом случае, речь идёт о&amp;nbsp;графах с&amp;nbsp;дополнительными свойствами.&lt;br /&gt;&lt;br /&gt;При помещении в&amp;nbsp;базу очередной записи необходимо проверить, что аналогичной записи в&amp;nbsp;базе ещё нет. Выражаясь математически, нужно убедиться в&amp;nbsp;отсутствии графов, &lt;a href="http://en.wikipedia.org/wiki/Graph_isomorphism"&gt;изоморфных&lt;/a&gt; добавляемому. Наиболее грубый (и&amp;nbsp;наименее быстрый) способ выглядит так: мы&amp;nbsp;перебираем всю базу и&amp;nbsp;осуществляем проверку изоморфизма каждого графа из&amp;nbsp;базы с&amp;nbsp;новым образцом.&lt;br /&gt;&lt;br /&gt;Проверка изоморфизма двух графов&amp;nbsp;&amp;#8212; непростая задача. Она очевидно принадлежит к&amp;nbsp;классу NP,&amp;nbsp;но&amp;nbsp;принадлежность её&amp;nbsp;к&amp;nbsp;классам P&amp;nbsp;или NP-полных до&amp;nbsp;сих пор под вопросом. (В&amp;nbsp;комментариях дана ссылка на&amp;nbsp;алгоритм, возможно решающий эту задачу за&amp;nbsp;полиномиальное время.) Пока что для этой задачи придуман собственный класс сложности в&amp;nbsp;NP,&amp;nbsp;под названием &lt;a href="http://en.wikipedia.org/wiki/Graph_isomorphism#Complexity_class_GI"&gt;GI&lt;/a&gt;. Но пусть даже у&amp;nbsp;нас будет эффективная процедура для проверки изоморфизма графов; в&amp;nbsp;любом случае, если записей в&amp;nbsp;базе&amp;nbsp;&amp;#8212; миллион, то&amp;nbsp;на&amp;nbsp;проверку присутствия нового образца уйдут многие минуты.&lt;br /&gt;&lt;br /&gt;Возможно, перед лицом этой опасности и&amp;nbsp;были изобретены &lt;a href="http://en.wikipedia.org/wiki/Graph_canonization"&gt;канонические коды&lt;/a&gt; графов. Канонический код&amp;nbsp;&amp;#8212; это строковое представление графа, не&amp;nbsp;зависящее от&amp;nbsp;порядка нумерации вершин; своего рода хеш-код без промахов. Имея такой код, никогда не&amp;nbsp;потребуется проверять непосредственно изоморфизм графов, достаточно сравнивать две строки. Коды одинаковы тогда и&amp;nbsp;только тогда, когда графы изоморфны.&lt;br /&gt;&lt;br /&gt;Таким образом, перед добавлением графа в&amp;nbsp;базу мы&amp;nbsp;вычислим его канонический код, и&amp;nbsp;затем осуществим поиск этого кода (строки) среди имеющихся. Поиск строк, в&amp;nbsp;отличие от&amp;nbsp;изоморфных графов, осуществляется быстро; проблема решена.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;Следует отметить, что канонические коды&amp;nbsp;&amp;#8212; не&amp;nbsp;единственное средство. Существует немало кодов (см.&amp;nbsp;комментарии), которые вычисляются за&amp;nbsp;полиномиальное время и&amp;nbsp;не&amp;nbsp;являются &amp;laquo;каноническими&amp;raquo; в&amp;nbsp;том смысле, что могут совпадать у&amp;nbsp;неизоморфных графов. Однако, если коды разные, то&amp;nbsp;графы неизоморфны. Пользуясь этим, можно осуществить отсеивание (screening) большей части неизоморфных графов, и&amp;nbsp;проверять изоморфизм только для оставшихся.&lt;br /&gt;&lt;br /&gt;Очевидно, что задача вычисления канонического кода не&amp;nbsp;проще, чем задача проверки изоморфизма; но&amp;nbsp;она интереснее, и&amp;nbsp;канонические коды удобнее в&amp;nbsp;использовании: на&amp;nbsp;один граф вызывается ровно одна специальная процедура.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;Любой канонический код состоит из&amp;nbsp;(i)&amp;nbsp;определённой схемы кодирования графа и&amp;nbsp;(ii)&amp;nbsp;процедуры переупорядочения вершин. Нас не&amp;nbsp;интересует схема кодирования; это может быть обход в&amp;nbsp;глубину, или в&amp;nbsp;ширину, или развёрнутая в&amp;nbsp;строку матрица смежности, или просто списки вершин и&amp;nbsp;рёбер... как&amp;nbsp;бы то&amp;nbsp;ни&amp;nbsp;было, кодирование графа подразумевает какой-то порядок его вершин. Наша задача&amp;nbsp;&amp;#8212; упорядочить вершины так, чтобы результат не&amp;nbsp;зависел от&amp;nbsp;изначального порядка. Это называется &lt;i&gt;каноническая нумерация&lt;/i&gt; (canonical numbering, canonical labeling).&lt;br /&gt;&lt;br /&gt;Похоже, что пионерами этой задачи являются Арлазаров, Зуев, Усков и&amp;nbsp;Фараджев, написавшие в&amp;nbsp;1973&amp;nbsp;году статью &amp;laquo;Алгоритм приведения неориентированных графов к&amp;nbsp;каноническому виду&amp;raquo;. Насколько большую роль сыграли их&amp;nbsp;идеи&amp;nbsp;&amp;#8212; это отдельная тема, выходящая за&amp;nbsp;рамки данной заметки (более того, в&amp;nbsp;оффлайн); мы&amp;nbsp;же останемся в&amp;nbsp;реалиях нынешней сетевой эпохи.&lt;br /&gt;&lt;br /&gt;Реалии, однако, мало изменились за&amp;nbsp;последние 15&amp;nbsp;лет. Австралийский профессор Брендан МакКей (Brendan McKay) в&amp;nbsp;1980-х годах написал статью &amp;laquo;&lt;a href="http://cs.anu.edu.au/%7Ebdm/papers/pgi.pdf"&gt;Practical Graph Isomorphism&lt;/a&gt;&amp;raquo; и&amp;nbsp;соответствующий программный пакет под названием &lt;a href="http://cs.anu.edu.au/%7Ebdm/nauty/"&gt;nauty&lt;/a&gt;. Долгое время никому не&amp;nbsp;удалось превзойти nauty по&amp;nbsp;быстродействию; хотя для некоторых частных случаев более эффективные алгоритмы всё&amp;nbsp;же появляются, например &lt;a href="http://arxiv.org/abs/0804.4881"&gt;вот&lt;/a&gt;, &lt;a href="http://arxiv.org/abs/0905.3927"&gt;вот&lt;/a&gt;. Особого упоминания заслуживают библиотеки &lt;a href="http://www.tcs.hut.fi/Software/bliss/index.html"&gt;bliss&lt;/a&gt; (&lt;a href="http://www.siam.org/proceedings/alenex/2007/alx07_013junttilat.pdf"&gt;2007&lt;/a&gt;) и&amp;nbsp;&lt;a href="http://vlsicad.eecs.umich.edu/BK/SAUCY/"&gt;saucy2&lt;/a&gt;&amp;nbsp;(&lt;a href="http://vlsicad.eecs.umich.edu/BK/SAUCY/saucy-dac08.pdf"&gt;2008&lt;/a&gt;), которые на&amp;nbsp;больших разреженных графах уверенно обгоняют nauty. Но&amp;nbsp;эти алгоритмы, фактически, не&amp;nbsp;более чем nauty с&amp;nbsp;модификациями.&lt;br /&gt;&lt;br /&gt;Печально другое: мало кто из&amp;nbsp;программистов сумел хотя&amp;nbsp;бы подойти близко к&amp;nbsp;пониманию того, как работает nauty. Если мы&amp;nbsp;посмотрим на&amp;nbsp;современные программные пакеты, то&amp;nbsp;окажется, что они либо напрямую используют эту библиотеку: &lt;a href="http://magma.maths.usyd.edu.au/magma/htmlhelp/text1640.htm"&gt;Magma&lt;/a&gt;, &lt;a href="http://mupad-combinat.sourceforge.net/README"&gt;MuPAD&lt;/a&gt;, &lt;a href="http://www.maths.qmul.ac.uk/%7Eleonard/grape/"&gt;GRAPE&lt;/a&gt;, &lt;a href="http://code.google.com/p/planarity/source/browse/#svn/trunk/c/nauty"&gt;Planarity&lt;/a&gt;, &lt;a href="http://code.google.com/p/cyberaide/source/browse/#svn/trunk/project/folkman/lib/nauty22-generic"&gt;Cyberaide&lt;/a&gt;, &lt;a href="http://igraph.sourceforge.net/doc/R/canonical.permutation.html"&gt;igraph&lt;/a&gt; (использует bliss), либо используют более примитивные алгоритмы: &lt;a href="http://www.google.com/codesearch/p?hl=en#A1nPtkX4FwM/openbabel/trunk/src/canon.cpp"&gt;OpenBabel&lt;/a&gt;, &lt;a href="http://faculty.sfasu.edu/jefferya/marvin-all-5_2_0/marvin/help/formats/smiles-doc.html#SMILES"&gt;Marvin&lt;/a&gt;. В&amp;nbsp;любом случае, об&amp;nbsp;осмыслении nauty речи не&amp;nbsp;идёт. Приятным исключением является &lt;a href="http://www.iupac.org/inchi/download/index.html"&gt;InChI&lt;/a&gt;, в&amp;nbsp;которой присутствует алгоритм, явно сделанный с&amp;nbsp;оглядкой на&amp;nbsp;nauty. Настоящая популярность к&amp;nbsp;алгоритму МакКея придёт тогда, когда в&amp;nbsp;каждом пакете он&amp;nbsp;будет реализован по-своему.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Перестановки и&amp;nbsp;упорядоченные разбиения&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Говоря здесь о&amp;nbsp;&lt;i&gt;разбиениях&lt;/i&gt;, мы&amp;nbsp;подразумеваем упорядоченные разбиения множества вершин графа, т.е. представления этого множества вершин виде упорядоченного набора его попарно непересекающихся непустых подмножеств. [a&amp;nbsp;b&amp;nbsp;c&amp;nbsp;|&amp;nbsp;d&amp;nbsp;e]&amp;nbsp;и&amp;nbsp;[a&amp;nbsp;c&amp;nbsp;b&amp;nbsp;|&amp;nbsp;e&amp;nbsp;d]&amp;nbsp;&amp;#8212; одно и&amp;nbsp;то&amp;nbsp;же разбиение, но&amp;nbsp;[d&amp;nbsp;e&amp;nbsp;|&amp;nbsp;a&amp;nbsp;b&amp;nbsp;c]&amp;nbsp;от него отличается. Подмножества [a&amp;nbsp;b&amp;nbsp;c]&amp;nbsp;и&amp;nbsp;[d&amp;nbsp;e]&amp;nbsp;называются &lt;i&gt;ячейками&lt;/i&gt; (cells).&lt;br /&gt;&lt;br /&gt;Если каждая ячейка разбиения &amp;#960;&lt;sub&gt;1&lt;/sub&gt;&amp;nbsp;является подмножеством какой-либо ячейки разбиения &amp;#960;&lt;sub&gt;2&lt;/sub&gt;, то&amp;nbsp;&amp;#960;&lt;sub&gt;1&lt;/sub&gt;&amp;nbsp;называется &lt;i&gt;подразбиением&lt;/i&gt; &amp;#960;&lt;sub&gt;2&lt;/sub&gt;, а&amp;nbsp;&amp;#960;&lt;sub&gt;2&lt;/sub&gt;&amp;nbsp;&amp;#8212; &lt;i&gt;надразбиением&lt;/i&gt; &amp;#960;&lt;sub&gt;1&lt;/sub&gt;. Также говорят, что &amp;#960;&lt;sub&gt;1&lt;/sub&gt;&amp;nbsp;&lt;i&gt;мельче&lt;/i&gt; &amp;#960;&lt;sub&gt;2&lt;/sub&gt;, а&amp;nbsp;&amp;#960;&lt;sub&gt;2&lt;/sub&gt;&amp;nbsp;&lt;i&gt;крупнее&lt;/i&gt; &amp;#960;&lt;sub&gt;1&lt;/sub&gt;. Мы&amp;nbsp;подразумеваем, что при подразбиении сохраняется порядок ячеек. Например, [a&amp;nbsp;b&amp;nbsp;|&amp;nbsp;c&amp;nbsp;|&amp;nbsp;d&amp;nbsp;e]&amp;nbsp;является подразбиением [a&amp;nbsp;b&amp;nbsp;c&amp;nbsp;|&amp;nbsp;d&amp;nbsp;e]&amp;nbsp;и&amp;nbsp;[a&amp;nbsp;b&amp;nbsp;|&amp;nbsp;c&amp;nbsp;d&amp;nbsp;e], но&amp;nbsp;не&amp;nbsp;[c&amp;nbsp;|&amp;nbsp;a&amp;nbsp;b&amp;nbsp;d&amp;nbsp;e].&lt;br /&gt;&lt;br /&gt;Разбиение, в&amp;nbsp;котором все подмножества состоят из&amp;nbsp;одного элемента, называется &lt;i&gt;дискретным&lt;/i&gt; (discrete partition). Дискретное разбиение очевидным образом образует &lt;i&gt;перестановку&lt;/i&gt; (permutation) вершин. Например, дискретное разбиение [b&amp;nbsp;|&amp;nbsp;c&amp;nbsp;|&amp;nbsp;a&amp;nbsp;|&amp;nbsp;d&amp;nbsp;|&amp;nbsp;e]&amp;nbsp;образует перестановку (b,&amp;nbsp;c,&amp;nbsp;a,&amp;nbsp;d,&amp;nbsp;e).&lt;br /&gt;&lt;br /&gt;Разбиение, состоящее из&amp;nbsp;одной ячейки, называется &lt;i&gt;элементарным&lt;/i&gt; (unit partition). Любое разбиение является подразбиением элементарного.&lt;br /&gt;&lt;br /&gt;Покажем процедуру перебора всех перестановок вершин, основанную на&amp;nbsp;переборе разбиений. При своей крайней примитивности, она является базой всего алгоритма канонической нумерации. Для получения всех перестановок в&amp;nbsp;процедуру следует передать элементарное разбиение.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;function AllPermutations&lt;/code&gt;(π = [V&lt;sub&gt;0&lt;/sub&gt;|...|V&lt;sub&gt;k&lt;/sub&gt;])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if&lt;/code&gt; π — дискретное разбиение:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;yield&lt;/code&gt; π&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;else&lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;выбрать V&lt;sub&gt;i&lt;/sub&gt; ∈ π : |V&lt;sub&gt;i&lt;/sub&gt;| ≠ 1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;for&lt;/code&gt; v ∈ V&lt;sub&gt;i&lt;/sub&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;AllPermutations&lt;/code&gt;([V&lt;sub&gt;0&lt;/sub&gt;|...|{v}|V&lt;sub&gt;i&lt;/sub&gt;\v|...|V&lt;sub&gt;k&lt;/sub&gt;])&lt;br /&gt;&lt;br /&gt;На&amp;nbsp;этом месте у&amp;nbsp;читателя может возникнуть вопрос: зачем нужны разбиения, если наша цель&amp;nbsp;&amp;#8212; перебор перестановок? Да,&amp;nbsp;для перебора всех перестановок не&amp;nbsp;нужно вводить понятие разбиений. Но&amp;nbsp;на&amp;nbsp;самом деле, все перестановки нам не&amp;nbsp;нужны, а&amp;nbsp;нужна одна перестановка&amp;nbsp;&amp;#8212; каноническая, которую мы&amp;nbsp;выбираем как &amp;laquo;лучшую&amp;raquo; среди какого-то класса перестановок (не&amp;nbsp;обязательно всех). Очевидно, что чем меньше этот класс, тем лучше, но&amp;nbsp;есть условие: состав этого класса перестановок не&amp;nbsp;должен зависеть от&amp;nbsp;исходной нумерации вершин. Именно здесь нам поможет идея разбиений: мы&amp;nbsp;будем не&amp;nbsp;отбрасывать перестановки, а&amp;nbsp;уточнять разбиения, участвующие в&amp;nbsp;переборе.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Годные разбиения&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Мы&amp;nbsp;называем разбиение &amp;#960; &lt;i&gt;годным&lt;/i&gt; (equitable), если для него выполняется следующее свойство: для любых двух ячеек V&lt;sub&gt;1&lt;/sub&gt;,&amp;nbsp;V&lt;sub&gt;2&lt;/sub&gt;&amp;nbsp;&amp;#8712;&amp;nbsp;&amp;#960; (не&amp;nbsp;обязательно различных) и&amp;nbsp;для любых v&lt;sub&gt;1&lt;/sub&gt;,&amp;nbsp;v&lt;sub&gt;2&lt;/sub&gt;&amp;nbsp;&amp;#8712;&amp;nbsp;V&lt;sub&gt;1&lt;/sub&gt;&amp;nbsp;выполняется равенство d(v&lt;sub&gt;1&lt;/sub&gt;,&amp;nbsp;V&lt;sub&gt;2&lt;/sub&gt;)=d(v&lt;sub&gt;2&lt;/sub&gt;,&amp;nbsp;V&lt;sub&gt;2&lt;/sub&gt;), где d(v,V)&amp;nbsp;&amp;#8212; количество вершин в&amp;nbsp;V,&amp;nbsp;смежных с&amp;nbsp;v. Очевидно, что все дискретные разбиения являются годнымы.&lt;br /&gt;&lt;br /&gt;В&amp;nbsp;работе МакКея доказано, что для каждого разбиения &amp;#960; существует единственное его наикрупнейшее подразбиение, являющееся годным. Процедура уточнения (refine) разбиения находит это годное подразбиение. В&amp;nbsp;упрощённом виде она выглядит так:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;function Refine&lt;/code&gt;(π = [V&lt;sub&gt;0&lt;/sub&gt;|...|V&lt;sub&gt;k&lt;/sub&gt;])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;for V&lt;sub&gt;i&lt;/sub&gt; ∈ π &lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;for V&lt;sub&gt;j&lt;/sub&gt; ∈ π&lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;подразбить V&lt;sub&gt;j&lt;/sub&gt;, сгруппировав элементы v ∈ V&lt;sub&gt;j&lt;/sub&gt; по d(v, V&lt;sub&gt;i&lt;/sub&gt;) и отсортировав по возрастанию d(v, V&lt;sub&gt;i&lt;/sub&gt;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;заменить V&lt;sub&gt;j&lt;/sub&gt; в π на результат этого подразбиения, удлиняя цикл по V&lt;sub&gt;i&lt;/sub&gt;, но не удлиняя цикл по V&lt;sub&gt;j&lt;/sub&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;return π&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Следовательно, чтобы перебрать все годные подразбиения и&amp;nbsp;все происходящие от&amp;nbsp;них перестановки, нам нужно всего лишь вставить вызов процедуры Refine в&amp;nbsp;перебор:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;function AllEquitablePermutations&lt;/code&gt;(π = [V&lt;sub&gt;0&lt;/sub&gt;|...|V&lt;sub&gt;k&lt;/sub&gt;])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;Refine(π)&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if&lt;/code&gt; π — дискретное разбиение:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;yield&lt;/code&gt; π&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;else&lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;выбрать V&lt;sub&gt;i&lt;/sub&gt; ∈ π : |V&lt;sub&gt;i&lt;/sub&gt;| ≠ 1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;for&lt;/code&gt; v ∈ V&lt;sub&gt;i&lt;/sub&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;AllPermutations&lt;/code&gt;([V&lt;sub&gt;0&lt;/sub&gt;|...|{v}|V&lt;sub&gt;i&lt;/sub&gt;\v|...|V&lt;sub&gt;k&lt;/sub&gt;])&lt;br /&gt;&lt;br /&gt;Процесс перебора перестановок можно изобразить как дерево разбиений, которое обходится слева направо. Среди листьев первым при обходе встречается самый левый. Обозначим его &amp;#958;.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_BaonBoyqin8/SlCs8IaY-zI/AAAAAAAAAC0/F7aMzv-On9Y/s1600-h/tree.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 223px;" src="http://3.bp.blogspot.com/_BaonBoyqin8/SlCs8IaY-zI/AAAAAAAAAC0/F7aMzv-On9Y/s400/tree.png" alt="" id="BLOGGER_PHOTO_ID_5354970105993820978" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;(Правая половина дерева показана не целиком. Вызовы &lt;code&gt;Refine&lt;/code&gt;, которые не уточняют подразбиение, также опущены.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Сравнения перестановок и автоморфизмы&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Наша задача&amp;nbsp;&amp;#8212; найти каноническую перестановку множества вершин исходного графа. Для этого нужно обзавестить функцией сравнения перестановок cmp, которая&amp;nbsp;бы определяла, которая из&amp;nbsp;двух &amp;laquo;лучше&amp;raquo; (или они одинаковые). Функция должна задавать транзитивное отношение, а&amp;nbsp;её&amp;nbsp;результат не&amp;nbsp;должен зависеть от&amp;nbsp;первоначальной нумерации вершин графа. Пример такой функции: по&amp;nbsp;исходному графу G&amp;nbsp;и&amp;nbsp;перестановкам &amp;#960;&lt;sub&gt;1&lt;/sub&gt;&amp;nbsp;и&amp;nbsp;&amp;#960;&lt;sub&gt;2&lt;/sub&gt;&amp;nbsp;построить графы G&lt;sub&gt;1&lt;/sub&gt; и&amp;nbsp;G&lt;sub&gt;2&lt;/sub&gt;, вычислить их&amp;nbsp;матрицы смежности, и&amp;nbsp;в&amp;nbsp;качестве cmp(&amp;#960;&lt;sub&gt;1&lt;/sub&gt;, &amp;#960;&lt;sub&gt;2&lt;/sub&gt;) вернуть результат лексикографического сравнения матриц, развёрнутых в&amp;nbsp;строки. Таким образом, канонической перестановкой будет считаться та,&amp;nbsp;для которой матрица смежности наибольшая; если таких перестановок две и&amp;nbsp;более, то&amp;nbsp;они считаются неразличимыми и&amp;nbsp;в&amp;nbsp;качестве канонической можно взять любую из&amp;nbsp;них.&lt;br /&gt;&lt;br /&gt;Какой смысл для нас имеют &amp;laquo;неразличимые&amp;raquo; перестановки? Пусть cmp(&amp;#960;&lt;sub&gt;1&lt;/sub&gt;, &amp;#960;&lt;sub&gt;2&lt;/sub&gt;)=0. Несложно доказать, что в&amp;nbsp;этом случае перестановка &amp;#947;=&amp;#960;&lt;sub&gt;2&lt;/sub&gt;&lt;sup&gt;-1&lt;/sup&gt;&amp;#8728;&amp;#960;&lt;sub&gt;1&lt;/sub&gt; (композиция &amp;#960;&lt;sub&gt;1&lt;/sub&gt;&amp;nbsp;и&amp;nbsp;перестановки, обратной &amp;#960;&lt;sub&gt;2&lt;/sub&gt;) является &lt;a href="http://mathworld.wolfram.com/GraphAutomorphism.html"&gt;автоморфизмом&lt;/a&gt; вершин исходного графа.&lt;br /&gt;&lt;br /&gt;Использование автоморфизмов позволяет существенно (иногда&amp;nbsp;&amp;#8212; на&amp;nbsp;порядки) сократить перебор при нахождении канонической перестановки. Поэтому в&amp;nbsp;nauty и&amp;nbsp;производных алгоритмах обязательно осуществляется нахождение всей группы автоморфизмов графа, тогда как нахождение собственно канонической нумерации сделано опцией.&lt;br /&gt;&lt;br /&gt;Есть следующий способ найти &lt;a href="http://en.wikipedia.org/wiki/Generating_set_of_a_group"&gt;генераторы&lt;/a&gt; группы автоморфизмов графа: при обработке первого встретившегося дискретного разбиения &amp;#958; оно сохраняется. Каждое последующее дискретное разибение &amp;#960; сравнивается с&amp;nbsp;&amp;#958;: если перестановка &amp;#958;&lt;sup&gt;-1&lt;/sup&gt;&amp;#8728;&amp;#960;&amp;nbsp;является автоморфизмом графа, то&amp;nbsp;эта перестановка записывается в&amp;nbsp;группу генераторов.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Сокращение перебора 1:&amp;nbsp;автоморфизм &amp;#958;&lt;sup&gt;-1&lt;/sup&gt;&amp;#8728;&amp;#960;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;После обнаружения автоморфизма &amp;#958;&lt;sup&gt;-1&lt;/sup&gt;&amp;#8728;&amp;#960;, обход можно &amp;laquo;поднять&amp;raquo; по&amp;nbsp;дереву до&amp;nbsp;общего предка &amp;#960; и&amp;nbsp;&amp;#958;, поскольку поддерево этого предка, в&amp;nbsp;котором находится &amp;#960;, &amp;laquo;изоморфно&amp;raquo; поддереву, в&amp;nbsp;котором находится &amp;#958;, и&amp;nbsp;не&amp;nbsp;содержит новой информации. Строгое доказательство приведено в&amp;nbsp;работе МакКея.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Сокращение перебора 2:&amp;nbsp;автоморфизм &amp;#961;&lt;sup&gt;-1&lt;/sup&gt;&amp;#8728;&amp;#960;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Как было сказано, поиск канонической нумерации&amp;nbsp;&amp;#8212; только опция в&amp;nbsp;алгоритме нахождения автоморфизмов. Когда эта опция включена, мы&amp;nbsp;храним ещё одну перестановку &amp;#961;, которая является &amp;laquo;лучшей&amp;raquo; на&amp;nbsp;данный момент (best so&amp;nbsp;far, BSF). При обнаружении первой перестановки &amp;#958; мы&amp;nbsp;копируем её&amp;nbsp;в&amp;nbsp;&amp;#961;. Каждую последующую перестановку &amp;#960; мы&amp;nbsp;сравниваем с&amp;nbsp;&amp;#961;. Если cmp(&amp;#960;, &amp;#961;) &amp;gt; 0,&amp;nbsp;то&amp;nbsp;мы&amp;nbsp;копируем &amp;#960; в&amp;nbsp;&amp;#961;. Если cmp(&amp;#960;, &amp;#961;) &amp;lt; 0,&amp;nbsp;то&amp;nbsp;ничего не&amp;nbsp;делаем. Если&amp;nbsp;же cmp(&amp;#960;, &amp;#961;) = 0,&amp;nbsp;то&amp;nbsp;&amp;#961;&lt;sup&gt;-1&lt;/sup&gt;&amp;#8728;&amp;#960;&amp;nbsp;является новым автоморфизмом, и&amp;nbsp;обход можно &amp;laquo;поднять&amp;raquo; по&amp;nbsp;дереву до&amp;nbsp;общего предка &amp;#960; и&amp;nbsp;&amp;#961;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Сокращение перебора 3:&amp;nbsp;орбиты&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://mathworld.wolfram.com/GroupOrbit.html"&gt;Орбита&lt;/a&gt; вершины v&amp;nbsp;&amp;#8212; это подмножество вершин, в&amp;nbsp;которые может перейти v&amp;nbsp;в&amp;nbsp;результате действия автоморфизмов. Например, на&amp;nbsp;следующем рисунке показаны орбиты вершин графа:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_BaonBoyqin8/SlCuXnZiD8I/AAAAAAAAAC8/qzps-922iNM/s1600-h/orbits.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 389px; height: 186px;" src="http://3.bp.blogspot.com/_BaonBoyqin8/SlCuXnZiD8I/AAAAAAAAAC8/qzps-922iNM/s400/orbits.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5354971677679816642" /&gt;&lt;/a&gt;&lt;br /&gt;В&amp;nbsp;начале работы nauty каждая вершина принадлежит своей собственной орбите. По&amp;nbsp;мере открывания новых автоморфизмов орбиты увеличиваются, а&amp;nbsp;их&amp;nbsp;количество&amp;nbsp;&amp;#8212; сокращается. Например, если найден автоморфизм (a,&amp;nbsp;b,&amp;nbsp;c,&amp;nbsp;d,&amp;nbsp;e)&amp;nbsp;&amp;#8594; (b,&amp;nbsp;a,&amp;nbsp;c,&amp;nbsp;e,&amp;nbsp;d), то&amp;nbsp;список орбит из&amp;nbsp;[a][b][c][d][e] становится [a&amp;nbsp;b]&amp;nbsp;[c]&amp;nbsp;[d&amp;nbsp;e]. Если после этого найден автоморфизм (a,&amp;nbsp;b,&amp;nbsp;c,&amp;nbsp;d, e)&amp;nbsp;&amp;#8594; (a,&amp;nbsp;c,&amp;nbsp;b,&amp;nbsp;d,&amp;nbsp;e), то&amp;nbsp;список орбит становится [a&amp;nbsp;b&amp;nbsp;c]&amp;nbsp;[d&amp;nbsp;e]&lt;br /&gt;&lt;br /&gt;Рассмотрим &amp;laquo;линию предков&amp;raquo; первого дискретного разбиения &amp;#958;. Предположим, мы&amp;nbsp;в&amp;nbsp;процессе обхода дерева вернулись к&amp;nbsp;какому-либо из&amp;nbsp;предков &amp;#958; (назовём его &amp;#957;), чтобы продолжить обход других потомков &amp;#957;, перебирая другие элементы ячейки V&lt;sub&gt;i&lt;/sub&gt;. Если рассматриваемый элемент v&lt;sub&gt;k&lt;/sub&gt; &amp;#8712; V&lt;sub&gt;i&lt;/sub&gt; лежит на&amp;nbsp;одной орбите с&amp;nbsp;уже рассмотренным элементом v&lt;sub&gt;j&lt;/sub&gt; &amp;#8712; V&lt;sub&gt;i&lt;/sub&gt;, то&amp;nbsp;существует автоморфизм, открытый в&amp;nbsp;процессе обхода более ранних узлов, который переводит v&lt;sub&gt;j&lt;/sub&gt; в&amp;nbsp;v&lt;sub&gt;k&lt;/sub&gt;. В&amp;nbsp;работе МакКея доказано, что в&amp;nbsp;этом случае нет смысла рассматривать всё поддерево, произведённое отделением v&lt;sub&gt;k&lt;/sub&gt;: все листья этого поддерева будут изоморфны ранее открытым листьям, и&amp;nbsp;все автоморфизмы, которые будут найдены при обходе этого поддерева, будут повторять предыдущие.&lt;br /&gt;&lt;br /&gt;Небольшое замечание: после реализации процедуры &lt;code&gt;Refine&lt;/code&gt; может показаться, что уточнение тривиального разбиения как раз и&amp;nbsp;определяет орбиты вершин. Для многих графов это действительно так, но&amp;nbsp;не&amp;nbsp;для всех. На&amp;nbsp;следующем рисунке показан пример графа, на&amp;nbsp;котором это не&amp;nbsp;выполняется:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_BaonBoyqin8/SlCyDoOksCI/AAAAAAAAADM/IOdKm_HUG6w/s1600-h/orbits-not-refine.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 331px; height: 212px;" src="http://1.bp.blogspot.com/_BaonBoyqin8/SlCyDoOksCI/AAAAAAAAADM/IOdKm_HUG6w/s400/orbits-not-refine.png" alt="" id="BLOGGER_PHOTO_ID_5354975732351414306" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Для всех графов, однако, верно то,&amp;nbsp;что разбиение по&amp;nbsp;орбитам является подразбиением уточнения тривиального разбиения.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Сокращение перебора 4:&amp;nbsp;fix и&amp;nbsp;mcr&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;С&amp;nbsp;узлами, которые не&amp;nbsp;являются предками &amp;#958;, предыдущее сокращение перебора делать нельзя. Для них есть менее сильный, но&amp;nbsp;тоже действенный критерий отсечения потомков. Для применения этого критерия необходимо сохранять информацию о&amp;nbsp;каждом автоморфизме в&amp;nbsp;отдельности (вместо множества орбит, которое накапливает информацию о&amp;nbsp;всех автоморфизмах в&amp;nbsp;совокупности). Для каждого найденного автоморфизма &amp;#947; запоминаются два множества: fix(&amp;#947;) и&amp;nbsp;mcr(&amp;#947;). fix(&amp;#947;)&amp;nbsp;&amp;#8212; это множество вершин, которое при действиии автоморфизма остаются на&amp;nbsp;своих местах. mcr(&amp;#947;)&amp;nbsp;&amp;#8212; это множество вершин, которые являются минимальными по&amp;nbsp;номеру в&amp;nbsp;своих орбитах. Имеются в&amp;nbsp;виду орбиты, составленные по&amp;nbsp;одному автоморфизму &amp;#947;, а&amp;nbsp;не&amp;nbsp;те,&amp;nbsp;которые используются в&amp;nbsp;предыдущем способе отсечения.&lt;br /&gt;&lt;br /&gt;Рассмотрим множество V&lt;sub&gt;i&lt;/sub&gt;, элементы которого мы&amp;nbsp;перебираем для продолжения обхода дерева из&amp;nbsp;какого-то узла, глубины&amp;nbsp;l. В&amp;nbsp;процессе пути от&amp;nbsp;корня дерева до&amp;nbsp;этого узла l&amp;nbsp;вершин были &amp;laquo;зафиксированы&amp;raquo;, когда мы&amp;nbsp;отделяли их&amp;nbsp;от&amp;nbsp;их&amp;nbsp;ячеек. Назовём множество этих вершин fixed.&lt;br /&gt;&lt;br /&gt;В&amp;nbsp;работе МаКея доказано следующее: если fixed &amp;#8834; fix(&amp;#947;), то&amp;nbsp;можно присвоить в&amp;nbsp;V&lt;sub&gt;i&lt;/sub&gt; пересечение V&lt;sub&gt;i&lt;/sub&gt; &amp;#8745; mcr(&amp;#947;) без ущерба для алгоритма. Фактически, речь идёт об&amp;nbsp;автоморфизмах, которые &amp;laquo;не&amp;nbsp;затрагивают&amp;raquo; вершины, которые мы&amp;nbsp;зафиксировали. Их&amp;nbsp;орбиты можно эксплуатировать так&amp;nbsp;же, как и&amp;nbsp;в&amp;nbsp;предыдущем разделе, что и&amp;nbsp;делается путём пересечения V&lt;sub&gt;i&lt;/sub&gt; и&amp;nbsp;mcr(&amp;#947;). Означенная операция выполняется для всех &amp;#947;, fix и&amp;nbsp;mcr которых мы&amp;nbsp;успели сохранить на&amp;nbsp;данный момент. Следует отметить, что генераторов группы автоморфизмов графа может быть очень много, и&amp;nbsp;сохранять все fix и&amp;nbsp;mcr не&amp;nbsp;всегда возможно. МакКей рекомендует ограничивать количество сохраняемых fix и&amp;nbsp;mcr числом, пропорциональным количеству вершин в&amp;nbsp;графе, с&amp;nbsp;коэффициентом пропорции больше 1&amp;nbsp;(у&amp;nbsp;него почему-то 50/32).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Алгоритм целиком&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;В&amp;nbsp;коде имеются глобальные переменные:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;getcanon&lt;/code&gt;&amp;nbsp;&amp;#8212; опция получения канонической нумерации в&amp;nbsp;&amp;#961;&lt;/li&gt;&lt;li&gt;&lt;code&gt;gca_first&lt;/code&gt;&amp;nbsp;&amp;#8212; уровень общего предка текущего узла и&amp;nbsp;&amp;#958;&lt;/li&gt;&lt;li&gt;&lt;code&gt;gca_canon&lt;/code&gt;&amp;nbsp;&amp;#8212; уровень общего предка текущего узла и&amp;nbsp;&amp;#961;&lt;/li&gt;&lt;li&gt;&lt;code&gt;orbits&lt;/code&gt;&amp;nbsp;&amp;#8212; массив индексов орбит; содержит для каждой вершины номер минимальной вершины в&amp;nbsp;её&amp;nbsp;орбите&lt;/li&gt;&lt;li&gt;&amp;#936;&amp;nbsp;&amp;#8212; списов посчитанных множеств fix и&amp;nbsp;mcr для найденных автоморфизмов&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;code&gt;function AutomorphismSearch&lt;/code&gt;(π)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;FirstNode&lt;/code&gt;(1, π)&lt;br /&gt;&lt;br /&gt;&lt;code&gt;function FirstNode&lt;/code&gt;(level, π = [V&lt;sub&gt;0&lt;/sub&gt;|...|V&lt;sub&gt;k&lt;/sub&gt;])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;π ← &lt;code&gt;Refine&lt;/code&gt;(π)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if&lt;/code&gt; π — дискретное:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ξ ← π&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;gca_first ← level&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if getcanon&lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ρ ← π&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;gca_canon ← level&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;return level-1&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;V&lt;sub&gt;i&lt;/sub&gt; ← &lt;/code&gt;первая ячейка π, в которой больше 1 вершины&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;for v ∈ V&lt;sub&gt;i&lt;/sub&gt;&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if orbits[v] ≠ v:&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;continue&lt;/code&gt; &lt;i&gt;(сокращение 3)&lt;/i&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;π'←[V&lt;sub&gt;0&lt;/sub&gt;|...|{v}|V&lt;sub&gt;i&lt;/sub&gt;\v|...|V&lt;sub&gt;k&lt;/sub&gt;]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;fixed ← fixed ∪ v&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if&lt;/code&gt; v — первая вершина в V&lt;sub&gt;i&lt;/sub&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;rtnlevel ← FirstNode(level+1, π')&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;gca_first ← level&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;else&lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;rtnlevel ← OtherNode(level+1, π')&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;fixed ← fixed \ v&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if rtnlevel &amp;lt; level:&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;return rtnlevel&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if level &amp;lt; gca_canon:&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;gca_canon ← level&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;return level-1&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;function OtherNode&lt;/code&gt;(level, π = [V&lt;sub&gt;0&lt;/sub&gt;|...|V&lt;sub&gt;k&lt;/sub&gt;])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;π ← &lt;code&gt;Refine&lt;/code&gt;(π)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if&lt;/code&gt; π — дискретное:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if cmp(π,ξ)=0&lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;AddFixMcr&lt;/code&gt;(ξ&lt;sup&gt;-1&lt;/sup&gt;∘π)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;JoinOrbits&lt;/code&gt;(ξ&lt;sup&gt;-1&lt;/sup&gt;∘π)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;return gca_first&lt;/code&gt; &lt;i&gt;(сокращение 1)&lt;/i&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if getcanon:&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;comp ← cmp(π, ρ)&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if comp=0&lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;AddFixMcr&lt;/code&gt;(ρ&lt;sup&gt;-1&lt;/sup&gt;∘π)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;JoinOrbits&lt;/code&gt;(ρ&lt;sup&gt;-1&lt;/sup&gt;∘π)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;return gca_canon&lt;/code&gt; &lt;i&gt;(сокращение 2)&lt;/i&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if comp&amp;gt;0&lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ρ ← π&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;return level-1&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;V&lt;sub&gt;i&lt;/sub&gt; ← &lt;/code&gt;первая ячейка π, в которой больше 1 вершины&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;for (fix, mcr) ∈ Ψ&lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if fixed ⊂ fix&lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;V&lt;sub&gt;i&lt;/sub&gt; ← V&lt;sub&gt;i&lt;/sub&gt; ∩ mcr &lt;i&gt;(сокращение 4)&lt;/i&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;for v ∈ V&lt;sub&gt;i&lt;/sub&gt;&lt;/code&gt;:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;π'←[V&lt;sub&gt;0&lt;/sub&gt;|...|{v}|V&lt;sub&gt;i&lt;/sub&gt;\v|...|V&lt;sub&gt;k&lt;/sub&gt;]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;fixed ← fixed ∪ v&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;rtnlevel ← OtherNode(level+1, π')&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;fixed ← fixed \ v&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if rtnlevel &amp;lt; level:&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;return rtnlevel&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;if level &amp;lt; gca_canon:&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;gca_canon ← level&lt;/code&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;code&gt;return level-1&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Детали реализации&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Авторская имплементация nauty содержит трюки для увеличения быстродействия и&amp;nbsp;уменьшения расхода памяти:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Для хранения графа и&amp;nbsp;множеств используются битовые массивы&lt;/li&gt;&lt;li&gt;Вся цепочка подразбиений от&amp;nbsp;корня дерева к&amp;nbsp;текущему узлу кодируется всего двумя массивами (lab и&amp;nbsp;ptn). При возвращении к&amp;nbsp;родительскому узлу разбиение восстанавливается функцией recover&lt;/li&gt;&lt;/ul&gt;Подробности можно узнать в&amp;nbsp;&lt;a href="http://cs.anu.edu.au/%7Ebdm/nauty/nug.pdf"&gt;nauty User&amp;#146;s&amp;nbsp;Guide&lt;/a&gt; и&amp;nbsp;в&amp;nbsp;коде самой nauty.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Применение&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Прелесть изложенного алгоритма в&amp;nbsp;том, что в&amp;nbsp;нём не&amp;nbsp;много завязок на&amp;nbsp;то,&amp;nbsp;что исходное множество вершин&amp;nbsp;&amp;#8212; это вершины простого графа. Фактически, можно находить автоморфизмы и&amp;nbsp;каноническую нумерацию для чего угодно, например:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Для ориентированных графов. Никаких изменений в&amp;nbsp;алгоритме не&amp;nbsp;требуется. МакКей в&amp;nbsp;статье упоминает ориентированные графы, и&amp;nbsp;в&amp;nbsp;nauty есть соответствующая опция &lt;code&gt;digraph&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Для графов с&amp;nbsp;раскрашенными вершинами. Всё, что требуется&amp;nbsp;&amp;#8212; начинать не&amp;nbsp;с&amp;nbsp;элементарного разбиения, а&amp;nbsp;сгруппировать вершины по&amp;nbsp;цветам. Эта опция также описана у&amp;nbsp;МакКея и&amp;nbsp;имеется в&amp;nbsp;nauty&lt;/li&gt;&lt;li&gt;Для графов с&amp;nbsp;раскрашенными рёбрами. Всё, что требуется&amp;nbsp;&amp;#8212; изменить функцию &lt;code&gt;cmp&lt;/code&gt; так, чтобы она учитывала цвета рёбер. Также, можно более эффективно написать процедуру &lt;code&gt;Refine&lt;/code&gt; (уточнение разбиения)&lt;/li&gt;&lt;li&gt;Для молекул со&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/Chirality_%28chemistry%29"&gt;стереохимическими&lt;/a&gt; &lt;a href="http://en.wikipedia.org/wiki/Cis-trans_isomerism"&gt;свойствами&lt;/a&gt;. Опять&amp;nbsp;же, модификации требуются только в&amp;nbsp;функции &lt;code&gt;cmp&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;Модификации алгоритма&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Не&amp;nbsp;было упомянуто об&amp;nbsp;огромном количестве дополнительных оптимизаций, имеющихся в&amp;nbsp;коде nauty. МакКей признаёт, что у&amp;nbsp;него самого не&amp;nbsp;все они задокументированы. Вот только некоторые:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&amp;laquo;Даровые&amp;raquo; автоморфизмы (cheap automorphisms, implicit automorphisms), для обнаружения которых не&amp;nbsp;требуется доходить до&amp;nbsp;дискретного разбиения. Работают в&amp;nbsp;nauty только для неориентированных графов (цвета вершин допускаются)&lt;/li&gt;&lt;li&gt;Проверка автоморфизма &lt;code&gt;cmp&lt;/code&gt;(&amp;#960;,&amp;#958;) может делаться более эффективно без вызова &lt;code&gt;cmp&lt;/code&gt;, с&amp;nbsp;учётом того, что &amp;#958; никогда не&amp;nbsp;меняется и&amp;nbsp;нас интересует только то,&amp;nbsp;есть&amp;nbsp;ли изоморфизм&lt;/li&gt;&lt;li&gt;Псевдо-канонические коды разбиений, благодаря которым можно выявить отсутствие автоморфизма, не&amp;nbsp;проверяя его явно. Они&amp;nbsp;же используются для предварительного определения знака функции &lt;code&gt;cmp&lt;/code&gt;&lt;/li&gt;&lt;li&gt;При выборе ячейки V&lt;sub&gt;i&lt;/sub&gt; для подразбиения можно применять различные эвристики, с&amp;nbsp;целью сокращения перебора&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-5597241540471934092?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/5597241540471934092/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=5597241540471934092' title='Комментарии: 5'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/5597241540471934092'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/5597241540471934092'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2009/06/graph-automorphisms-canonical-labeling.html' title='Graph automorphisms, canonical labeling, nauty'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_BaonBoyqin8/SlCs8IaY-zI/AAAAAAAAAC0/F7aMzv-On9Y/s72-c/tree.png' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-1306487819623707747</id><published>2009-03-31T10:35:00.006+04:00</published><updated>2009-07-05T23:00:08.652+04:00</updated><title type='text'>Рекурсивные функции на Common Lisp</title><content type='html'>&lt;span style="font-weight: bold;"&gt;1. Перебор всех подписков данного списка&lt;/span&gt;&lt;pre&gt;(defun for-all-subsets (list func)&lt;br /&gt;(unless list&lt;br /&gt;  (funcall func nil))&lt;br /&gt;(when list&lt;br /&gt;  (for-all-subsets&lt;br /&gt;   (cdr list)&lt;br /&gt;   (lambda (s) (funcall func (cons (car list) s))))&lt;br /&gt;  (for-all-subsets (cdr list) func)))&lt;/pre&gt;&lt;code&gt;func&lt;/code&gt; — функция одного параметра —  вызывается для каждого подсписка.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;2. Перебор всех подсписков заданного размера&lt;/span&gt;&lt;pre&gt;(defun for-all-subsets-size (list size func)&lt;br /&gt;(if (zerop size)&lt;br /&gt;    (funcall func nil)&lt;br /&gt;    (when list&lt;br /&gt;      (for-all-subsets-size&lt;br /&gt;       (cdr list) (1- size)&lt;br /&gt;       (lambda (s) (funcall func (cons (car list) s))))&lt;br /&gt;      (when (&amp;lt; size (length list-set))&lt;br /&gt;         (for-all-subsets (cdr list-set) size func)))))&lt;/pre&gt;&lt;code&gt;func&lt;/code&gt; — функция одного параметра —  вызывается для каждого подсписка.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;3. Перебор всех подсписков, полученных из данного удалением одного элемента&lt;/span&gt;&lt;pre&gt;(defun for-all-cuts (list func)&lt;br /&gt;(declare (list list) (function func))&lt;br /&gt;(when list&lt;br /&gt;  (funcall func (car list) (cdr list)))&lt;br /&gt;(when (cdr list)&lt;br /&gt;  (for-all-cuts (cdr list)&lt;br /&gt;   (lambda (elem cut)&lt;br /&gt;     (funcall func elem (cons (car list) cut))))))&lt;/pre&gt;&lt;code&gt;func&lt;/code&gt; — функция двух параметров — вызывается для каждого элемента и соответствующего подсписка, который получен его удалением из исходного списка.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;4. Перебор максимальных по включению подсписков, удовлетворяющих какому-либо условию&lt;/span&gt;&lt;pre&gt;(defun for-all-max-subsets (set func)&lt;br /&gt;(if set&lt;br /&gt;    (let ((saved))&lt;br /&gt;      (for-all-max-subsets&lt;br /&gt;       (cdr set)&lt;br /&gt;       (lambda (s)&lt;br /&gt;         (when (funcall func (cons (car set) s))&lt;br /&gt;           (push s saved))))&lt;br /&gt;      (for-all-max-subsets&lt;br /&gt;       (cdr set)&lt;br /&gt;       (lambda (s)&lt;br /&gt;         (or (position s saved :test #'equalp)&lt;br /&gt;             (funcall func s)))))&lt;br /&gt;    (progn&lt;br /&gt;      (funcall func nil)&lt;br /&gt;      nil)))&lt;/pre&gt;&lt;code&gt;func&lt;/code&gt; — функция одного параметра — получает список и должна возвращать &lt;code&gt;nil&lt;/code&gt;, если список не годится. Если для какого-то списка &lt;code&gt;func&lt;/code&gt; вернула ненулевое значение, все подсписки этого списка не будут проверяться.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;5. Перебор всех циклических перестановок данного списка&lt;/span&gt;&lt;pre&gt;(defun for-all-cyclic-permutations (list func)&lt;br /&gt;  (funcall func list)&lt;br /&gt;  (do ((iter (cdr list) (cdr iter))&lt;br /&gt;             (prev list))&lt;br /&gt;      ((not iter))&lt;br /&gt;    (when prev&lt;br /&gt;       (setf (cdr prev) nil)&lt;br /&gt;       (funcall func (append iter list))&lt;br /&gt;       (setf (cdr prev) iter))&lt;br /&gt;       (setf prev iter)))&lt;/pre&gt;&lt;code&gt;func&lt;/code&gt; — функция одного параметра —  вызывается для каждой циклической перестановки.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;Примеры:&lt;/span&gt;&lt;pre&gt;&gt; (for-all-subsets '(a b c) #'print)&lt;br /&gt;&lt;br /&gt;(A B C)&lt;br /&gt;(A B)&lt;br /&gt;(A C)&lt;br /&gt;(A)&lt;br /&gt;(B C)&lt;br /&gt;(B)&lt;br /&gt;(C)&lt;br /&gt;NIL&lt;br /&gt;&lt;br /&gt;&gt; (for-all-subsets-size '(a b c d) 2 #'print)&lt;br /&gt;&lt;br /&gt;(A B)&lt;br /&gt;(A C)&lt;br /&gt;(A D)&lt;br /&gt;(B C)&lt;br /&gt;(B D)&lt;br /&gt;(C D)&lt;br /&gt;&lt;br /&gt;&gt; (for-all-cuts '(a b c d) (lambda (a b) (format t "~a ~a~%" a b)))&lt;br /&gt;&lt;br /&gt;A (B C D)&lt;br /&gt;B (A C D)&lt;br /&gt;C (A B D)&lt;br /&gt;D (A B C)&lt;br /&gt;&lt;br /&gt;&gt; (for-all-max-subsets&lt;br /&gt;   '(2 10 2 5 4)&lt;br /&gt;   (lambda (x) (if (&gt; (apply #'+ x) 10) nil (progn (print x) t))))&lt;br /&gt;&lt;br /&gt;(2 2 5)&lt;br /&gt;(2 2 4)&lt;br /&gt;(10)&lt;br /&gt;(5 4)&lt;br /&gt;&lt;br /&gt;&gt; (for-all-cyclic-permutations '(a b c d e) #'print)&lt;br /&gt;&lt;br /&gt;(A B C D E) &lt;br /&gt;(B C D E A) &lt;br /&gt;(C D E A B) &lt;br /&gt;(D E A B C) &lt;br /&gt;(E A B C D) &lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-1306487819623707747?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/1306487819623707747/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=1306487819623707747' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/1306487819623707747'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/1306487819623707747'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2009/03/common-lisp.html' title='Рекурсивные функции на Common Lisp'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-8528436185071805571</id><published>2009-01-31T23:59:00.002+03:00</published><updated>2009-02-01T00:35:21.691+03:00</updated><title type='text'>[Oracle] Построчное хранение данных</title><content type='html'>Минимальная единица информации в хранилище Oracle называется &lt;a href="http://download.oracle.com/docs/cd/B28359_01/server.111/b28318/logical.htm#i4894"&gt;блок&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Таблицы хранятся в блоках построчно. Чем меньше суммарный объём полей в записях, тем больше записей помещается в одном блоке.&lt;br /&gt;&lt;br /&gt;При чтении какой-либо записи Oracle берёт с диска (или из кеша) весь блок, в котором находится эта запись.&lt;br /&gt;&lt;br /&gt;При необходимости перебрать, например, все значения одного поля таблицы, надо прочесть все соответствующие блоки. Предположим, что таблица большая и не помещается в кеш. Тогда, при наличии в таблице «посторонних» неиспользуемых в этом запросе полей, Oracle будет считывать большее количество блоков для получения того же результата.&lt;br /&gt;&lt;br /&gt;Пример на Oracle 11:&lt;br /&gt;&lt;pre&gt;create table a (n number, s varchar2(4000));&lt;br /&gt;create table b (n number);&lt;br /&gt;begin&lt;br /&gt;  for i in 1..1000000 loop&lt;br /&gt;    insert into a values(i, lpad('a', 4000, 'a'));&lt;br /&gt;    insert into b values(i);&lt;br /&gt;  end loop;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;set timing on;&lt;br /&gt;&lt;br /&gt;select sum(n) from a;&lt;br /&gt;&lt;br /&gt;    SUM(N)&lt;br /&gt;----------&lt;br /&gt;5.0000E+11&lt;br /&gt;&lt;br /&gt;Elapsed: 00:02:04.51&lt;br /&gt;&lt;br /&gt;select sum(n) from b;&lt;br /&gt;&lt;br /&gt;    SUM(N)&lt;br /&gt;----------&lt;br /&gt;5.0000E+11&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:00.11&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Хранения «по колонкам» в Oracle не предусмотрено. Для лучшего быстродействия следует избегать объединения в одной таблице полей, которые могут быть востребованы по отдельности.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-8528436185071805571?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/8528436185071805571/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=8528436185071805571' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/8528436185071805571'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/8528436185071805571'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2009/01/oracle.html' title='[Oracle] Построчное хранение данных'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-3449651014406853215</id><published>2008-12-07T07:20:00.004+03:00</published><updated>2008-12-07T11:30:43.284+03:00</updated><title type='text'>practical-common-lisp.{fb2, pdf}</title><content type='html'>Перегнал &lt;a href="http://gigamonkeys.com/book"&gt;Practical Common Lisp&lt;/a&gt; в формат &lt;a href="http://www.fictionbook.org/index.php/FictionBook"&gt;fb2&lt;/a&gt;, не без помощи OpenOffice с &lt;a href="http://code.google.com/p/ooofbtools/"&gt;ooofbtools&lt;/a&gt;, а также ручной правки.&lt;br /&gt;Сноски, таблицы, иллюстрации, цитаты и код оформлены правильными fb2-тегами. Книга будет нормально выглядеть на &lt;a href="http://www.lbook.com.ua/ru"&gt;LBook&lt;/a&gt; и прочих карманных девайсах.&lt;br /&gt;Скачать можно &lt;a href="http://sites.google.com/site/vicksnosedrops/Home/practical-common-lisp.fb2?attredirects=0"&gt;здесь&lt;/a&gt;.&lt;br /&gt;Алсо, pdf-файл с книгой раньше находился на сайте издательства Apress, но почему-то пропал. &lt;a href="http://ifolder.ru/9424677"&gt;Здесь&lt;/a&gt; лежит копия.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-3449651014406853215?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/3449651014406853215/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=3449651014406853215' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/3449651014406853215'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/3449651014406853215'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2008/12/practical-common-lispfb2-pdf.html' title='practical-common-lisp.{fb2, pdf}'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-2289543725623456240</id><published>2008-11-25T02:45:00.020+03:00</published><updated>2009-03-05T17:53:09.962+03:00</updated><title type='text'>Реверсивный поиск и перебор поддеревьев графа</title><content type='html'>Большинство комбинаторных и оптимизационных проблем содержат в себе задачу обхода вершин некоего связного графа. Граф, как правило, имеет большой размер и не задан явно, иначе перебор его вершин не составил бы труда. Известна лишь какая-то начальная вершина; также для каждой полученной в процессе перебора вершины можно узнать список смежных с ней вершин. Задача проста: посетить все вершины графа по одному разу.&lt;br /&gt;&lt;br /&gt;Существуют два классических алгоритма для решения этой задачи: поиск в глубину (&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;depth&lt;/span&gt;-&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_1"&gt;first&lt;/span&gt; &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;search&lt;/span&gt;) и поиск в ширину (&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_3"&gt;breadth&lt;/span&gt;-&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_4"&gt;first&lt;/span&gt; &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_5"&gt;search&lt;/span&gt;). Оба алгоритма требуют, кроме памяти для хранения «фронта» (стека или очереди) вершин, которые будут посещены, дополнительной памяти для запоминания тех вершин, которые уже посещены.&lt;br /&gt;&lt;br /&gt;При некотором условии можно написать рекурсивный алгоритм обхода, не требующий запоминания посещённых вершин. Алгоритм был предложен в 1996 году и называется «реверсивный поиск» (&lt;a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.26.4487"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_6"&gt;reverse&lt;/span&gt; &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_7"&gt;search&lt;/span&gt;&lt;/a&gt;) [1]. Для его работы требуется, чтобы для каждой вершины &lt;i&gt;v&lt;/i&gt; графа была определена соседняя вершина &lt;i&gt;f(v)&lt;/i&gt;, имеющая максимальный «приоритет». &lt;i&gt;f&lt;/i&gt; должна быть задана так, чтобы любой путь вида &lt;i&gt;v → f(v) → f(f(v)) → ...&lt;/i&gt; заканчивался, и обязательно в одной и той же вершине &lt;i&gt;v&lt;sup&gt;*&lt;/sup&gt;&lt;/i&gt;. Для определённости положим f(v&lt;sup&gt;*&lt;/sup&gt;)=v&lt;sup&gt;*&lt;/sup&gt;.&lt;br /&gt;&lt;br /&gt;Простейший пример: пронумеруем вершины графа натуральными числами (&lt;i&gt;p(v)&lt;/i&gt;) и определим &lt;i&gt;f(v)&lt;/i&gt; как вершину, имеющую максимальный номер среди множества &lt;i&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_8"&gt;nei&lt;/span&gt;(v)&lt;/i&gt; всех соседей &lt;i&gt;v&lt;/i&gt;. Конечно, не любая нумерация будет соответствовать критерию. На множестве &lt;i&gt;V&lt;/i&gt; вершин графа функция &lt;i&gt;p&lt;/i&gt; должна иметь единственный локальный максимум, он же и глобальный.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_BaonBoyqin8/SStSrNf7giI/AAAAAAAAAB0/s12pgHC9uMY/s1600-h/rs1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 271px; height: 90px;" src="http://1.bp.blogspot.com/_BaonBoyqin8/SStSrNf7giI/AAAAAAAAAB0/s12pgHC9uMY/s400/rs1.png" alt="" id="BLOGGER_PHOTO_ID_5272398691078734370" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Рассмотрим граф &lt;i&gt;T&lt;/i&gt;, множество вершин которого совпадает с &lt;i&gt;V&lt;/i&gt;, а ребро между &lt;i&gt;u&lt;/i&gt; и &lt;i&gt;w&lt;/i&gt; есть, когда &lt;i&gt;u=f(w)&lt;/i&gt; или &lt;i&gt;w=f(u)&lt;/i&gt;. Несложно показать, что этот граф будет деревом. На рисунке рёбра дерева обозначены стрелками, ведущими от &lt;i&gt;v&lt;/i&gt; к &lt;i&gt;f(v)&lt;/i&gt;, по возрастанию номера вершины.&lt;br /&gt;&lt;br /&gt;Собственно алгоритм реверсивного поиска очень прост:&lt;ol&gt;&lt;li&gt;Выбрать произвольную вершину &lt;i&gt;v&lt;/i&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Если &lt;i&gt;v≠f(v)&lt;/i&gt;, то присвоить &lt;i&gt;v←f(v)&lt;/i&gt; и перейти к шагу 1&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Положить на стек вершину &lt;i&gt;v&lt;/i&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Пока стек не пуст:&lt;ol&gt;&lt;li&gt;Снять со стека вершину &lt;i&gt;u&lt;/i&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;«Посетить» &lt;i&gt;u&lt;/i&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Для каждой вершины &lt;i&gt;w ∈ &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_9"&gt;nei&lt;/span&gt;(u)&lt;/i&gt;:&lt;ul&gt;&lt;li&gt;Если &lt;i&gt;f(w)=u&lt;/i&gt;, то положить &lt;i&gt;w&lt;/i&gt; на стек&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ol&gt;Шаги 1 и 2 предназначены для обнаружения корня дерева, а шаги 3 и 4 обходят это дерево в глубину. Обойдя все вершины дерева, процедура обойдёт все вершины исходного графа. Обход дерева делается в направлении, противоположном возрастанию функции p, отсюда название алгоритма.&lt;br /&gt;&lt;br /&gt;После этого примера может создаться впечатление, что алгоритм вообще не имеет смысла, т.к. все заботы, связанные с отбрасыванием уже посещённых вершин, переложены на функцию &lt;i&gt;f&lt;/i&gt;, и посчитать её не проще, чем обойти все вершины графа. Это не так: на многих графах, заданных неявно с использованием терминов какой-либо предметной области, функция &lt;i&gt;f&lt;/i&gt; тривиально формулируется в тех же терминах. Авторы в своей статье приводят алгоритмы для перебора:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_10"&gt;триангуляций&lt;/span&gt; точек на плоскости&lt;/li&gt;&lt;li&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_11"&gt;остовных&lt;/span&gt; деревьев графа&lt;/li&gt;&lt;li&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_12"&gt;остовных&lt;/span&gt; деревьев графа на плоскости с &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_13"&gt;непересекающимися&lt;/span&gt; рёбрами&lt;br /&gt;&lt;/li&gt;&lt;li&gt;связных индуцированных &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_14"&gt;подграфов&lt;/span&gt; графа&lt;/li&gt;&lt;li&gt;вершин n-мерного выпуклого многогранника, заданного гиперплоскостями граней&lt;/li&gt;&lt;li&gt;и других объектов&lt;/li&gt;&lt;/ul&gt;(Кроме того, рассматривается модификация алгоритма, допускающая наличие нескольких «локальных максимумов» в графе поиска.)&lt;br /&gt;&lt;br /&gt;Мы покажем алгоритм перебора всех &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_15"&gt;поддеревьев&lt;/span&gt; произвольного графа &lt;i&gt;G&lt;/i&gt;, основанный на реверсивном поиске. Множество вершин &lt;i&gt;V&lt;/i&gt; графа поиска соответствует множеству &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_16"&gt;поддеревьев&lt;/span&gt; &lt;i&gt;G&lt;/i&gt;. Мы будем считать, что между вершинами &lt;i&gt;u&lt;/i&gt; и &lt;i&gt;w&lt;/i&gt; есть ребро, если &lt;i&gt;u&lt;/i&gt; получается из &lt;i&gt;w&lt;/i&gt; добавлением или удалением одного ребра из графа &lt;i&gt;G&lt;/i&gt;. Допустим, что рёбра графа &lt;i&gt;G&lt;/i&gt; как-то упорядочены. &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_17"&gt;Будем&lt;/span&gt; считать, что &lt;i&gt;u=f(w)&lt;/i&gt;, если &lt;i&gt;u&lt;/i&gt; получается из &lt;i&gt;w&lt;/i&gt; удалением &lt;i&gt;максимального&lt;/i&gt; ребра (не считая тех рёбер, после удаления которых получается два дерева вместо одного). Нетрудно понять, что вершина &lt;i&gt;v&lt;sup&gt;*&lt;/sup&gt;&lt;/i&gt; в таком случае будет соответствовать пустому &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_18"&gt;подграфу&lt;/span&gt; &lt;i&gt;G&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;Небольшой пример: построим граф поиска всех &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_19"&gt;поддеревьев&lt;/span&gt; графа из трёх вершин:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_BaonBoyqin8/SStn12msyBI/AAAAAAAAACE/I2QInuAkMHE/s1600-h/rs2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 91px; height: 83px;" src="http://1.bp.blogspot.com/_BaonBoyqin8/SStn12msyBI/AAAAAAAAACE/I2QInuAkMHE/s400/rs2.png" alt="" id="BLOGGER_PHOTO_ID_5272421963655858194" border="0" /&gt;&lt;/a&gt;Каждый узел графа поиска соответствует одному из &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_20"&gt;поддеревьев&lt;/span&gt; нашего графа; пустое поддерево не является исключением.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_BaonBoyqin8/Sa_nAZQHN6I/AAAAAAAAACU/dhbumbPmIc8/s1600-h/rs3a.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 329px; height: 327px;" src="http://2.bp.blogspot.com/_BaonBoyqin8/Sa_nAZQHN6I/AAAAAAAAACU/dhbumbPmIc8/s400/rs3a.png" alt="" id="BLOGGER_PHOTO_ID_5309716479657588642" border="0" /&gt;&lt;/a&gt;Направления стрелок соответствуют удалению рёбер с максимальным номером в исходном графе.&lt;br /&gt;&lt;br /&gt;Шаги 1 и 2 алгоритма перебора &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_21"&gt;поддеревьев&lt;/span&gt; соответствуют шагам 3 и 4 общего алгоритма реверсивного поиска, т.к. известно, что начальное поддерево — пустое и искать его не надо. Кроме того, держать &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_22"&gt;поддеревья&lt;/span&gt; на стеке не требуется; можно вместо поддерева класть на стек рёбра исходного графа. Каждое ребро &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_23"&gt;кладётся&lt;/span&gt; на стек дважды: первый раз для добавления в поддерево, второй раз для удаления из него.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Для каждого ребра &lt;i&gt;e&lt;/i&gt; графа &lt;i&gt;G&lt;/i&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Положить &lt;i&gt;e&lt;/i&gt; на стек&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;S&lt;/i&gt; ← пустой граф&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Пока стек не пуст:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Снять ребро &lt;i&gt;j&lt;/i&gt; со стека&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Если &lt;i&gt;j ∈ S&lt;/i&gt;:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Удалить &lt;i&gt;j&lt;/i&gt; из &lt;i&gt;S&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Иначе:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Добавить &lt;i&gt;j&lt;/i&gt; в &lt;i&gt;S&lt;/i&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;«Посетить» &lt;i&gt;S&lt;/i&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Положить &lt;i&gt;j&lt;/i&gt; на стек&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Для каждого ребра &lt;i&gt;h&lt;/i&gt; графа &lt;i&gt;G&lt;/i&gt;, соседнего с одним из рёбер &lt;i&gt;S&lt;/i&gt; и не принадлежащего &lt;i&gt;S&lt;/i&gt;:&lt;br /&gt; &lt;ul&gt;&lt;li&gt;&lt;i&gt;S’&lt;/i&gt; ← &lt;i&gt;S + h&lt;/i&gt;&lt;br /&gt; &lt;/li&gt;&lt;li&gt;Если &lt;i&gt;S’&lt;/i&gt; — дерево и если &lt;i&gt;h&lt;/i&gt; — максимальное ребро &lt;i&gt;S’&lt;/i&gt;, содержащее вершину степени 1, то положить &lt;i&gt;h&lt;/i&gt; на стек&lt;br /&gt; &lt;/li&gt;&lt;/ul&gt;      &lt;/li&gt;&lt;/ol&gt;    &lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ol&gt;Поддерево &lt;i&gt;S’&lt;/i&gt; получается добавлением ребра &lt;i&gt;h&lt;/i&gt; к &lt;i&gt;S&lt;/i&gt;. «Соседи» &lt;i&gt;S’&lt;/i&gt; в графе поиска — это &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_24"&gt;поддеревья&lt;/span&gt;, полученные удалением одного ребра из &lt;i&gt;S’&lt;/i&gt;. Поскольку соседи также должны являться деревьями, то из &lt;i&gt;S’&lt;/i&gt; можно удалять только «висячие» рёбра, т.е. такие, которые имеют вершину со степенью 1. &lt;i&gt;S=f(S’)&lt;/i&gt;, если &lt;i&gt;h&lt;/i&gt; является максимальным из всех таких рёбер. Можно сказать, что &lt;i&gt;h&lt;/i&gt; должно быть максимальным ребром из всех, которые &lt;i&gt;могли&lt;/i&gt; быть добавлены в &lt;i&gt;S'&lt;/i&gt; на последнем шаге. В этом и только в этом случае мы переходим к поддереву &lt;i&gt;S'&lt;/i&gt;. Это даёт гарантию, что ни одно поддерево не будет посещено дважды. При обходе в ширину или в глубину избежать повторений так просто не получилось бы.&lt;br /&gt;&lt;br /&gt;[1] David Avis, Komei Fukuda. Reverse search for enumeration. Discrete applied mathematics 65, pp. 21-46, 1996.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-2289543725623456240?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/2289543725623456240/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=2289543725623456240' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/2289543725623456240'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/2289543725623456240'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2008/11/blog-post.html' title='Реверсивный поиск и перебор поддеревьев графа'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_BaonBoyqin8/SStSrNf7giI/AAAAAAAAAB0/s12pgHC9uMY/s72-c/rs1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9184507877749766768.post-4025339322352443712</id><published>2008-09-07T22:06:00.001+04:00</published><updated>2009-03-05T17:58:27.391+03:00</updated><title type='text'>Канонический код дерева</title><content type='html'>&lt;span style="font-size:100%;"&gt;Канонический код графа (graph canonical code) — это уникальная строка, которая не зависит от порядка нумерации вершин. У изоморфных графов одинаковые канонические коды, у неизоморфных — разные.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Вершины и рёбра графа могут быть снабжены дополнительными метками (цветами). В этом случае канонический код включает в себя цветовые идентификаторы.&lt;br /&gt;&lt;br /&gt;Канонические коды графов позволяют избавиться от дорогостоящей процедуры проверки изоморфизма (graph isomorphism), и определять совпадение графов по сравнению строк.&lt;br /&gt;&lt;br /&gt;Например, как найти дубликаты в множестве N графов? Вычислить N канонических кодов, отсортировать их и последовательно сравнить. Гораздо выгоднее, чем делать N*(N-1)/2 проверок изоморфизма. Можно сказать, что канонический код — это хеш-код без промахов.&lt;br /&gt;&lt;br /&gt;Канонический код может выглядеть по-разному: как матрица смежности, развёрнутая в строку, или как маршрут обхода графа в ширину, или в глубину. Какое представление выбрать, не столь важно. Задача в любом случае сводится к вычислению канонической нумерации вершин (canonical numbering, canonical labeling). Каноническая нумерация — это нумерация (перестановка) вершин графа, гарантирующая, что изоморфные графы будут пронумерованы одинаково. Получив нужную перестановку, уже не составит труда построить канонический код, например, по обходу графа в глубину: начать обход с вершины под номером 1 и при ветвлении выбирать ту вершину, «канонический номер» которой меньше.&lt;br /&gt;&lt;br /&gt;Наличие полиномиального алгоритма для задачи канонической нумерации означало бы, что задача изоморфизма графов полиномиально разрешима. На данный момент, для задачи изоморфизма графов полиномиального алгоритма не найдено, но и не доказана NP-полнота (в отличие от известной задачи изоморфизма подграфу, subgraph isomorphism). По некоторым признакам &lt;a href="http://www2.informatik.hu-berlin.de/Forschung_Lehre/algorithmenII/Buecher/GI/introduction.ps.gz"&gt;можно предполагать&lt;/a&gt;, что она не является NP-полной.&lt;br /&gt;&lt;br /&gt;В &lt;a href="http://www.psy.omsu.omskreg.ru/session/isomorphism/"&gt;ряде частных случаев&lt;/a&gt; существуют полиномиальные алгоритмы проверки изоморфизма и канонической нумерации. Мы рассмотрим простейший случай: когда граф является деревом.&lt;br /&gt;&lt;br /&gt;В книге Ахо, Хопкрофта и Ульмана «&lt;a href="http://ebdb.ru/Search.aspx?p=1&amp;amp;s=%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%B8%D0%B5+%D0%B8+%D0%B0%D0%BD%D0%B0%D0%BB%D0%B8%D0%B7+%D0%B2%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D1%85+%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%BE%D0%B2&amp;amp;x=0&amp;amp;y=0"&gt;Построение и анализ вычислительных алгоритмов&lt;/a&gt;» приводится алгоритм для определения изоморфизма двух деревьев (tree isomorphism), работающий за линейное время от количества вершин. На его основе нетрудно составить алгоритм канонической нумерации вершин дерева, работающий за то же линейное время.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Этап 1. &lt;/span&gt;&lt;span style="font-weight: bold;"&gt;Выделение корня&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Отрубим от дерева все листья. Получится дерево меньшего размера. Будем повторять процедуру до тех пор, пока не останется одна вершина (center) или две вершины, соединённых ребром (bicenter).&lt;br /&gt;&lt;br /&gt;Если осталась одна вершина, мы делаем её корнем и располагаем остальные вершины по уровням, считая от корня:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_BaonBoyqin8/SMT3VN3HVnI/AAAAAAAAAAU/o1uy1kpysxw/s1600-h/tree1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_BaonBoyqin8/SMT3VN3HVnI/AAAAAAAAAAU/o1uy1kpysxw/s400/tree1.png" alt="" id="BLOGGER_PHOTO_ID_5243587810036242034" border="0" /&gt;&lt;/a&gt;Если осталось ребро, мы располагаем остальные вершины по уровням, считая от этого ребра, обе вершины которого помещаются на первый уровень:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_BaonBoyqin8/SMT4K3UIxrI/AAAAAAAAAAc/1J6PrH8bR7s/s1600-h/tree2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_BaonBoyqin8/SMT4K3UIxrI/AAAAAAAAAAc/1J6PrH8bR7s/s400/tree2.png" alt="" id="BLOGGER_PHOTO_ID_5243588731696891570" border="0" /&gt;&lt;/a&gt;Мы разделили N вершин на K уровней так, что у каждой вершины на уровне k&gt;1 есть «родитель» из уровня k-1. Вспомним ещё, что вершины и рёбра снабжены цветами. В наших обозначениях вершины будут красные и синие, а рёбра — чёрные и зелёные.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_BaonBoyqin8/SMT_AvEuYTI/AAAAAAAAAAk/3uGWuANrv58/s1600-h/tree3.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_BaonBoyqin8/SMT_AvEuYTI/AAAAAAAAAAk/3uGWuANrv58/s400/tree3.png" alt="" id="BLOGGER_PHOTO_ID_5243596254267466034" border="0" /&gt;&lt;/a&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;Этап 2.&lt;/span&gt;&lt;span style="font-weight: bold;"&gt; &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;Поуровневая&lt;/span&gt; сортировка&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Для алгоритма нужно задать условный порядок на цветах. Пусть красный цвет будет старше синего, а зелёный старше чёрного. Введём обобщённый код вершины, состоящий из цветового кода ребра, которое соединяет её с родителем, и из цветового кода самой вершины. Сгруппируем вершины нижнего уровня, имеющие одинаковый обобщённый код, и присвоим каждой группе ранг, соответствующий старшинству цветов. Цвет ребра имеет больший приоритет, чем цвет вершины. Ранг 0 считается самым старшим:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_BaonBoyqin8/SMUOc2ZZkiI/AAAAAAAAAAs/PBtzjJe_ig0/s1600-h/tree4.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_BaonBoyqin8/SMUOc2ZZkiI/AAAAAAAAAAs/PBtzjJe_ig0/s400/tree4.png" alt="" id="BLOGGER_PHOTO_ID_5243613229943984674" border="0" /&gt;&lt;/a&gt;Составим обобщённые коды родителей этих вершин, которые расположены на предыдущем уровне. Кроме цветовых кодов ребра к родителю и самой вершины, включим в обобщённый код отсортированный список рангов её детей. Ранги детей имеют меньший приоритет, чем цветовые коды вершины и ребра:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_BaonBoyqin8/SMUQxG5gFBI/AAAAAAAAAA0/OMWBKQflNHs/s1600-h/tree5.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_BaonBoyqin8/SMUQxG5gFBI/AAAAAAAAAA0/OMWBKQflNHs/s400/tree5.png" alt="" id="BLOGGER_PHOTO_ID_5243615776994235410" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Повторяем описанную процедуру:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_BaonBoyqin8/SMUWitLy5nI/AAAAAAAAAA8/wPjURUo-Bl0/s1600-h/tree6.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_BaonBoyqin8/SMUWitLy5nI/AAAAAAAAAA8/wPjURUo-Bl0/s400/tree6.png" alt="" id="BLOGGER_PHOTO_ID_5243622126643242610" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;и ещё раз:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_BaonBoyqin8/SMUXm7mv3sI/AAAAAAAAABE/0wNdqveHjyw/s1600-h/tree7.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_BaonBoyqin8/SMUXm7mv3sI/AAAAAAAAABE/0wNdqveHjyw/s400/tree7.png" alt="" id="BLOGGER_PHOTO_ID_5243623298745491138" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Пока не дойдём до уровня 1. В нашем случае на первом уровне одна вершина, поэтому её ранг нулевой, в случае двух вершин на первом уровне нужно сравнить их обобщённые коды и присвоить им ранги 0 и 1 (или 0 и 0).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Этап 3.&lt;/span&gt;&lt;span style="font-weight: bold;"&gt; Нумерация&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Начиная с уровня 1, &lt;span class="blsp-spelling-corrected" id="SPELLING_ERROR_1"&gt;присваиваем&lt;/span&gt; вершинам возрастающие номера в порядке возрастания их рангов. В случае совпадения рангов на уровне порядок присваивания номеров может быть любым, т.к. вершины с одинаковым рангом в некотором смысле идентичны. (Выражаясь математически, эти вершины лежат на одной &lt;a href="http://mathworld.wolfram.com/GroupOrbit.html"&gt;орбите&lt;/a&gt;, т.е. существует &lt;a href="http://mathworld.wolfram.com/GraphAutomorphism.html"&gt;автоморфизм&lt;/a&gt; графа, переводящий одну вершину в другую.)&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_BaonBoyqin8/SMUc0Z5h8eI/AAAAAAAAABM/romZI3_e-Bo/s1600-h/tree8.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_BaonBoyqin8/SMUc0Z5h8eI/AAAAAAAAABM/romZI3_e-Bo/s400/tree8.png" alt="" id="BLOGGER_PHOTO_ID_5243629027773772258" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Построение канонического кода&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Как было сказано, строить канонический код можно многими способами. Приведём пример канонического кода, построенного при обходе дерева в глубину:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_BaonBoyqin8/SMUq-oawL9I/AAAAAAAAABk/m4erWuDXyP8/s1600-h/tree9.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_BaonBoyqin8/SMUq-oawL9I/AAAAAAAAABk/m4erWuDXyP8/s400/tree9.png" alt="" id="BLOGGER_PHOTO_ID_5243644596632694738" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Для построения такого кода не нужен даже третий этап предыдущего алгоритма (нумерация). Мы начинаем обход с корня и для каждой посещённой вершины обходим её детей в порядке старшинства. Для определения старшинства детей хватит «&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;внутриуровневых&lt;/span&gt;» кодов, полученных на этапе 2.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Анализ сложности&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Этап 1, очевидно, требует O(N) операций. Нетрудно также понять, что на этапе 2 мы, двигаясь от последнего уровня к первому, проходим все вершины по одному разу. Затруднения может вызвать только процесс «ранжирования» вершин на уровне, но при использовании &lt;a href="http://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%80%D0%B0%D0%B7%D1%80%D1%8F%D0%B4%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0"&gt;поразрядной сортировки&lt;/a&gt; он занимает время, пропорциональное количеству вершин, и суммарно по уровням получается тоже O(N) операций. Этап 3 и построение самого кода также занимают линейное время.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Каноническая нумерация в общем случае&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Австралийский профессор &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_3"&gt;Brendan&lt;/span&gt; &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_4"&gt;McKay&lt;/span&gt; написал библиотеку на C под названием &lt;a href="http://cs.anu.edu.au/%7Ebdm/nauty/"&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_5"&gt;nauty&lt;/span&gt;&lt;/a&gt;. Она предназначена для поиска групп автоморфизмов произвольных графов; одним из её «побочных» результатов является каноническая нумерация вершин.&lt;br /&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_6"&gt;Nauty&lt;/span&gt; работает очень быстро. Цвета рёбер она не поддерживает, но при необходимости всегда можно добавить в граф &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_7"&gt;псевдо&lt;/span&gt;-вершины таким образом, что каноническая нумерация получившегося графа без учёта цветов рёбер будет являться канонической для исходного графа.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9184507877749766768-4025339322352443712?l=shmat-razum.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://shmat-razum.blogspot.com/feeds/4025339322352443712/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9184507877749766768&amp;postID=4025339322352443712' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/4025339322352443712'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9184507877749766768/posts/default/4025339322352443712'/><link rel='alternate' type='text/html' href='http://shmat-razum.blogspot.com/2008/09/blog-post.html' title='Канонический код дерева'/><author><name>Dmitry</name><uri>http://www.blogger.com/profile/09876968556571551632</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_BaonBoyqin8/SMT3VN3HVnI/AAAAAAAAAAU/o1uy1kpysxw/s72-c/tree1.png' height='72' width='72'/><thr:total>0</thr:total></entry></feed>
