В 1987 году он стал одним из инициаторов проекта, в результате которого появился язык программирования Haskell. Сегодня Саймон Пейтон-Джонс — ведущий исследователь лаборатории Microsoft Research, находящейся в британском Кембридже. В 1998 году он выпустил переработанное описание языка Haskell, являющееся текущим стабильным описанием. Кроме того, Пейтон-Джонс — ведущий разработчик Glasgow Haskell Compiler (GHC), «стандартного компилятора де-факто», согласно сайту haskell.org. Он же снабдил язык часто приводимым официальным девизом «Избегать успеха любой ценой».

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

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

Сейбел: Когда вы научились программировать?

Пейтон-Джонс: В школе. Intel только что выпустила 4004 — первый в мире микропроцессор. Но у нас ни 4004, ни чего-нибудь похожего не было — любители в то время могли достать его лишь с большим трудом. Был только школьный компьютер IBM — странная машина, собранная из каких-то запчастей. Системы постоянного хранения данных не было, и программу всякий раз приходилось набирать с нуля.

Всего было 100 ячеек памяти, в каждой из которых, кажется, хранились восьмизначные десятичные числа. Там располагались и программа, и ваши данные. Искусство заключалось в том, чтобы уместить программу в эти 100 ячеек. Не помню, как именно я написал свою первую программу; мы с одним парнем все время торчали за этим компьютером. Мне тогда было пятнадцать — 1973 или 1974 год.

Освоив немного эту машину, мы выяснили, что в Суиндонском техническом колледже есть компьютер. Мы стали ездить туда в один из выходных на невероятно медленном автобусе — поездка занимала около часа. То был Elliot 803; он помещался в особой комнате в полудюжине белых шкафов размером с холодильник, и управляла им женщина-оператор в белом халате.

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

Сейбел: В школе ведь не было никаких уроков.

Пейтон-Джонс: Конечно, нет! Совсем ничего, никаких компьютеров не было в программе.

Сейбел: То есть просто — «Эй, ребятки, вот вам компьютер, действуйте».

Пейтон-Джонс: Именно так. Он помещался в большом шкафу, обычно закрытом. Но мы брали ключ, включали его — и вот вам дисплей, показывающий только то, что есть в регистрах, и десятичные номера — содержимое ячеек памяти. Вводишь программу, нажимаешь Go. И можно было пройти ее пошагово. Не было даже языка ассемблера за отсутствием ASCII-символов. В буквальном смысле слова машинный код, отображаемый при помощи десятичных чисел, даже не шестнадцатеричных.

Сейбел: Но экран-то был?

Пейтон-Джонс: Телевизионный экран. Единственное средство вывода информации.

Сейбел: А для ввода?

Пейтон-Джонс: Нечто вроде сенсорной клавиатуры, никаких механических кнопок — довольно сложное устройство. Надо было только коснуться кнопки. Всего их было десятка два.

Сейбел: Только для чисел?

Пейтон-Джонс: Да, и еще кнопки Go и Step. И еще — «показать данную ячейку памяти». Все крайне примитивно, но оттого раззадоривало еще больше.

Сейбел: Раз так, выходит, программу надо было продумывать заранее, в мелочах, до того как вводить ее в машину.

Пейтон-Джонс: Сначала мы рисовали блок-схему, потом разбивали ее на отдельные инструкции, переводили инструкции в этот странный цифровой формат и вводили цифры. Программа состояла обычно из 800 цифр. Потом мы нажимали Go. Если везло, мы не ошибались ни в одной из этих 800 цифр, и все работало. Поэтому у нас уходило много времени: один смотрел на экран и проверял, другой говорил: «Переходи к следующей ячейке».

Затем я поступил в Кембриджский университет; эра микропроцессоров только начиналась. В университете был компьютерный клуб. Там стояла большая машина под названием Phoenix с исключительно замысловатой системой учета ресурсов.

Было очень важно, в какое именно время ты пользовался компьютером. Тебе давали некоторое количество единиц машинной «валюты», и чем больше памяти или времени у тебя уходило, тем больше их расходовалось. Соответственно, чем меньше ресурсов отнимала твоя программа, тем меньше ты тратил. Мы, зеленые студенты, которым этих единиц давали мало, просиживали там ночи напролет — после девяти вечера все стоило дешевле.

И мы с девяти вечера до трех ночи торчали там, сочиняя программы. На чем мы писали? На BCPL. Опять сплошная самодеятельность, как видите. В то время я занимался математикой и формально совсем не учился компьютерным наукам.

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

Но математика оказалась нелегким делом, ведь в Кембридже отличная математическая школа. Так что я переключился на электричество.

Сейбел: «Науки об электричестве» — это то, что в Америке называется «Электротехника»?

Пейтон-Джонс: Совершенно верно. Мой школьный приятель Томас Кларк тоже учился в Кембридже. Мы с ним собирали компьютеры. Покупаешь микропроцессор, много транзисторов серии 7400 и соединяешь их проводами. Большой проблемой оставались принтеры и экраны. С ними было трудно.

Сейбел: Стоили очень дорого.

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

Сейбел: Значит, в 1976-1979 годах вы собирали компьютеры из подручных средств. Но ведь как раз в это время выпустили Altair.

Пейтон-Джонс: Да. Самоделки уже выходили из моды. Но нам просто нравилось все это.

С этими нашими машинами была еще одна сложность — программы. Самое продвинутое, что можно было загрузить, — конвеевская игра «Life» (Жизнь). Она работала прекрасно. Но что-то серьезное, вроде языка программирования, требовало слишком много работы — у нас были крохотные постоянные хранилища данных. Ну и, кроме того, все писалось в шестнадцатеричном коде, никакого ассемблера.

Сейбел: Машинный код в чистом виде.

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

Сейбел: Помните вашу первую интересную программу?

Пейтон-Джонс: Программа для извлечения 24-значных квадратных корней и помещения их в 99 ячеек памяти — это еще для школьного компьютера.

Сейбел: И одна оставалась в запасе!

Пейтон-Джонс: Правильно. Что-то вроде метода Ньютона-Рафсона для квадратных корней. Я страшно гордился этим. Что же было потом? Потом, наверное, тот самый компилятор, который мы забросили, на BCPL. Мы многое с ним связывали и разрабатывали его детально. Системы типизации не было, так что у нас имелись только громадные листы распечаток с картинками, схемами и стрелками.

Сейбел: В BCPL не было системы типизации?

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

Сейбел: Вы извлекли уроки из этой неудачи?

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

