Выпуск 9. Ноябрь 2013

Обзор CPAN за октябрь 2013 г. | Содержание | Как я решал Perl Golf от PragmaticPerl

Интервью с Marc Lehmann. Часть 1

Marc Lehmann — автор AnyEvent, Coro, common::sense, JSON::XS и многих других популярных модулей на CPAN.

Когда и как начал программировать?

Я начал программировать, когда мне было около девяти лет с какой-то восьмибитной платой, где нужно было вводить инструкции в шестнадцатиричном коде. Мои родители где-то «услышали», что компьютеры «плохо влияют на развитие» и поэтому не хотели покупать мне «настоящий» компьютер, но та плата их не смущала. К счастью, через несколько лет они купили мне C-128, а вскоре после этого Amiga 500, который был идеальным компьютером для знакомства с устройством ОС, низкоуровневым программированием и многим другим. Первые компьютеры позволяли мне понять, как устроено железо и как программировать на низком уровне, но Amiga OS была настолько же сложной, как и ядро современной операционной системы. Когда Amiga стала уже слишком старой и медленной (несмотря на апргейд платы до 68030), я перешел на упрощенную платформу PC+DOS, а в 1993 на HP/UX и затем, почти сразу, на GNU/Linux.

Если говорить про языки, я начал с машинного кода, затем переключился на BASIC и 6502, Modula-2 и m68k, затем на Turbo Pascal + x86 и в конечном итоге на Perl + C на HP/UX и GNU/Linux.

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

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

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

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

Какой редактор используешь?

Исключительно VIM. Я честно пытался выучить Emacs, даже купил книгу “Learning GNU/Emacs”, чего я обычно не делаю, чтобы что-то выучить. До сего дня я четко помню, на какой странице мой мозг бросил исключение и перезагрузился. Если выразиться иначе, то я, возможно, понимаю, что Emacs более крутой редактор, но мой мозг на базовом уровне несовместим с ним, и я остаюсь с VI до конца моих дней.

Поэтому мне кажется, что все холиворы вокруг редакторов совсем не о том: дело в твоем мозге, он либо VI-образный, либо Emacs-образный. Не важно каким бы редактор хорошим не был, возможно вам он вообще не подходит.

Между тем, до VIM я пользовался некоторое время joe — для того, кто пришел из Turbo Pascal/Turbo C, joe удобен тем, что повторяет те же горячие клавиши. Я вспомнил joe, потому что у него до сих пор есть одна исключительная особенность — возможность работать с файлами, которые не помещаются в памяти. Поэтому когда нужно интерактивно отредактировать файл размером 60 ГБ — joe прекрасно с этим справляется.

Когда и как познакомился с Perl?

Это было в 1993 на HP/UX — я искал удобный язык для Unix. Я был потрясен интернетом и свободно доступной информацией, «стандартами» (RFC) и тем фактом, что практически все (конфигурационные файлы, протоколы, такие как FTP) в Unix и в интернете представлено в виде текста.

Perl 4 вышел с хорошей документацией (и бесплатной :), и так я приобщился к нему. Когда перешел на Perl 5 в 1995, я просто перечитал все man-страницы по порядку, сильно впечатлился и до сих пор не могу отойти.

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

С какими другими языками нравится работать?

«Нравится» – довольно растяжимое понятие :)

Я часто объединяю Perl и C++ (если возможно), или С (если нет). Мне очень нравится XS за его мощь и относительную простоту (то, что получается на выходе, не процесс изучения), поэтому я бы описал мой основной язык как «Perl+XS».

И так как я играюсь со многими языками (для развлечения или по работе), я не могу сказать, что они мне действительно нравятся. Я периодически копаюсь с posix shell, разными диалектами ассемблера и javascript и стараюсь не смотреть на php/python/ruby… код ни под каким предлогом, с переменным успехом правда.

Так что по-настоящему мне нравится только Perl.

Что, по-твоему, является наиболее сильным преимуществом Perl?

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

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

Также есть множество «микро-особенностей», которые нельзя счесть за что-то большое, но они действительно таковыми являются. Например, __END__ — не выглядит как что-то выдающееся, однако позволяет элегантно запустить perl-процесс, скажем, через ssh-соединение. AnyEvent::Fork::Remote использует эту особенность, но я пользовался этим и раньше в коммерческих проектах. В других языках, например Python, нет эквивалента __END__, поэтому реализация AnyEvent::Fork::Remote потребует грязных хаков, включая временные файлы и прочее. В Perl решение простое и элегантное, и Perl полон таких время от времени полезных суперштук.

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

