[an error occurred while processing this directive]

.цв

11. СТИЛИ ПРОГРАММИРОВАНИЯ НА LISP.

.ов

Все до сих пор рассмотренные средства языка LISP не убеждают в том, что LISP является мощным и универсальным языком программирования. Эти средства, однако, могут обеспечить любые вычисления, осуществляемые компьютером. Действительно, комплект функций LISP ATOM, CAR, CDR, COND, CONS, EQ, DEFUN является завершенным языком программирования общего назначения. Этот факт будет в дальнейшем проиллюстрирован при определении более совершенных функций LISP в терминах этих элементарных функций. Во многих практических применениях встроенные функции универсальных вычислительных машин могут работать и на микросистемах при надстройке исходных средств некоторыми дополнительными определениями.

Опыт специалистов LISP показывает не только теоретическую возможность применения ограниченных форм LISP, но и практическое удобство этого применения. Данный раздел содержит ряд идей по стилю программирования на LISP, позволяющему избежать употребления программных средств, выходящих за рамки рассмотренных до сих пор. Этот стиль программирования предполагает создание фрагментов программ, состоящих из многих малых функций. Эти функции обрабатывают структуры данных, установленные SETQ И PUT.

Если использование PUT И SETQ ограниченно указанным образом, то поведение функций LISP может зависеть только от следующих за ними аргументов. Это повдение не может зависеть от "сторонних эффектов", определяемых предыдущими функцями. Основным достоинством такого построения LISP программы является то, что каждую функцию можно прочитать независимо от всех остальных функций. Это способствует более удобному способу разработки и отладки программ, т.к. функции могут записываться, тестироваться и корректироваться по отдельности, до того, как их объединить в единую структуру программы. В отличие от рассмотренного существуют виды LISP программ, являющиеся схемами преобразования данных аргумента в результат. При этом каждая отдельная функция будет строится для выполнения какого-либо шага этого преобразования, оставаясь при этом как можно более простой. Полный процесс данного преобразования, т.о., состоит из целой серии почти элементарных шагов, смысл каждого из которого очевиден.

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

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

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

.цв

12. ПОСТРОЕНИЕ ПРОСТЫХ ФУНКЦИЙ LISP.

.ов

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

(DEFUN имя (аргументы) (COND)

(условие некоторая простая структура)

(Т вызывающая имя с несколько измененными аргументами)))

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