Сейбел: Но даже ее оказалось недостаточно?

Пейтон-Джонс: Ну, у нас были и другие заботы — надо было получать степень бакалавра. А компьютерами мы занимались по вечерам и ночью.

Сейбел: Что вам не нравится в том, как вы учились программированию?

Пейтон-Джонс: Никто меня этому не учил. Но не уверен, что это большой пробел. Самый большой пробел — то, что я на глубинном уровне так и не приобрел познаний в объектно-ориентированном программировании. Нет, я знаю, конечно, как писать объектно-ориентированные программы. Но делать это в большом масштабе — дело совсем другое. Если писать большие программы, использовать сложным образом разные иерархии классов, создавать фреймворки — вот тогда приходит глубинное понимание. Это не то же самое, что прочесть книгу.

Я считаю это пробелом, так как не могу авторитетно высказываться насчет того, что можно и чего нельзя делать в объектно-ориентированном программировании. Я всегда очень осторожен в своих высказываниях, особенно стараюсь не говорить отрицательно об императивном программировании — это невероятно сложная и богатая парадигма программирования. Но жизнь сложилась так, что мне ни разу не пришлось в течение нескольких лет писать большие программы на C++. Так приобретаются глубинные познания на уровне рефлексов, и у меня их нет.

Сейбел: Что было после Кембриджа?

Пейтон-Джонс: Я подумал: «Надо бы поработать с компьютерами». И я год занимался послеуниверситетской подготовкой по компьютерным наукам; это мое единственное официальное образование в компьютерной области.

Сейбел: Что-то вроде магистратуры?

Пейтон-Джонс: Что-то вроде магистратуры. То был очень полезный для меня год. Думаю, это примерно соответствовало сегодняшней степени бакалавра по компьютерным наукам, но было рассчитано на студентов, которые начинают с нуля.

Сейбел: Вы несколько лет провели в индустрии, прежде чем вернуться в науку. Чем вы занимались?

Пейтон-Джонс: Это была крошечная компания. Мы производили оборудование и программы для компьютеров, которые устанавливались в приборы измерения веса на ленточных транспортерах. Помню, я спроектировал штуку, наблюдавшую за ячейкой загрузки углепогрузочного транспортера. Она контролировала скорость ленты в зависимости от наполнения ячейки, чтобы соблюдать норму перемещения угля за единицу времени. Небольшая система, но оперировавшая в реальном времени; я использовал для нее PL/Z, слегка похожий на Алгол. Я писал программу на компьютере Z80 с операционной системой Chromix — урезанный UNIX, так сказать.

В компании обычно работало человек шесть, временами больше — до пятнадцати. Из-за ее крошечных размеров все было довольно ненадежно. Денег то было много, то совсем не было. После двух лет я решил, что предпринимательство не для меня. Это был мой главный опыт, если говорить о мелких компаниях; чтобы стать успешным предпринимателем, надо извлекать энергию из стрессовых финансовых ситуаций, а у меня в таких случаях энергия, наоборот, расходуется. Мой начальник был управляющим директором компании. Чем хуже шли дела, тем энергичнее он выглядел. Он бегал вокруг, порождал новые идеи насчет программ, трудился как пчела и был счастлив. Именно так и нужно себя вести, а если стресс высасывает из тебя энергию, то постоянно ходишь как вареный.

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

Джон Уошбрук, старший научный специалист факультета, взял меня под свое крыло и сказал кое-что очень важное. «Начни хоть с чего-нибудь, пусть даже мелкого», — сказал он. Это относилось не к программированию, а к исследованиям. Пусть тема будет мелкой, неоригинальной, маловажной — надо взять и написать статью. Я так и сделал. Совет Джона очень много значил для меня.

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

Сейбел: Итак, вы вернулись в науку, но степень так и не получили. Почему?

Пейтон-Джонс: Сейчас занять должность на факультете без степени было бы крайне трудно. Тогда — это был 1982, 1983 год — я подал заявку в Лондонский университетский колледж. Моя сестра изучала там компьютерные науки и сказала мне: «Там читают лекции по этой теме, почему бы тебе не пойти туда?» И меня, к моему удивлению, приняли. Видимо, тогда преподавателей не хватало, поэтому принимали каждого, кто хоть как-то зарекомендовал себя в этой области. Иначе как бы они наняли человека без степени?

После семи лет преподавания в колледже я начал задумываться о степени. Однако писать диссертацию — это так муторно! Выяснилось, что в Кембридже можно получить степень особым образом — представить опубликованную работу и, если повезет, станешь доктором. Я начал хлопотать об этом, но тут получил место профессора в Глазго. Теперь уже никому не было дела, есть у меня степень или нет, и я оставил эту мысль. У Робина Милнера ее тоже нет, так что я оказался в неплохой компании. На этом все и закончилось.

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

Пейтон-Джонс: Совершенно верно. Это необходимое, хотя и недостаточное условие, если делать исследовательскую карьеру в академических учреждениях или, например, в лабораториях Microsoft Research либо Google, то есть в научных центрах крупных компаний. Без степени вы застрянете на стартовой отметке.

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

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

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

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

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

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

При этом чисто функциональное программирование первоначально было слишком эксцентричным и академичным, слишком тесно связанным с математикой. За последние 20 лет — те, что я с ним работаю, — оно становится все более ориентированным на практику, не обращаясь к абстрактным идеям, а пытаясь убрать одно за другим препятствия, мешающие реальным программистам использовать функциональные языки для создания приложений. Развитие Haskell может служить примером.

Хорошо, что есть люди, может быть, слегка непрактичные, которые идут впереди общей массы. Перспективы чисто функционального мира могут стать для этой общей массы путеводной звездой. Собственно, так и происходит. Многое в системах типизации и обобщенных типах изначально было разработано в контексте языков функционального программирования — они послужили своего рода лабораторией. Еще один пример: генераторы и «ленивые» потоки. В Python есть списковые выражения на синтаксическом уровне. Есть много индивидуальных находок. Если они переходят на текущий уровень программирования, то получают новые названия, слегка видоизменяются. Не хочу показаться великим поставщиком идей, но здесь действительно есть кое-что, нигде до того не реализованное. Так что это сослужило свою службу.

Сейбел: Какова, по-вашему, связь между исследованиями и собственно программированием?