Но CPAN, ядро и гибкость, объединенные в один язык Perl, являются все еще уникальной комбинацией.

Что, по-твоему, является самой важной особенностью языков будущего?

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

Если говорить о ближайшем будущем, я надеюсь, что языки позволят просто обмениваться данными между разными процессами или разными нодами. Конечно, я работаю над этим в Perl (мой список дел довольно длинный — libev/EV были в моем списке в течение десяти лет, когда они стали достаточно приоритетными), но мне еще далеко до завершения.

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

Но, если серьезно, было бы действительно хорошо иметь возможность просто объяснить задачу компьютеру (про себя, используя некие абстрактные мысли), и машина сама реализует решение и отладит его…

Ты написал несколько модулей для событийного программирования, которые стали очень популярными. Почему ты решил открыть их, и почему, по-твоему, они стали настолько распространенными?

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

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

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

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

Если взять AnyEvent, у меня был партнер по разработке, который считал этот модуль настолько полезным, что он обязан был быть известным. И поэтому мы пытались написать несколько «необходимых» дополнительных модулей, как например AnyEvent::HTTP. Тяжело сказать, насколько это помогло, я не сильно старался, даже дополнительные модули были написаны, потому что были нужны лично мне.

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

По факту, в AnyEvent и EV можно увидеть процесс эволюции. У AnyEvent интерфейс похож на Event (методы). Позже EV научило меня, что достаточно минимального интерфейса (функции с несколькими фиксированными параметрами), и поэтому я переработал интерфейс AnyEvent в AE.

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

Можно ли коротко описать почему Coro это единственные настоящие треды в Perl?

По-видимому, коротко — нет.

Определяющим свойством тредов в других языках (Python или C) является общее адресное пространство (код и данные). Когда ничего общего нет, это называется «процессами».

«Модель конкурирующих тредов» в Perl, ithreads, использует подобные треды в C для эмуляции Unix-процессов в Perl. На уровне Perl вы получаете (полную ошибок) модель процессов, и разделение переменных или кода между ithread настолько же (не-)эффективно и неестесственно, как и разделение их между настоящими процессами, с тем лишь исключением, что настоящие процессы не должны эмулировать MMU (Memory Management Unit — блок управления памятью, — прим. перев.). Разделение объектов и кода даже не реализовано (объекты, основанные на массивах и хешах, приходят из других тредов пустыми). Разделение уже существующих структур данных вообще невозможно, и так далее.

Поэтому, выражение «единственные настоящие треды в Perl» не было задумано для спора, но как очевидная констатация факта. Однако в течение прошлых лет люди снова и снова давали разные определения тредам и затем оспаривали это выражение.

По существу, все эти споры были вариациями на тему «тред это что-то, что работает параллельно с другими тредами, потому Coro не настоящие треды, а ithreads — да», не учитывая, что: а) придется не признавать тредами pthreads, треды в Python, треды в Ruby и большинство других реализаций, так как почти все они не работают параллельно; и б) процессы также подпадают под это определение тредов, что делает само определение не очень полезным (уже есть термин для процесса — «процесс»).

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

Почему был написан common::sense?

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

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

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

Что там произошло между JSON::XS и сортировкой хешей в Perl?

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

Много лет назад в CGI.pm была ошибка, которая могла привести к исчерпанию ресурсов системы, и по какой-то причине разработчики Perl 5 подумали, что внесение изменений в ядро языка было лучше, чем исправление ошибки в модуле, или введение системы лимитов для таких случаев.

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

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

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

Не было никакого цикла изменений — документация и код были исправлены, и (к счастью) было создано много патчей для важных модулей (не JSON::XS, а, например, LWP), которые бы не работали со следующим выпуском perl.

Мое участие во всем этом выражалось в сомнении насчет необходимости столь резкого изменения. Оно было бы действительно необходимым в случае серьезной уязвимости, но представленные аргументы для меня не были достаточными (несколько других языков решают это «старым» и надежным способом, и не было ни одного примера эксплуатирования этой уязвимости). И до сих пор никто, у кого бы я не спросил, не ответил мне, почему не была решена изначальная проблема (или хотя бы проверена).