Рассмотрим задачу объединения двух различных списков в один. в LISP функция, выполняющая эту операцию, обычно имеет имя APPEND. Если один из рассматриваемых списков является пустым, работа APPEND является весьма несложной операцией. В начале определения можно принять во внимание этот факт следующим образом:

   (DEFUN APPEND (A B) (COND
      ((NULL A) B)
      ...

Данная запись, очевидно, находится в соответствии с приведенным выше образцом. Если первый аргумент функции APPEND не является пустым списком, мы можем попытаться построить нужный результат, учитывая тот факт, что мы имеем дело со списком, имеющим по крайней мере один элемент. Это означает, что можно расчленить А на (CAR A) и (CDR A). Считая, что рабочая версия APPEND уже существует, заметьте, что

   (APPEND (CDR A) B)

сконструирует требуемый объединенный список без первого элемента (т.е. содержащий элемент (CAR A). Этот пропущенный элемент может быть заменен при использовании функции CONS и т.о., будет получено полное определение формы:

   (DEFUN PPEND (A B) (COND
      ((NULL A) B)
      (T (CONS (CAR A)
               (APPEND (CDR A) B)))))

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

   (APPEND '(P Q R) '(X Y Z))

Сначала тестируется первый аргумент, является ли он пустым списком или нет; видим, что нет. Т.о., APPEND доходит до конца с

   A = (P Q R)
   B = (X Y Z)

Функция вызывается еще раз с аргументами (CDR A) и B, что аналогично случаю вызова APPEND на более высоком уровне.

   (APPEND '(Q R) '(X Y Z))

Этот случай является более простым по сравнению с исходным, т.к. длина первого аргумента APPEND уменьшилась. Список (Q R X Y Z) становится значением функции APPEND. Чтобы получить результирующее значение (P Q R X Y Z) внешней функции APPEND, необходимо обработать функциями CONS значения (CAR A) (т.е.P).

Повторные вызовы функции APPEND не приводят к бесконечным последовательностям обращений к функции, т.к. успешные вызовы функции включают более короткие списки в качестве первых аргументов. Очевидно, уменьшение длины аргумента приводит к таким вызовам функции, при которых первый аргумент является пустым списком, поэтому тест (NULL A) завершится и не требуется никаких дальнейших обращений. Т.к. все внутренние обращения к APPEND заканчиваются, то необходимый список преобразовывается с помощью функций CONS до тех пор, пока он не возвращается пользователю. Этот процесс может быть упрощен, если выполнять его по шагам включенным в пример вызова функции, где каждая отдельная строка показывает преобразование начального обращения к APPEND в вводную строку LISP, которая вычисляет то же значение.

   (APPEND '(P Q R) '(X Y Z))
  -> (CONS 'P (APPEND '(Q R) '(X Y Z)))
  -> (CONS 'P (CONS 'Q (APPEND '(R) '(X Y Z))))
  -> (CONS 'P (CONS 'Q (CONS 'R (APPEND '() '(X Y Z)))))
  -> (CONS 'P (CONS 'Q (CONS 'R '(X Y Z))))
  -> (CONS 'P (CONS 'Q '(R X Y Z)))
  -> (CONS 'P '(Q R X Y Z))
  -> '(P Q R X Y Z)

Теперь рассмотрим еще одну полезную функцию LISP - MEMBER.

Эта функция осуществляет тест является ли данный элемент элементом списка, например:

   (MEMBER 'B '(A B C)) = T
   (MEMBER 'Z '(A B C)) = 0

Заметим, что если второй аргумент функции MEMBER является пустым списком, функция возвращает значение 0, поэтому ее определение может начинаться следующим образом:

   (DEFUN MEMBER (A L) (COND
      ((NULL L) NIL)
       ...

Этот образец программы аналогичен использованному функцией APPEND. Другим простым случаем, когда MEMBER принимает значение Т, является случай, когда А - первый элемент L:

   ...
   ((EQ A (CAR L)) T)
   ...

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

   (DEFUN MEMBER (A L) (COND
      ((NULL L) NIL)
      ((EQ A (CAR L)) T)
      (T (MEMBER A (CDR L)))))

и просмотр, аналогичный данному в описании функции APPEND, показывает:

   (MEMBER 'C '(A B C D))
-> (MEMBER 'C '(B C D))
-> (MEMBER 'C '(C D))
-> T
   (MEMBER 'C '(A B))
-> (MEMBER 'C '(B))
-> (MEMBER 'C NIL)
-> NIL

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

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

(b) Условие перехода при первоначальном определении функции может служить проверкой для очень простых слчаев;

(c) Достаточно сложные результаты могут быть получены без использования средств, более сложных чем обращения к функциям и условные структуры. Действительно, LISP не требует ни функций SETQ для корректировки переменных, ни каких-либо встроенных циклических конструкций;

(d) Поведение функции или группы функций может быть предсказано и оформлено в виде таблицы аргументов и результатов из каждого внутреннего обращения.

.цв

13. ОТЛАДКА ФУНКЦИЙ LISP.

.ов

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

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

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

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

Это позволяет перейти от начального представления ошибки (часто объемного и неудобного) к простому вызову одной функции, которая "ведет себя" неправильно. Для начала имеет смысл отказаться от детальной обратной трассировки и попытаться определить среди объектов трассировки некоторые имена функций, которые могут работать непрвильно. Затем эти функции можно исследовать по одной путем прямого вызова с клавиатуры, приписывая им сначала тривиальные аргументы, затем постепенно более сложные до тех пор, пока ошибка не проявит себя.

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

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

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

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

.цв

14. КОРРЕКТИРОВОЧНЫЕ ФУНКЦИИ LISP.

.ов

Наиболее распространенным способом корректировки программ LISP с ошибками является переопределение завершенных функций. Т.к. большинство функций LISP довольно короткие, то печатание их не является сложной задачей. Клавиши управления курсором и клавиша КОПИРОВАНИЕ весьма повышают скорость и надежность воспроизведения тех частей определения, которые не требуют изменения. Поэтому полезно знать, что определение функции - это почти то же самое, что объявление переменной. После определения функции, например FN, это определение может рассматриваться как значение переменной FN. Тогда это значение может быть напечатано на экране, если просто в ответ на подсказку 'Evaluate' ввести с клавиатуры имя FN.

Использование редактирования с помощью курсора допускается почти в каждом языке, имеющемся в обеспечении ОС ОНИКС. LISP также допускает другой стиль работы, который основывается на том факте, что программы LISP обрабатываются как структуры данных. Использование программ LISP для редактирования других кусков программы LISP называется структурным редактированием. Простейшим примером такого способа является коррекция ошибок при печати или синтаксиса. Представьте, что функция с именем STRING-TOGETHER была определена; предполагалось вызвать функцию APPEND, но случайно была допущена ошибка и пропущена буква так, что появилось слово 'APPND' Определение может быть скорректировано заменой APPND на APPEND в любом месте программы. Удобной функцией для осуществления этой замены является функция SUBST, определяемая следующим образом:

   (DEFUN SUBST (A B S) (COND
     ((EQ S B) A)
     ((ATOM S) S)
     (T (CONS (SUBST A B (CAR S))
              (SUBST A B (CDR S))))))

Эта функция осуществляет замену A на B в том месте, где она появляется в структуре данных S. Заметим, что это определение соответствует общему образцу, показанному в главе 12. Необходимая корректировка может быть достигнута следующим образом:

   (SETQ STRING-TOGETHER
      (SUBST 'APPEND 'APPND STRING-TOGETHER)).

Для осуществления более сложных случаев корректировки при написании программ полезно определить редактор LISP, который является версией функции SUBST, но эта функция меняет свои аргументы на основании последовательности директив, выдаваемых пользователем. В отличие от просмотра полного определения функции и внесения систематических изменений, данный редактор использует функцию READ для обнаружения требующихся изменений, функцию PRINT для показа произведенных изменний на экране, а также повторяющуюся структуру, аналогичную структуре функции SUBST, позволяющую создавать модифицированную версию ввода. Редактор такого типа представлен в главе 23.

Если памяти компьютера не достаточно для хранения такого структурного редактора, то некоторые из требуемых эффектов могут быть достигнуты прямым использованием функций CAR, CDR, RPLACA, RPLACD. Представьте, что в приведенном выше примере в определении функции SUBST слово CONS по ошибке напечатано как COND. По двум причинам невозможно устранить ошибку, записав:

   (SETQ SUBST (SUBST 'CONS 'COND SUBST))

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

Требуемое изменение можно сделать следующим образом:

   (SETQ W SUBST)

Таким образом устанавливается W, имеющая в качестве своего значения определение SUBST, изображаемая следующим образом:

           (LAMBDA (A B S) (COND ((EQ S
B) A) ((ATOM S) S) (T (COND (SUBST A B (
CAR S)) (SUBST A B (CDR S))))))

Слово LAMBDA показывает, что данная структура данных представляет определение функции. Последовательность применений функций CAR и CDR укажет W ссылку на компонент SUBST, подлежащий обработке:

   (SETQ W (CADDR (CADDR W)))

При этом W остается со значением:

   (T (COND (SUBST A B (CAR S)) (SUBST A B (CDRS))))

Запись (SETQ W (CADR W)) приведет к структуре, начинающейся с (COND. Если кто-то не доверяет прямому использованию CADDR CADDR как выбирающей функции, использованной здесь, то он может попробовать пошаговое выполнение (CAR W) и (CDR W) и затем установить W, используя ту функцию, где содержится требуемый компонент.

W указывает на необходимость изменения определения в функции SUBST, а вызов:

   (RPLACA W 'CONS')

изменяет указатель функции COND на указатель функции CONS. Проверка определения функции SUBST показывает, что корректировка постоянна и не распространяется на копию определения SUBST. Этот способ являтся достаточно простым, но эффективным.

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

.цв

15. ВВОД И ВЫВОД В LISP.

.ов

Для многих применений LISP требуются только самые элементарные средства ввода и вывода. Действительно, результаты, которые в других языках включают чтение и печать, очень часто в языке LISP могут быть достигнуты простым заданием аргумента функции LISP и и предоставлением возможности системе показать полученную структуру. Однако, в некоторых случаях бывает полезна связь с пользователем через программу. Самым простым способом осуществления этой связи является использование функций READ и PRINT, которые являются входными точками программы LISP на уровне пользователя для считывания команд и печати результатов. Если функция READ не содержит аргумента при обращении к ней, т.е. она записана как (READ), то она ожидает ввода выражения с клавиатуры и возвращает это выражение как свое значение. Как будет показано ниже, функция READ может содержать аргумент и использоваться для чтения выражения из файла, а не с клавиатуры. Функция READ игнорирует пробелы в начале строки и пустые строки и может работать с числовыми данными, идентификаторами и списками, заключенными в скобки. Если при этом обнаруживаются неуместные точки или правые скобки, то выдается сообщение об ошибке.

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

Существуют четыре тесно связанные между собой функции печати: PRIN, PRINC, PRINT, PRINTC. Все они выводят на экран свои аргументы и могут работать как с атомами, так и со списками. PRINT и PRINTC устанавливают перевод строки в конце вывода, а PRIN и PRINC - нет. PRIN и PRINT отличаются от PRINC и PRINTC тем, что имеют дело с атомами, содержащими знаки препинания. Различия будут объяснены для функций PRIN и PRINC, а PRINT и PRINTC аналогичны и отличаются лишь переводом строки в конце вывода.

Если PRINC изображает атом, то печатаются просто символы, составляющие имя атома. Например, переменные BLANK, LPAR, и RPAR имеют в качестве своих начальных значений идентификаторы с именами правая и левая скобка и пробел. Тогда (PRINC LPAR BLANK RPAR ) печатает текст'( )'. Заметим, что если текст необходимо хранить в файле, а затем считывать, то скобки будут указывать на списковую структуру, а пробел будет игнорироваться. Чтобы получить вывод на экран, который можно считать в память снова, используется PRIN. (PRIN LPAR BLANK RPAR) печатает текст '!(! !)', где перед каждым знаком препинания стоит восклицательный знак. Восклицательные знаки распознаются как знаки перехода и атом строится как если бы символы, слеующие за восклицательными знаками были буквами.

Подведем итог: PRINC используется для печати сообщений, тгда как функция PRIN удобна для случаев, где используются необычная запись атома. Тогда необходимо поставить на первое место такие необычные атомы или использовать ранее определенные переменные, такие как LPAR или необходимо предшествование небуквенных символов в именах атомов с восклицательными знаками. Например, переменные LPAR и RPAR могут быть установлены командами:

   (SETQ LPAR '!()
   (SETQ RPAR '!))

В ОС ОНИКС иногда бывает полезно производить серии контрольных кодов для подачи на драйвер экрана. В LISP это делается при использовании функции VDU. VDU берет числовые аргументы и посылает их операционной системе. Если они являются кодами для обычных символов, тогда эти символы появятся на экране, а если они являются контрольными кодами, они могут привести к изменению цвета экрана, возможности построения рисунков и печати символов двойного размера. Детальное описание кодов можно найти в руководстве пользователя по программированию на Бейсике в среде ОС ОНИКС.

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

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

Функция OPEN рассматривает имя файла как аргумент и располагает этот файл на диске. Эта функция возвращает значение, которое называется 'указателем файла'. Запись (OPEN имя T) открывает файл с именем для ввода и выдает сообщение, если файл с данным именем не существует. Функция (OPEN имя NIL) открывает файл для вывода или при необходимости создает его.

Кроме того, функции READ, GETCHAR, READLINE могут получить указатель файла ввода для считывания в качестве своих аргументов.

Функции WRITE и WRITEO являются версиями функций PRINT и PRIN, первым аргументом которых является указатель выходного файла. Остальные аргументы они записывают в этот файл. Предикат (указатель конца файла EOF) может проверить, располагается ли входной файл в конце этого предиката. (Заметьте, что EOF - это аббревиатура от END OF FILE, что означает 'конец файла'). Когда ввод или вывод завершается, необходимо вызвать (CLOSE указатель). Если этого не сделать, то некоторые из данных, посылаемых в выходной файл могут быть утеряны. Операционная система ОНИКС позволяет открыть одновременно ограниченное число файлов. Поэтому, необходимо закрывать файлы как можно раньше.

LISP позволяет производить операции, которые могут рассматриваться как средства чтения и печати, где файлы представляются списками символов. Функция EXPLODE рассматривает в качестве аргумента идентификатор и возвращает в качестве результата список составляющих идентификатор символов. Например, (EXPLODE 'EXPLODE) имеет значение (E X P L O D E). Функция IMPLODE работает в обратном порядке: получив список символов (например, при чтении одного символа с помощью функции GETCHAR), она образует из них идентификатор. В данной версии LISP функция EXPLODE работает только с идентификаторами, но не с числами и списками. Аналогично, IMPLODE создает идентификаторы независимо от последовательности символов, даже если эта последовательность содержит числа и скобки.

Для преобразования кодов символов в идентификаторы в LISP существует функция CHARACTER и ORDINAL. Функции CHAR возвращают число символов, которые используются функцией PRINC.

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

   (PRINC 'X=BLANK X)

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

   (DEFUN LETTER (CH)
      (SETQ CH (ORDINAL CH))
      (AND (GREATERP CH 64)
           (LESSP CH 91)))

Данный тест основан на том, что внутренние коды букв от A до Z заклюючены в пределах от 65 до 90. При использовании файлов можно избежать использования функций OPEN и CLOSE, прибегая к помощи специальных команд операционной системы SPOOL и EXEC - см. описание функции '*' и способа ее использования в приложении 1.

.сс

[an error occurred while processing this directive]