Пейтон-Джонс: Они активно взаимодействуют. Моя область исследований — языки программирования. Для чего, в конце концов, они создаются? Чтобы легче было программировать. Язык — не что иное, как пользовательский интерфейс программы. Поэтому программирование и исследования в области языков тесно между собой связаны. У нас пока плохо получается наблюдать за программистами. Нужно проводить формальное изучение того, как программисты пишут программы, чтобы понимать, что они делают. Это очень затратно, и к тому же деятельность у программистов довольно нечеткая — результат не всегда выходит однозначный.

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

Сейбел: А вы делали опыты хотя бы в небольшом масштабе? Вы работаете на Microsoft, у которой полно денег. Почему не посадить за одну и ту же задачу команду опытных Haskell-программистов и команду опытных С#-программистов? Ведь вам именно это нужно?

Пейтон-Джонс: Да, вы правы. Отчасти это вопрос денег. Но не только: это также вопрос времени и внимания. Для такого эксперимента нужна новая методология. Нужно повернуть по-другому свое сознание. Кроме того, со стороны кажется, будто у нас много денег, но стандартная картина для Microsoft — это одинокий исследователь за своим компьютером. Мы не можем просто так взять и потратить деньги на какой-нибудь проект. Если бы могли, было бы здорово. Ближе к переднему краю стоят лаборатории в Редмонде — там исследуются прототипы продуктов. Новые версии Visual Studio проходят там широкую проверку на потребительские свойства.

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

Пейтон-Джонс: Они также делают кое-что интересное в плане тестирования API. Стивен Кларк и его редмондские коллеги стараются регулярно наблюдать за программистами, получившими новый API, — о чем те говорят, что пытаются сделать. Проектировщики данного API сидят за стеклянной перегородкой и наблюдают.

Иногда эти проектировщики говорят программистам: «Нет-нет, не делайте так! Это неправильно!» Часто такие ситуации бывают очень поучительными. Потом API меняют там, где нужно. Честно говоря, в этом отношении исследования языков не столь продвинулись. Отчасти потому, что там нужно решать более сложные проблемы. Кроме того, в культурном смысле мы еще мало к этому адаптированы. Я считаю это нашей слабостью. И я не чувствую лично себя в состоянии предложить для этого решение.

Сейбел: Если исследователи выдвигают интересные идеи насчет улучшения процесса программирования, достаточно ли быстро они внедряются на практике?

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

Им надо сделать то, что потребитель готов оценить сегодня. У них просто нет времени заморачиваться с тем, что может работать в принципе или даже с тем, что может работать прямо сейчас, но пока еще не совсем доделано.

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

Я не хотел бы утверждать, что компьютерные «практики» зомбированы и не желают внедрять хорошие идеи, которые могут облегчить им жизнь. То, что они делают, имеет под собой основания. Расстояние между прототипами и практически применимыми вещами часто бывает большим. Думаю, Microsoft здесь неплохо справляется. Microsoft Research позволяет сократить это расстояние и располагает кое-какими механизмами — инкубационными группами и так далее, — которые сближают исследователей и разработчиков ПО, позволяют пересечь разделяющую их границу. Поэтому я считаю, что MSR полезен настолько, насколько дает возможность преодолеть эту границу.

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

Если таких камней преткновения довольно много, застреваешь. Мне кажется, различные элементы исследовательских подходов к языку живут в разных точках спектра. Некоторые довольно близки к тому, что уже есть. Вы можете сказать, что эта штука подключается прямо в фреймворк, не требует модификации Java, это инструмент статического анализа, позволяющий найти ошибки в коде. Ура! Такие вещи воспринимаются куда легче, чем «вот вам полностью новый способ программирования».

Если же говорить конкретно о функциональном программировании, то отношение к нему сильно изменилось в последнее время. Гораздо больше народа знают теперь, что это такое. Уже не приходится всякий раз объяснять, что такое Haskell. Некоторые говорят: «Я читал про него недавно на Slashdot, по-моему, это круто». Еще несколько лет назад такого не было.

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

Сейбел: А как вы сами начали заниматься функциональным программированием?

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

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

Сейбел: Думаю, многие после такой лекции сказали бы: «Занятно!» — и вернулись к своему BCPL. Почему же вы пошли дальше, занялись исследованиями, стали объяснять людям, как пользоваться этими программами?

Пейтон-Джонс: Еще сыграли роль статьи Дэвида Тернера о комбинаторах S и К, которые помогают преобразовывать и затем выполнять лямбда-вычисления. О лямбда-вычислениях я знал немного больше — тогда эта идея набирала популярность. Тернер показывал, как преобразовывать лямбда-вычисления в три комбинатора: S, К и I, которые представляют собой закрытые лямбда-термы. Фактически речь шла о том, чтобы преобразовать сколь угодно сложные лямбда-термы в эти три комбинатора. Можно даже обойтись без I, так как он равен SKK.

Довольно странное преобразование — берешь лямбда-терм, который хоть как-то понимаешь, и получаешь набор из S и К, в котором не понимаешь ничего. Но вот применяешь их к аргументу, и случается чудо — ответ выходит тот же, что и с применением изначального терма. Что-то очень умное — для меня по тем временам невероятное. Но это всегда работает!

Я обратился к функциональным программам по вдохновению. Отчасти, думаю, потому, что был связан с «железом», а это выглядело способом реализации лямбда-вычислений. Ведь сперва кажется, что в них вообще нет механизма реализации, что это чистая математика, далекая от компьютеров. A S-K комбинаторы, как мне показалось, позволяют применять их на практике — и так оно и было.

Сейбел: Значит, вы решили, что достаточно заложить эти комбинаторы в машину, а потом уже на их основе выполнять операции?

Пейтон-Джонс: На самом деле именно этим занимались мои друзья. Уильям Стой, Томас Кларк и еще несколько человек создали компьютер SKIM, или SKI Machine, который напрямую выполнял S и К. Я не участвовал в их проекте, но тогда все развивалось в этом направлении. Статья Джона Бэкуса «Can programming be liberated from the von Neumann style» (Может ли программирование освободиться от стиля фон Неймана) была невероятно популярна. Он получил Премию Тьюринга за эту статью, и он — изобретатель Фортрана — фактически заявил: «Будущее за функциональным программированием».

Он заявил и другое: «Возможно, для этих программ придется развивать новую компьютерную архитектуру». Как видите, за функциональное программирование высказывались очень влиятельные люди, и мы как сумасшедшие цитировали статью Бэкуса. SKIM тоже вписывался в этот процесс. Мы полагали, что нестандартная реализация — или, по крайней мере, нестандартный подход к программам — даст нам совершенно новые виды компьютерной архитектуры. Это увлечение — «радикально новая архитектура для функционального программирования» — длилось все 1980-е. Пожалуй, мы пошли немного не той дорогой, но все это было крайне захватывающим.

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

