Программы и агенты
Приготовьтесь. Я сейчас открою вам страшную тайну.
Дело в том, что ни одна программа ничего не делает и не вычисляет сама по себе — для этого нужен некто, способный прочесть код, интерпретировать его как инструкцию к исполнению, и выполнить в соответствии со своим пониманием и своими возможностями. Точно так же, как учебник анализа остается мертвым текстом, пока кому-то не приспичит приложить производные да интегралы к практическим задачам.
Казалось бы, ничего особенного, и должно подразумеваться само собой, — но почему-то многие теоретики компьютерной науки и компьютерные философы об этом забывают.
Например, в книгах по декларативным языкам программирования можно встретить высказывания о каких-то запрограммированных в декларативном стиле вычислениях, да еще с глубокомысленными замечаниями и рекомендациями по оптимизации кода, — хотя на самом деле никакое утверждение ни на каком языке не предполагает необходимости что-то в нем сообщаемое выполнять, поскольку выполнять, в общем-то, нечего. Декларативный язык потому и называется декларативным, что он описывает, констатирует — а не предлагает. Если есть, допустим, утверждение "Я люблю манную кашу" — наше дело принять его к сведению, а любая активность по поводу — это уже наша личная инициатива.
Очевидно, поборники декларативности просто путают разные вещи — и немного жульничают, в рекламных целях. Они скромно умалчивают, что любая обработка кода начинается в ответ на совершенно императивную команду; введена эта команда оператором в интерактивной оболочке или считана неким агентом из какого-то хранилища в процессе выполнения другой программы — совершенно неважно. То, что в некоторых оболочках команды замаскированы под предложения декларативного языка, дела не меняет. Смысл написанного определяется контекстом — то есть, по сути дела, некоторым агентом, в этом контексте работающим.
Так что же такое — этот загадочный агент?
В элементарном курсе информатики нас учат, что программы исполняются процессором, который выбирает из оперативной памяти инструкцию за инструкцией, загружает в регистры необходимые данные, как-то эти данные преобразует, куда-то при необходимости сохраняет, — и все начинается сначала. В принципе, процессоров может быть много — и они как-то делят работу между собой. Кто-то специализируется на управлении устройствами, кто-то наделен набором универсальных команд. Суть от этого не меняется. В любом случае исполнителем будет некое устройство, способное преобразовывать данные — а программа представляет собой такие же данные, как и все остальное; по джентльменскому соглашению, мы принимаем, что какие-то записи в памяти представляют исполняемый код, а другие — только подвергаются обработке.
Картина, конечно же, упрощенная. Например, программа может на лету компилировать куски динамически формируемого текста и объявлять результат исполняемым кодом — процессору это все равно. Кое-кто назовет подобные трюки дурным стилем программирования. Но если присмотреться, живая природа в большинстве случаев предпочитает действовать именно так, совмещая разные функции в одном органе, — неграмотная, что с нее возьмешь?
Не существует предпочтительного набора базовых команд. Сколь угодно сложные куски кода часто оформляются как элементарная команда — иногда они внедряются в архитектуру компьютеров, становятся микропрограммой; микропрограммы могут быть реализованы аппаратно, как команда процессора, — и наоборот, какие-то ранее элементарные команды становятся микропрограммами, а то и внешним кодом. Все зависит от текущей моды, от преимущественной ориентации на определенный класс задач.
В этом плане работа командного интерпретатора по сути ничем не отличается от исполнения откомпилированного кода — есть иерархия операций, и вместо низкоуровневых деталей можно оперировать крупными блоками. Соответственно, компилируемые языки ничем принципиально не отличаются от интерпретируемых — и в том, и в другом случае требуется некоторая среда, организующая переход от высших уровней к низшим. Оболочка операционной системы, интерпретатор, виртуальная машина, IDE... Все это эквивалентные способы иерархической организации и визуализации работы процессора. Пошаговое выполнение программы в отладчике какого-либо компилируемого языка выглядит точно так же, как исполнение кода в диалоговых оболочках Ruby или Prolog. А в конечном счете работает все та же железка, со своим набором встроенных команд.
Но тут есть одна философская тонкость.
Сегодня мы при слове компьютер сразу представляем себе цифровое устройство — а ведь компьютеры бывают еще и аналоговые. И начиналась-то информатика именно с них. Программы не вводили с какого-то внешнего носителя, и не набивали с терминала, — а набирали вручную путем коммутации аналоговых цепей. Первые цифровые компьютеры поначалу тоже программировали путем ручной установки значений регистров, обыкновенными кнопками и тумблерами. Это потом стало проще, когда появились стандарты архитектуры и стандартные форматы данных, и программы стали всего лишь разновидностью данных, а не положением переключателей на пульте управления.
Но все равно, современный процессор — это в конечном счете некое аналоговое устройство, скоммутированное таким образом, чтобы обеспечить выполнение стандартного набора операций. То, что коммутация выполняется на уровне микросхем и даже отдельных атомов (а теперь уже и фотонных пучков), ничего не меняет по сути. Есть набор устройств, и мы коммутируем их, вводим определенную программу — а именно, выборку данных и команд, преобразование данных, передачу управления, взаимодействие с другими процессорами и контроллерами устройств, и т. д.
Получается, что работает процессор не сам по себе, а лишь в качестве нашего помощника, автоматизирующего некоторые типовые операции, которые мы, в принципе, могли бы выполнить и сами, кабы у нас не было иных, более интересных занятий. Процессор лишь действует по заложенной в него жесткой программе, он так устроен, и никуда от этого не уйти. Выходит, компьютерное железо ничем не фундаментальнее трансляторов или интерпретаторов. А подлинным агентом оказывается живой человек.
Современному компьютерщику трудно представить себе то далекое время, когда не было персональных компьютеров и планшетов, когда про компьютерные сети мало кто слыхал, а напрямую управлять компьютером разрешалось только специально обученному персоналу. Новые поколения уже не знают, что такое перфокарта или перфолента, — разве что понаслышке, из школьного курса истории информатики. А я еще помню, как приходилось вместо терминала сидеть за клавиатурой перфоратора, и правильность набора проверять при помощи карты-читалки (текстовая строка на перфокартах появилась далеко не сразу, да и печать в ней была не безошибочной). О том, чтобы отлаживать программу в реальном времени, речи быть не могло. Не спасала и возможность вставлять в код отладочную печать, поскольку результаты работы выдавались на страшный агрегат под названием АЦПУ, а расход специальной бумаги для печати в организациях ограничивался по каждому отделу. В таких условиях программисту частенько приходилось самому исполнять роль машины и проигрывать типовые варианты выполнения программы до передачи задания оператору. Если в работе программы наблюдались странности — ошибки вылавливать надо было тем же методом, принимая роль процессора на себя. Идея о первичности человека как агента исполнения программ была, так сказать, прочувствована на собственных мозгах.
Можно возразить, что человек тоже представляет собой некоторое аналоговое устройство, набор нейронов в мозгу, — и что он тоже просто выполняет заложенную в него программу. Встает вопрос: кем заложенную? Если все сводить к чисто природной эволюции, к физиологии, — мы ничем не отличаемся от животных. Кому-то приятно чувствовать себя скотом — а я не хочу. Если допустить, что был какой-то сверхъестественный программист — мы впадаем в мистику, и это уже не философия, а сплошное суеверие.
Но есть еще один путь — допустить, что природа человека вовсе не определяется его физиологией, что человек — это всегда член общества, включенный в иерархию общественно значимых деятельностей, и именно это включение с младенчества формирует определенные способы функционирования мозга, программирует наши "мокросхемы" (wetware). Тогда получается, что главный программист — это общество в целом, процесс общественного производства и всевозможные культурные процессы. А если уж на то пошло, то и общество программируется чем-то другим — в его жизнедеятельности проявляются объективные законы развития мира в целом, неизбежно приводящие к возникновению разума в каких-то формах, которые потом исчезнут без следа, но повторятся в другом месте, в другое время, другими способами, на другом уровне.
Вот такой компот. Сложновато для непривычных.
Однако что с того современному компьютерщику? Какое нам дело до всеобщих материй? Нам бы скоренько сваять что-нибудь под нужды заказчика, чтобы на выходе было правильно и чтобы на руки за это поиметь.
А дело в том, что любые методы программирования возникают не от фонаря — они выполняют какую-то общественную задачу. Поэтому, скажем, одни языки программирования приживаются — а другие нет.
— Ничего подобного, — скажет крутой программер, — просто одни языки для чего-то удобнее других.
Но откуда берется это что-то, для чего оно удобно? Почему получается, что программисту или админу надо решать вполне определенный круг задач? Так исторически сложилось. А история — это история человеческой деятельности. Отсюда мораль: не следует абсолютизировать. Сейчас оно так, потом будет по-другому.
В частности, то, что единственным реальным агентом сейчас является человек, — не догма, а руководство к действию. Возможно, когда-то в будущем машины смогут сами выбирать правила своей работы, самостоятельно решать, как лучше оптимизировать свою архитектуру. А там, глядишь, появятся и другие агенты, о которых мы пока ничего не знаем, и представить себе не в состоянии. Тем не менее, философски подкованный программист к такому повороту всегда готов.
|