Единственный ответ, который я получил (от релиз-менеджера perl) был следующим: «Это изменение решает проблему, и на данный момент у нас нет лучшего патча». Это вполне разумно, но только звучит как постфактум.

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

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

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

Можешь привести несколько примеров, когда App::Staticperl может быть полезным?

Я часто отправляю клиентам скомпилированный с помощью staticperl исполняемый файл вместе с другими Perl-файлами — часто они не знают Perl и не смогут справиться с необходимостью установки большого количества дополнительных модулей. С работающим staticperl создание бинарного файла ненамного сложнее обычного запуска программы, и даже если человек знает, как устанавливать модули, очень удобно иметь возможность сразу запустить программу.

На системах со статической линковкой (не glibc и не windows) можно даже создать исполняемый файл, который будет работать везде без зависимостей — например, я часто рассылаю GNU/Linux-бинарники, который запускаются на x86- и amd64-системах с новым ядром, вне зависимости от libc или других вещей (alpine linux — хорошая оболочка для создания таких файлов).

Иногда клиенты ничего не хотят знать про Perl, и staticperl позволяет мне спрятать perl в исполняемый файл или динамическую библиотеку, чтобы не пугать пользователей. Удивительно насколько быстрым и удобным может быть Perl, когда пользователи не знают, что программа написана на нем, а не на C :)

Также удобно встраивать perl в программу без многих лишних файлов. staticperl для меня создает .h и .c файлы, а затем компилирует и линкует их с libperl. У меня получается полнофунциональный интерпретатор Perl, плюс библиотеки Perl и свои модули, без каких-либо внешних файлов и без каких-либо зависимостей в файловой системе. Все в одной C-программе.

Это актуально на практике еще и тогда, когда я могу встроить perl в тех случаях, когда бы встраивал lua.

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

К сожалению, все особенности staticperl достаются огромной ценой. Несколько кривых модулей (в основном Module::Build) делают использование staticperl не таким простым, а доступным только экспертам, которые понимают, как компилируется Perl, в чем разница между статической линковкой и статическим исполняемым файлом. У большинства людей нет желания или необходимости знать это. Я иногда думаю, что я единственный, который регулярно встраивает Perl (для меня это естественно, но очевидно, что не для всех).

Если вы все же продвинутый пользователь, создание независимых исполняемых файлов со staticperl становится развлечением. Это гораздо быстрее и легче (для меня), чем использование, например, PAR::Packer. Также у staticperl-файлов больше шансов фактически заработать, судя по моему (возможно, однобокому) мнению. Если бы PAR::Packer работал надежно, не было бы причины писать App::Staticperl или Urlader.

(В качестве рекламы, Urlader + Perl::LibExtractor это еще один способ запаковать perl, чем я и пользуюсь на Windows. Это все также быстрее и проще, чем PAR::Packer, и работает с любой программой, не только Perl. Да и, для меня, концептуально проще).

Ты довольно сильно оптимизируешь свои программы. Это обычно необходимость или просто привычка?

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

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

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

Посмотрите на это под другим углом: можно написать приложение на C, можно написать на Perl. При прочих равных на С оно будет быстрее, но довольно сложным в написании. Поэтому, возможно, стоит написать его на Perl — что будет удобнее, и так как Perl уже достаточно оптимизирован на уровне C, приложение будет достаточно быстрым.

Поэтому основным принципом в работе должен быть «используй наиболее удобный язык, который достаточно быстрый», и поэтому сильно оптимизированные C-библиотеки в Perl делают его выбор наиболее частым из-за удобства.

Если соединить «используй подходящий для задачи язык» и Perl+C, то этим обычно покрываются все задачи, потому что Perl хорош в тех вещах, где С отстает, и наоборот, что в результате выливается в лучшее из обоих языков.

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

Однако, у скорости всегда меньший приоритет, чем у корректности.

О том где работает Марк, каковы его мысли насчет будущего Perl, конференций и сообщества, почему он до сих пор использует CVS и другие интересные ответы читайте в следующем номере.

Вячеслав Тихановский


Обзор CPAN за октябрь 2013 г. | Содержание | Как я решал Perl Golf от PragmaticPerl
Нас уже 1393. Больше подписчиков — лучше выпуски!

Комментарии к статье