Иногда хорошо дать волю воображению — получаешь совершенно иной подход к программированию. Не добавляешь еще один кирпич к стене, а строишь новую стену. Это так увлекательно! Во всяком случае, именно это подталкивало меня. Может, потому что это было ловким трюком? Но и ловкие трюки, я считаю, имеют свое значение. Ленивые вычисления оказались очень ловким трюком — мы делали то, что вообще не считали возможным.

Сейбел: Например?

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

Сейбел: Со временем.

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

Сейбел: Потом этот вид вычислений был встроен в языки — взять, например, генераторы в Python или любую программу, позволяющую получать значения. Что вы подумаете? Что есть множество вещей, представимых как серии бесконечных вычислений, из которых можно получать результаты, пока не устанешь? Или что это интересный способ решения некоторых проблем, но далеко не основа всего?

Пейтон-Джонс: Думаю, я тогда не забирался так глубоко; это было просто прикольно. И заниматься этим мне нравилось. По-моему, надо лишь определить, что вам нравится, и следовать этим путем. Функциональное программирование вдохновляло меня, но каких-то глубинных причин заняться им не было. Просто мне казалось, что это прекрасный способ создавать программы. Я люблю лыжи — значит, я буду ходить на лыжах. Не потому, что это изменит мир, а потому, что мне нравится.

Сейчас мне кажется, что ленивые вычисления важны, так как позволяют сохранить чистоту. Я уже говорил об этом по другим поводам. Я люблю эти вычисления и, если есть выбор, всегда возьму ленивый язык. По-моему, они полезны в какой угодно области программирования. Вы, конечно же, читали статью Джона Хьюза «Why functional programming matters» (О важности функционального программирования). Это, вероятно, первое открытое выступление на тему того, что ленивые вычисления — не просто игрушка. Главное — они помогают создавать модульные программы.

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

Вот почему ленивые вычисления — хорошая вещь. Они также полезны и на локальных уровнях программы. Так, если Haskell-программист будет писать определение функции вместе с локальными определениями, то сделает это так: «f от x равно тому-то и тому-то там, где...», и после инструкции «там, где» — сколько-то определений, нужных далеко не во всех случаях. Но все равно их следует перечислить. Те, которые нужны, будут вычислены, те, которые не нужны, — не будут. И программист думает: «О черт, все эти подвыражения надо вычислить, но вычислить нельзя, потому что все полетит из-за деления на ноль. Поставлю-ка я определение в правую ветвь условия».

А в нашем случае — ничего подобного. Надо просто написать те вспомогательные определения, которые могут понадобиться, и те, что понадобятся, будут вычислены. Это очень, очень удобный инструмент.

Но вернемся к общим положениям. С ленивым механизмом вычисления сложнее предсказать, когда понадобится вычислить выражение. И если вы хотите вывести что-нибудь на экран, то язык с вызовом по значению, где порядок вычисления явно определен, делает это при помощи «функции» с побочным эффектом — я специально ставлю кавычки, так как это вовсе не функция, — с типом, скажем, string -> unit. При вызове функции она печатает что-то на экране — в виде побочного эффекта. Это есть в Лисп, в ML, в любом языке с вызовом по значению.

А в чистом языке, если есть функция string -> unit, ее никогда не надо вызывать: вы ведь знаете, что она выдаст всего лишь ответ типа unit. Больше она не делает ничего. А каков ответ, вы знаете. Но поскольку функция с побочными эффектами, очень важно вызвать ее. В ленивом языке проблема вот какая: вы говорите «f применяется к print 'привет», а вычисляет ли f свой первый аргумент, для вас неясно. Это все происходит внутри функции. Если же аргументов будет два — print 'привет' и print пока', — она может выполнить один, или оба в любом порядке, или ни одного. Поэтому при ленивых вычислениях делать ввод /вывод при помощи побочных эффектов невозможно. Вы не можете написать таким способом рациональную, надежную, предсказуемую программу. Сначала это было непривычно — нет, собственно, никакого ввода/вывода. И потому долгое время в программах использовалась только функция string -> string. Целая программа делала только это. Строка на входе была вводом, а строка результата — выводом. Вот и все.

Можно было слегка усложнить схему, закодировав в строке вывода команды вывода, которые выполнялись внешним интерпретатором. Строка вывода могла скомандовать: «Вывести вот это на экран, а вон то сохранить на диске». Интерпретатор уже умел это делать. Итак, мы имели замечательную, чисто функциональную программу, а нехороший интерпретатор интерпретировал командную строку. Но если считываешь файл, как вернуть ввод в программу? Просто: сделать строку с командами вывода, которые интерпретируются нехорошим интерпретатором, и произвести ленивое вычисление — результат поступает обратно на вход. Итак, теперь программа преобразует поток ответов в поток запросов. Поток запросов направляется на нехороший интерпретатор, каждый запрос генерирует ответ, который идет затем на вход. Поскольку вычисление ленивое, программа выдает ответ с таким расчетом, чтобы он успел пройти цикл и прийти на вход. Правда, это работало не без сбоев — если ответ поглощался слишком агрессивно, программа зависала, ведь нужен был ответ на вопрос, которого еще не было на выходе.

Смысл в том, что ленивость загнала нас в угол, и пришлось решать вопрос с вводом/выводом. Это была самая важная проблема ленивого программирования. Но начиналось-то все с другого — с того, как это прикольно, как здорово.

Сейбел: За все то время, что вы занимаетесь программированием, как изменились ваши представления о нем как таковом?

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

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

Пейтон-Джонс: Да, я много чего понял с тех пор. То был компилятор для императивного языка на императивном языке. Теперь я пишу компиляторы для функционального языка на функциональном языке. Но в GHC, нашем компиляторе для Haskell, важно то, что в нем применен промежуточный язык с типизацией.

Сейбел: А в этом языке применена типизация исходного языка?

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

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

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

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

Сейбел: Когда сторонники динамической и статической типизации начинают препираться, первые говорят: «Очень много программ, где статическая типизация мешает мне написать то, что я хочу». А вторые отвечают: «Да, такие программы есть, но на практике это не составляет проблемы». Что вы думаете по этому поводу?

