(2) Подспудная простота. Хотя общая схема процедуры может быть блестящей или давать блестящие результаты, каждый из шагов и переходы между шагами предельно просты. Насколько просты? Достаточно просты, чтобы с ними справился прилежный идиот – или нехитрое механическое устройство. Как правило, в учебниках алгоритмы сравниваются с рецептами, созданными для кулинаров-новичков. В кулинарной книге для умелых поваров может быть написано: “Припустите рыбу в подходящем вине почти до готовности”, – но алгоритм того же процесса, возможно, начнется так: “Выберите белое вино с маркировкой «сухое»; возьмите штопор и откройте бутылку; налейте вино в кастрюлю до уровня в два с половиной сантиметра от дна; включите конфорку под кастрюлей на полную мощность…” Иначе говоря, алгоритм дотошно разбивает процесс на предельно простые шаги, не требуя от его исполнителя ни принимать сложные решения, ни выносить осторожные суждения, ни применять интуицию.
(3) Гарантия результатов. Что бы ни делал алгоритм, он делает это всегда, если в процессе его исполнения не происходит ошибок. Алгоритм – это надежный рецепт.
Вполне очевидно, как эти свойства сделали возможным появление компьютера. Любая компьютерная программа представляет собой алгоритм, в конечном счете состоящий из простых шагов, которые могут с поразительной безотказностью исполняться тем или иным простым механизмом. Обычно выбор падает на электронные схемы, но каузальные особенности электронов, шныряющих туда-сюда по кремниевым чипам, никак не влияют на силу компьютеров (хотя и могут влиять на скорость их работы). Те же самые алгоритмы могут исполняться даже быстрее устройствами, которые манипулируют фотонами в оптоволокне, или – гораздо медленнее – командами людей, использующими бумагу и карандаш.
Фактически Дарвин открыл не один алгоритм, а целый класс родственных алгоритмов, которые он не мог однозначно отличить друг от друга.
27. Автоматизация лифта
Прежде чем закончить интерлюдию о компьютерах, я хочу описать еще одну группу полезных идей об исходном коде, комментариях и объектном коде, которые мы сумеем применить, чтобы лучше понять, как значение может храниться в мозге. Не стоит сразу браться за головоломку – для начала целесообразно подробнейшим образом изучить предельно простой пример и усвоить все понятия. (В сфере искусственного интеллекта эти примеры называются модельными задачами. Прежде чем браться за жуткую реальную задачу, стоит сначала решить модельную.) В связи с этим я предлагаю вам познакомиться с историей – выдуманной ради простоты, но в остальном вполне реалистичной – о том, как лифтеров заменили компьютерные чипы.
В моей молодости были лифтеры – люди, работа которых заключалась в том, чтобы весь день ездить на лифте вверх-вниз и останавливаться на нужных этажах для посадки и высадки пассажиров. Когда-то они крутили любопытную ручку, которую можно было поворачивать по часовой стрелке и против часовой стрелки, чтобы направлять лифт вверх или вниз, и им требовалась определенная сноровка, чтобы останавливать кабину на нужной высоте. При входе в лифт и при выходе из него людям часто приходилось подниматься или спускаться на небольшую ступеньку, и лифтеры всегда их об этом предупреждали. Существовал целый свод правил относительно того, что и когда говорить, на какие этажи подниматься в первую очередь, как открывать двери и так далее. На стажировке они заучивали эти правила и практиковались следовать им, пока это не входило у них в привычку. Сами правила разрабатывались годами, и в процессе в них постоянно вносились небольшие изменения и уточнения. Представим, что этот процесс остановился, когда был создан идеальный свод правил. Он чудесно работал. Любой, кто в точности следовал правилам, становился превосходным лифтером.
Теперь представим, что случилось, когда простая компьютерная программа сумела взять на себя все задачи лифтера. (На самом деле это происходило постепенно, по мере того как появлялись все новые автоматические механизмы, которые забирали у лифтеров все более сложные задачи, но мы представим, будто лифты перешли от человеческого контроля к полностью компьютерному одним махом.)
Допустим, производитель лифтов нанимает команду программистов и вручает им свод правил, которым руководствовались лифтеры: “Вот подробное описание необходимых нам функций; напишите компьютерную программу, которая будет следовать правилам из этой книги так же хорошо, как лучшие лифтеры, и мы будем довольны”. Изучая свод правил, программисты составляют список всех нужных действий и условий, при которых их следует и не следует предпринимать. В процессе они могут избавиться от некоторой небрежности свода правил. К примеру, если они встроят в лифт датчики, которые будут обеспечивать остановку лифта на нужной высоте, они смогут избавиться от цикла, требующего от лифтера сказать “Осторожно, ступенька”, но, возможно, оставят простую (записанную на пленку) фразу “Этаж N-й. Будьте осторожны при выходе из кабины”. Затем они пишут набросок программы, используя так называемый псевдокод – неформальный язык, нечто среднее между обычным человеческим языком и более требовательной системой исходного кода. Строка псевдокода может выглядеть примерно так: “если этаж вызова > текущего этажа, то ASCEND до этаж вызова = текущему этажу и STOP; OPENDOOR. WAIT…”.
Как только план написан на псевдокоде и выполняет необходимые условия, псевдокод можно перевести в исходный код, который представляет собой гораздо более строгую и структурированную систему команд, включая определение терминов – переменных, подпрограмм и так далее. Человеку по-прежнему несложно расшифровать исходный код – в конце концов, его пишут люди, – а следовательно, правила и условия свода правил в нем достаточно очевидны, если знать, куда смотреть. Расшифровке исходного кода способствуют две его характеристики: во-первых, по названиям переменных и команд обычно можно понять, за что они отвечают (callfloor [этаж вызова], weightsum [общая масса], TELLFLOOR [объявить номер этажа] и т. д.). Во-вторых, как мы видели в главе 24, программисты могут добавлять к своему исходному коду комментарии, заключенные в скобки объяснения, которые говорят читателям исходного кода, что имел в виду программист и что должны делать разные элементы программы. При создании программы целесообразно снабжать свой код комментариями, ведь забыть, зачем нужна конкретная строка кода, очень легко. Эти комментарии окажутся очень полезны, когда вы будете исправлять ошибки программирования.
Исходный код составляется согласно строгому синтаксису, где каждому элементу отводится свое место и не допускаются пунктуационные ошибки, поскольку он загружается в программу-компилятор, которая берет исходный код и транслирует его в последовательности элементарных операций (объектный код), подлежащих исполнению реальной (или виртуальной) машиной. Компилятор не может гадать, что программист имел в виду в той или иной строке исходного кода, поэтому исходный код должен точно говорить компилятору, какие команды исполнять, однако компилятор может выполнять эти задачи множеством разных способов и умеет выбирать наиболее эффективный способ, исходя из обстоятельств. Одни компиляторы работают лучше других: к примеру, если загрузить одну и ту же программу (в исходном коде) в два разных компилятора, объектный код, выданный одним компилятором, может исполняться гораздо быстрее, чем объектный код, выданный другим компилятором. Допустим, вы написали шахматную программу, загрузили ее исходный код в два разных компилятора и запустили две скомпилированных версии играть друг против друга на одном компьютере. Хотя обе версии будут “обдумывать одни и те же мысли в одном и том же порядке” (у них нет другого выбора – у них одинаковый исходный код), возможно, одна из них всегда будет выигрывать, просто потому что она обдумывает эти мысли быстрее, используя меньшее количество базовых машинных циклов, а следовательно, просчитывает за отведенное время большее количество ходов!