Пейтон-Джонс: Это отчасти зависит от привычки. Я, например, говорю, что так и не привык писать на C++. С другой стороны, вы не будете страдать от отсутствия ленивых вычислений, если вообще ими не пользовались, а я буду, потому что пользуюсь ими постоянно. Возможно, динамическая типизация — похожий случай. Мое ощущение — насколько оно может быть ценным с моим специфическим опытом — таково, что крупные программные блоки вполне могут иметь статическую типизацию, особенно в таких богатых системах типизации. И там, где такая типизация возможна, она очень полезна по неоднократно приводившимся причинам.

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

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

Еще одно: статическая типизация для меня отчасти объясняет, что именно делает программа. Это такой не очень развитый язык, на котором я могу сказать что-то по поводу действий программы. Меня часто спрашивают: «Где в функциональном языке аналог UML-схем?» Думаю, лучший ответ — «система типизации». Там, где объектно-ориентированный программист будет рисовать картинки, я стану создавать сигнатуры типов. Они, конечно, не схемы, но поскольку мы в формальном языке, то они есть неотъемлемая часть текста программы и статически проверяются в коде, который я пишу. Поэтому у них масса достоинств. Это почти архитектурное представление того, что делает программа.

Сейбел: А вам приходилось писать такие программы, про которые известно, что они корректны, но по каким-то причинам выходят за границы проверки типов?

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

Сейчас есть целая мини-индустрия, которая исследует возможности применения типизации в программах с обобщенными типами. Это очень увлекательно. Но проще взять язык динамического типа. Я пытаюсь убедить Джона Хьюза написать статью для «Journal of Functional Programming» о том, чем плоха статическая типизация. Думаю, это будет интересная статья, если Джон — сторонник строгой типизации, изощренный прикладной функциональный программист, который сейчас пишет много на нетипизированном Erlang, — объяснит, чем плоха статическая типизация. Полагаю, статья будет полна интересных размышлений. Не знаю, чем все это закончится.

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

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

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

Сейбел: Что-то вроде контрактного программирования в Eiffel?

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

Сейбел: Как вы подходите к проектированию программ?

Пейтон-Джонс: Наверное, я могу сказать, что главная проблема — скажем, если я пишу что-то новое для GHC, — не в том, чтобы облечь идею в код, а скорее в том, чтобы сформулировать идею.

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

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

Затем главное понять, что здесь является типом данных. Что у нас в С--? Структура данных, представляющая императивную программу. Второй шаг — пройти по программе, смотря на каждый кусочек в отдельности. Ваше внимание идет по пути управляющей логики программы или, наоборот, откатывается назад через нее. Это удобно представлять через структуру данных под названием Zipper — полезная, чисто функциональная структура данных для того, чтобы окинуть взглядом чисто функциональную структуру данных.

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

Многие споры шли на уровне типов. Норман говорил: «Вот API», — показывая сигнатуру типов, а я в ответ: «Зачем так сложно?» Он объяснял зачем, а я говорил: «Может быть, вот так будет проще». Так что мы довольно долго бились над описанием типов.

Но много времени уходило не на собственно программирование, а на определение самой идеи. Что мы хотим сделать с анализом потока данных? Надо было дать четкий ответ: что подразумевает такой-то шаг программы. Так что немало времени мы потратили на уточнение того, что у нас на входе и что на выходе, и какие у них типы данных. И всего лишь определив эти типы данных, мы довольно подробно описали работу программы. Даже удивительно, насколько подробно.

Сейбел: Как размышления над типом данных соотносятся с кодированием? Набросав типы, вы можете приниматься за код? Или, наоборот, написание кода помогает в понимании типов?

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

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

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

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

Сейбел: Какие среды и инструменты вы сейчас используете?

Пейтон-Джонс: О, ужасно примитивные. Работаю в Emacs, компилирую при помощи GHC. Вот и все. Есть профилирующие инструменты, поставляемые с нашим компилятором; люди часто пользуются ими, чтобы профилировать программы на Haskell. Мы применяем их для профилирования самого компилятора. GHC производит много промежуточной выходной информации, и мне видно, что там происходит.

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

Я даже нечасто пользуюсь всеми хитростями Emacs, хотя некоторые любят этим заниматься. Также масса народу пользуется интегрированными средами разработки — Visual Studio, Eclipse. Мне кажется, неприятие языков функционального программирования отчасти связано с тем, что мы не выпустили свою интегрированную среду разработки. Опять проблема курицы и яйца. Сейчас напирают на курицу — идет всплеск интереса к функциональному программированию. Надеюсь, что и за яйцо тоже возьмутся. Интегрированная среда для Haskell потребует серьезной разработки. Даже при таких оболочках, как Visual Studio или Eclipse, предстоит большая работа над красивым плагином, который бы делал все как надо.

Сейбел: В GHC есть цикл REPL, GHCI. Вы предпочитаете работать с Haskell интерактивно?

Пейтон-Джонс: Ну, сам я сейчас в основном редактирую и компилирую. Но другие просто живут в GHCI.

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

Пейтон-Джонс: Думаю, если входные данные достаточно просты, проблем с моей программой быть не должно. Проблемы возникают, когда GHC пытается скомпилировать какую-нибудь непомерную входную программу и получает неверный ответ.

Тестирование необычайно важно для создания свойств. Очень полезна QuickCheck — библиотека Haskell, генерирующая случайные тесты для функции в зависимости от ее типа. И я старался понять, почему использую QuickCheck — очень приятный инструмент — меньше, чем мог бы. Видимо, потому, что меня беспокоят ситуации, когда трудно сгенерировать тестовые данные. Так или иначе, куча народу создает программы, от которых GHC просто воротит. Для этого у GHC и есть свой багтрекер.

Я обычно начинаю с чего-нибудь, что работает не так. Возможно, компилятор зависнет в каком-то месте, или откажется выполнять программу, которую должен выполнять, или станет генерировать неоптимальный код. Если он просто генерирует плохой код, я гляжу на него на разных стадиях компиляции и соображаю: «Здесь все в порядке, здесь тоже, а здесь нет — в чем дело?»

Сейбел: Как именно вы это делаете?

Пейтон-Джонс: В GHC есть флажки, которые позволяют выводить что-либо на печать.

Сейбел: Встроенные операторы печати для отладки?

Пейтон-Джонс: Да. Плюс к тому структура такая же, как у большинства компиляторов: на верхнем уровне есть конвейерная структура преобразований. Если что-то не так внутри какого-то из шагов, задача усложняется. Но я предпочитаю несложные методы отладки. Покажите мне программу до и после данного шага. Ага, я вижу, в чем ошибка! Если же не вижу, то могу использовать какой-нибудь из небезопасных операторов printf, чтобы понять, что происходит.

Есть разные отладчики для Haskell. Один из них, и просто отличный, написал в этом году студент летней школы, Пепе Иборра: это интерактивный отладчик, который теперь поставляется вместе с GHC. Я, правда, его мало использовал — он появился недавно, и, кроме того, не очень понятно, как пошагово проходить функциональную программу.

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

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

Сейбел: Это как всегда. Зачем создавать новые отладчики, если люди довольствуются операторами печати?

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

Возможно, вы разговаривали в основном с людьми академического склада и с теми, кто в силу возраста не привык к сложным отладчикам. Я бы не стал делать никаких общих выводов. И, конечно, не хочу принизить значение качественных отладчиков — особенно для сложных систем с множеством программных слоев. GHC очень прост сравнительно со средой .NET, где есть слои DOM и UML, и не знаю, что еще. Теперь вокруг столько примочек, что программная поддержка становится действительно важной.

Сейбел: Еще один способ создавать правильные программы — формальные доказательства. Что вы думаете об их полезности?

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

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

Более продуктивным для практических целей будет описание некоторых свойств, которыми должна обладать программа. К примеру, вы пишете: «Клапан 1 никогда не должен закрываться одновременно с клапаном 2. Это дерево всегда должно быть сбалансировано. Эта функция всегда должна иметь результат больше нуля». Это небольшие частичные спецификации, не полные. Это просто утверждения, в которых вы хотите быть уверены.

Как их написать? Функциональные языки неплохо приспособлены для этого. Именно это и происходит, если писать спецификацию в Quick-Check — свойства получаются функциями языка Haskell. Допустим, мы хотим проверить, что функция reverse является своей противоположностью, тогда мы напишем check reverse с типом список из А -> булевское значение. Итак, checkreverse от xs будет: reverse от reverse xs равно xs. Это функция, которая всегда оказывается верной. Функция-свойство. Но она написана на том же самом языке, и это здорово.

Теперь можно думать о статических проверках этого. Это может быть трудно, а может и легко. Но все равно, если свойство записано по всем правилам, это большое облегчение. Можно проверить его путем генерирования тестовых данных — именно это делает QuickCheck.

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

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

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

Мне кажется очень важным уйти от этого глобального подхода, «все или ничего», в сторону очень полезного статического и динамического тестирования частичных спецификаций. Это повысит вашу уверенность в правильности программы, а на большее рассчитывать нельзя. Даже так называемые полные спецификации не учитывают вещи вроде того, что программа должна работать за 0,1 с или занимать не более 10 Кбайт памяти. Часто в них ничего не говорится о ресурсах, о времени. Даже если программа формально отвечает спецификации, из-за этих мелочей она может работать не так, как нужно. Думаю, мы обманываем себя, когда говорим, что проверили программу и все в порядке. Лучше честно сказать, что мы повышаем свою уверенность в ней. Тут все может начинаться скромно — вы повышаете уверенность на 75%, прикладывая всего 5% от общего количества усилий. Это хороший показатель.

Сейбел: Поговорим о параллелизме. Гай Стил поручил мне задать вам вопрос о транзакционной памяти — спасет она мир или все же нет?

Пейтон-Джонс: Нет, конечно. Сама по себе — нет. Параллелизм — многоголовый зверь, которого не убьешь одной пулей. Если речь заходит о параллелизме, то я за различные подходы.

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

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

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

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

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

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

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

Пейтон-Джонс: Но кое-что остается неизменным — шифр книги, верно? Есть несколько способов решить задачу. Например, вы можете сказать, что при работе с заглушкой сама библиотека не меняется, меняется только сама книга. Вы не изменяете ее ключевое поле — только значение, где книга в данный момент находится. И теперь каталог может меняться, где бы книга ни была. Это прекрасно и поддается выражению естественным способом.

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

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

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

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

Вот один из них — в системах с блокировкой этого нет. Я определяю высокоуровневые инварианты: у меня несколько банковских счетов, общая сумма денег на них равна N. Деньги перемещаются со счета на счет. Вот мой инвариант. Любая транзакция предполагает этот инвариант в начале и восстанавливает его в конце. Как вы определяете, что она это делает? Мы смотрим на любую транзакцию вида «Возьмите три из этого места и переместите их вон в то». Инвариант сохранен. Каково мое умозаключение в данном случае? Чисто последовательное. Определив высокоуровневые инварианты, я могу делать последовательные умозаключения о каждой транзакции отдельно.

Сейбел: Поскольку транзакции изолированы друг от друга.

Пейтон-Джонс: Да. Это действительно очень мощный принцип. Можно делать последовательные умозаключения относительно императивного кода, несмотря на параллелизм. Вы обязаны определить высокоуровневые инварианты, но это также полезно для душевного спокойствия: вы знаете, что именно пытаетесь сохранять. Если посреди транзакции встречается исключение, это тоже здорово — оно не может уничтожить инварианты, поскольку транзакция тогда завершается ничем. Просто сказка! И совершенно по-другому теперь можно рассуждать о скорости выполнения — вы удостоверились, что все минимально правильно, теперь надо убедиться, что программа нигде не подтормаживает. Это уже труднее: на сегодня есть только профилирующие инструменты и инструменты точечной обратной связи.

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

Пейтон-Джонс: Ну, транзакционную память можно реализовать несколькими способами, и оптимистический параллелизм — лишь один из них. Можно устраивать блокировку по мере продвижения — это уже больше похоже на пессимистический параллелизм.

Сейбел: Тут есть еще и тот момент, что менеджеры блокировок — самая сложная часть баз данных.

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

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

Последовательная реализация очереди с двухсторонним доступом — это задача, которую проходят в объеме курса бакалавра. Для параллельной реализации с блокировкой по каждому узлу — это тема для научной статьи. Это слишком нелегко, прямо до абсурда. А для транзакционной памяти такую проблему решает студент. Вы просто «заворачиваете» операции «вставить» и «стереть» в одну атомарную операцию — и дело сделано. По-моему, это занятно. Тут есть количественная разница. Сегодня те, кто реализует транзакционную память, должны убедиться, что несколько изменений вносятся в память как единая атомарная операция. Это не так просто — у вас есть только атомарная инструкция «сравнение с обменом». Надо быть внимательным.

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

Хочу сказать еще кое о чем — мы опять возвращаемся к функциональному программированию. Транзакционная память, конечно, не имеет к нему прямого отношения. Она касается изменения разделяемого состояния — не самой функциональной вещи.

Дело в том, что когда-то я пошел на лекцию Тима Харриса о транзакционной памяти в Java. До этого о транзакционной памяти я вообще не слышал. Тим описывал «атомарные транзакции» и, в общем, больше ничего такого.

Я сказал: «Здорово, но тогда придется протоколировать все побочные эффекты в памяти, все инструкции по загрузке и хранению. А сколько их в Java!» Но в Haskell, благодаря монадам, их почти нет. Загрузка и сохранение в Haskell выражены явно, и программисты считают их важными операциями.

И я подумал, что надо ввести в Haskell все эти атомарные операции, — будет классно. Потом, после лекции, я стал объяснять Тиму, как это можно сделать. Вскоре, поскольку у нас была чистая, элегантная инфраструктура, мы придумали retry и orElse. Механизм retry позволяет осуществлять блокировку внутри транзакции, а orElse — выбор внутри транзакции. Тиму и его коллегам при разработке транзакционной памяти такое не пришло в голову, поскольку они имели дело с довольно сложной средой.

И потому они мало задумывались о блокировке — или предполагали, что блокировка будет атомарной операцией вида «запускать эту транзакцию только при наличии такого-то предиката». Но это очень некомпозиционно. Предположим, вы хотите переложить деньги с одного счета на другой: каковы условия проведения транзакции? Ответ: если на первом счете достаточно денег, а на другом достаточно места, — то есть имеются ограничения на обеих сторонах. Довольно сложное условие. Если задействован и третий счет, все еще сложнее. Все очень некомпозиционно — надо вытаскивать наружу все предусловия методов.

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

Сейбел: То есть эта концепция на самом деле не завязана на Haskell, вы просто смогли ее придумать?

Пейтон-Джонс: Правильно. При той изначальной простоте легче было оценить хорошую идею. Нас все больше раздражало, что нельзя устроить блокировку, не теряя абстракции. Так и получились retry и orElse. Видимо, функциональное программирование лучше всего подходит для этого: изучить какого-нибудь неведомого монстра и затем выпускать в обычный мир. Транзакционная память — прекрасный пример; там было взаимодействие в обоих направлениях. Цикл теперь замкнулся, и, по-моему, это чудесно.

Сейбел: Какие книги по программированию вы бы взяли с собой на необитаемый остров?

Пейтон-Джонс: Обязательно «Programming Pearls» Джона Бентли. По поводу жемчужин: в чудной книге «Beautiful Code» есть глава Брайана Хэйеса «Создание программ для „Книги"». Думаю, под книгой Брайан понимает вечно прекрасную программу. Даны три точки, надо найти, с какой стороны линии между двумя точками окажется третья. Есть решения, которые работают неважно, а потом находится идеальное простое решение.

Еще, конечно же, «The Art of Computer Programming» Дона Кнута. Это не та книга, чтобы читать ее от и до, но одно время я активно ею пользовался. «Purely Functional Data Structures» (Чисто функциональные структуры данных) Криса Окасаки — просто фантастика: нечто вроде курса Артура Нормана, из которого сделана целая книга. Про то, как делать очереди, поисковые таблицы и кучи без всяких побочных эффектов и с хорошими ограничениями алгоритмической сложности. Великолепная книга, читать каждому. К тому же невелика по объему и доступно написана. «Structure and Interpretation of Computer Programs» Абельсона и Сассмана — мне очень понравилось. «Compiling with Continuations» (Компиляция на продолжениях) Эндрю Аппеля — о том, как компилировать функциональную программу, используя стиль передачи продолжений. Тоже превосходная вещь.

Есть одна важная для меня книга, которую я читал мало, — «A Discipline of Programming» Дейкстры. Дейкстра заботится о красоте программ. Его программы полностью императивны, но обладают «свойством Хо-ара»: вместо того чтобы не иметь очевидных ошибок, они совершенно очевидно не имеют ошибок. Он очень хорошо и изящно рассуждает об этом. Я впервые понял, как это — рассуждать о программах, когда тебе невозможно возразить. Наконец, на меня сильно повлияла книга Пера Бринча Хансена о написании параллельных операционных систем. Я постоянно перечитываю ее.

Сейбел: Вы сейчас много программируете?

Пейтон-Джонс: Конечно. Ни дня без кода. Ну, не то чтобы так и было, но это моя мантра. Мне кажется, тем, кто что-то делает хорошо, угрожает движение вверх по карьерной лестнице, пока они совсем не перестанут заниматься тем, что делают хорошо. Поэтому работа в Microsoft Research и в исследовательской сфере вообще хороша тем, что я могу заниматься компилятором, над которым тружусь с 1990 года. Это большая по объему вещь, и есть длинные фрагменты кода, которые я знаю лучше всех остальных.

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

Сейбел: Вам это доставляет такое же удовольствие, как и в начале?

Пейтон-Джонс: Да, конечно. Это лучшая в мире вещь. Мне кажется, у большинства программистов есть чутье: «здесь должен быть какой-то хороший выход». Работа в исследовательской сфере хороша еще и тем, что надо мной не стоит менеджер, которому нужен результат через неделю. Я могу спокойно сесть и подумать: «Здесь должен быть какой-то хороший выход».

Поэтому я много времени уделяю рефакторингу, вожусь с интерфейсами, создаю новые типы или полностью переписываю большие куски, чтобы их улучшить. GHC довольно велик — не по промышленным стандартам, а по понятиям функционального программирования: в нем 80 000 строк кода на Haskell, а может, и больше. Это компилятор-долгожитель — ему уже пятнадцать лет. Он активно развивается, большие куски переписываются, и нет мест, которые нельзя трогать. Поэтому меня так приятно возбуждает возможность сесть и сказать себе: «Здесь должен быть какой-то хороший выход». Иногда я оставляю что-нибудь на несколько недель — не могу найти хороший выход, зная, что он есть. Это мучительно. Потому что красивый способ должен быть.

Сейбел: Что происходит в эти несколько недель?

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

Сейбел: Как это происходит? Вы просыпаетесь утром и понимаете, что решение найдено? Или снова начинаете восхождение и в конце концов добираетесь до вершины?

Пейтон-Джонс: Скорее второе. Озарения по утрам случаются редко. На исследовательской работе есть время подумать над сделанным, записать свои мысли. Иногда получается настолько интересно, что я пытаюсь соорудить статью. Одна из таких статей называется «The Secrets of the GHC Inliner» (Секреты GHC). В ней описываются техники реализации, примененные нами для некоторых элементов GHC и полезные, как мы считаем, другим разработчикам. Как ученый, я имею возможность абстрагироваться от кода, которому мы на четвертый раз наконец-то придали нужный вид, и написать о нем, чтобы другие смогли применять наши методы.

Сейбел: Что для вас программирование? Вы считаете себя ученым, инженером, ремесленником или кем-нибудь еще?

Пейтон-Джонс: Вы знакомы со статьей Фреда Брукса на этот счет -«The Computer Scientist as Toolsmith» (Компьютерный исследователь как системный программист)? Я недавно ее перечитал — отлично написано. Нельзя забывать, что прежде всего мы конструируем разные вещи. Поэтому программировать так увлекательно.

Но одновременно мне очень хочется вывести некие постоянно действующие принципы. У меня есть статья о том, как написать хорошую статью или сделать хороший доклад об исследовании, и одно из главных правил в ней — «не описывайте артефакт». Артефакт есть продукт реализации некой идеи. А что такое идея, этот предмет многократного использования, который вы пытаетесь вручить своим читателям или слушателям? Нечто такое, что может быть для них полезно. Задача ученого как раз в том, чтобы извлекать идеи, пригодные для многократного использования, из конкретных артефактов. Это не наука в смысле открытия законов. Но это перевод ежедневной жизненной суеты в абстракции многократного использования. И я считаю это важным.

Сейбел: Поговорим на тему «инженер или ремесленник». Должны ли мы уподобляться тем, кто строит мосты, которые, по большей части, не разваливаются? Или скорее имеем дело с обжиганием горшков — пусть даже очень сложных горшков, — когда можно лишь научиться этому делу у другого гончара?

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

Насколько велик GHC? У меня нет внутреннего чувства на этот счет — такого, какое есть насчет здания. Поэтому, думаю, мы совсем не инженеры-мостостроители. Их шаблоны проектирования настолько отточены, что они действительно могут быть уверены, что мост не развалится. Мы даже близко к этому не стоим. Но это вовсе не значит, что мы не должны заботиться об этом.

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

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

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

Сейбел: Гай Стил говорил, что закон Мура действовал на протяжении всей его карьеры программиста, но вот насчет всей карьеры своего сына он уже не уверен. Он также рассуждал насчет программирования в ближайшем будущем. Не пора ли уже прекращать эти разговоры -«Построили мост через Ла-Манш, давайте строить через Атлантику»?

Пейтон-Джонс: Нет-нет. С ПО все иначе. Если вы написали программу в десять раз больше предыдущей, это не значит, что вам нужно запускать ее на компьютере, работающем в десять раз быстрее. Программный счетчик тратит 90% времени на 10% кода или как-то так. Участки программы, критичные с точки зрения быстродействия, могут быть невелики по объему.

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

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

Сейбел: Что вам особенно нравится в программировании?

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

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

Системы слишком уж распухли. Если вы хотите сделать веб-сервис на ASP.NET, то должны освоить API и инструменты, уметь писать на трех разных языках, знать Silverlight и LINQ — и вы можете создавать акронимы до конца света. И про каждый из них есть толстая книга.

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

И это мне не нравится. Я так и не запомнил таблицу умножения. Я всегда все делал на основе нескольких простых правил и придумал много хитростей, чтобы делать это быстро. Все умножают семь на девять, а я умножаю семь на десять, вычитаю, получаю шестьдесят три. Другие просто запоминают таблицу. А она ведь совсем небольшая. Поэтому я не люблю системы, которые заставляют меня держать много чего в голове, и инстинктивно их сторонюсь. В то же время я признаю, что они полезны и важны в практическом плане. Но я задаю себе вопрос: если подумать над ними чуть дольше, нельзя ли сделать интерфейсы меньше, проще и универсальнее?

Сейбел: Мне иногда кажется, что вся эта сложность — именно из-за того, что системы разрабатывают умные люди. И им хочется играть в свои игры.

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

На практике нам приходится разбивать задачи на кусочки. За каждый кусочек отвечает человек, который исходит из собственного опыта и культурного багажа. То, что он создает на своем пространстве, хуже, чем могло бы быть, — человеку не хватает времени. В итоге весь ансамбль тоже выглядит хуже, чем мог бы. Плюс отрицательное влияние наследства прежних систем.

Это наследство — как ядро каторжника, которое мы волочим за собой. И в этом отношении удачей стал Haskell. Оглядываясь, я всегда вижу перед собой принцип, который мы в него заложили: «Избегать успеха любой ценой». Теперь это что-то вроде мема — люди запомнили эту фразу и мне же ее цитируют.

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

Сейбел: Вы несколько раз упоминали о красоте кода. Каковы признаки красивого кода?

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

Сейбел: А как насчет перлов — небольших замысловатых, но интересных фрагментов кода, — они тоже красивы?

Пейтон-Джонс: Иногда что-то правильно, но этого не понять без умственных усилий. Иногда нужно предварительное знание. Если взглянуть на код для АВЛ-дерева, не зная, в чем его смысл, не понятно, зачем нужны все эти вращения. Но если вы знаете, какой инвариант тут поддерживается, то видите: ага, если поддерживается этот инвариант, мы получаем логарифмическое время поиска. Вы смотрите на каждую строку кода и понимаете, что здесь поддерживается инвариант. Инвариант дает нам предварительное знание и позволяет судить о том, что код совершенно очевидно правилен.

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

Сейбел: Есть ли верхняя граница, если говорить о размере, вплоть до которой код все еще может считаться красивым?

Пейтон-Джонс: Не знаю, связано ли это с размером. Знание, необходимое, чтобы убедить себя, что он правильный, направлено в сторону большей уверенности в его коррекности. В очень больших программах всегда есть недочеты или даже несомненные ошибки. Но ликвидировать их сразу же бывает нецелесообразно. Это справедливо в отношении GHC и еще более — в отношении программ Microsoft.

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

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

Есть ли верхняя граница, если говорить о размере? Не знаю. Но мне кажется, что продолжая создавать хорошие абстракции, можно строить мосты через Атлантику. У нас есть программы, которые работают, — пусть не идеально, но на удивление хорошо, учитывая их размер.

Сейбел: Вопрос в том, можно ли построить здание — большое, приспособленное для проживания и красивое одновременно.

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

Сейбел: И единственный выход — признать, что программа отжила свое, и начать делать новую?

Пейтон-Джонс: Надо переработать какие-то ее куски. Если не можешь позволить себе переработку в то время, как программа разошлась и используется, то не исключено, что лет через десять придется ее выкинуть и задуматься о новой. А если можешь, если программа обновляется так, как самообновляются клетки нашего тела, получается то, что, надеюсь, происходит с GHC.

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