Регистрация | Войти
Lisp — программируемый язык программирования
Предыдущая Оглавление Следующая

7. Макросы: Стандартные управляющие конструкции

В то время, как многие из идей, появившиеся в Лиспе, от условных выражений до сборки мусора, были добавлены в другие языки, есть одна особенность языка, которая продолжает делать Common Lisp стоящим особняком от всех, это его система макросов. К несчастью, слово "макрос" описывает множество вещей в компьютерных науках, к которым макросы Common Lisp имеют неявное и метафорическое отношение FIXME. Это приводит к бесконечным недопониманиям, когда адепты Лиспа пытаются объяснить другим, насколько макросы замечательны.1)

Чтобы понять макросы Лиспа, необходимо подойти к делу со свободной головой, без предубеждений, основанных на других вещах, которые также оказались названными словом "макросы". Итак, давайте начнём нашу тему с шага назад и обзора различных путей, которыми создаются расширения в языках.

Всем программистам должна быть привычна идея о том, что определение языка может включать стандартную библиотеку функций, которая строится на "ядре" языка – библиотеку, которая могла бы быть написана посредством языка любым программистом, если бы она не была определена как часть стандартной библиотеки. Стандартная библиотека языка Си, например, может быть написана почти полностью на переносимом Си. Аналогично, большая часть всё растущего набора классов и интерфейсов, которые поставляются в стандартном наборе Java Development Kit (JDK), написаны на "чистом" Java.

Одним из преимуществ определения языков в терминах "ядро плюс стандартная библиотека", это лёгкость в понимании и воплощении. Однако реальная выгода заключается в выразительности – многое из того, про что вы думаете как про "язык", на самом деле просто библиотека – язык легко расширять. Если в Си нет функции для той или иной необходимой вам задачи, вы можете её написать и теперь у вас есть слегка улучшенная версия Си. Точно так же в языках, таких как Java или Smalltalk, где почти все интересные части "языка" определены в терминах классов, определяя новый класс, вы расширяете язык, делая его более подходящим для написания программ, делающих то, что вам надо.

В то время, как Common Lisp поддерживает оба этих метода расширения языка, макросы дают Лиспу ещё один путь. Как было упомянуто кратко в Главе 4, каждый макрос определяет свой собственный синтаксис – определение того, как s-выражения, которые ему передаются, будут превращены в Лисп-формы. С помощью макросов, как части ядра языка, возможно создавать новые синтаксические управляющие конструкции, такие как WHEN, DOLIST и LOOP, а так же формы определений вроде DEFUN и DEFPARAMETER, как часть "стандартной библиотеки", вместо встраивания их в ядро. Это имеет свои последствия для реализации языка, но как программиста на Лисп, вас будет больше заботить то, что это даёт вам ещё один способ расширения языка, делая его языком, лучше подходящим для выражения решений ваших собственных программистских проблем.

В данный момент может показаться, что преимущества от наличия ещё одного пути расширения языка будет легко понять. Но по некоторой причине большое количество программистов, которые фактически не использовали макросы Лиспа и которые не задумываются о создании новых функциональных абстракций или определений новых иерархий классов для решения своих задач, панически боятся самой мысли о том, что они будут иметь возможность описания новых синтаксических абстракций. Наиболее общей причиной "макрофобии", похоже является плохой опыт от использования других "макросистем". Простой страх перед неизвестным несомненно так же играет определенную роль. Чтобы избежать макрофобических реакций, я буду постепенно знакомить вас с данным предметом, через обсуждение нескольких стандартных макросов и конструкций контроля, определённых в Common Lisp. Это те вещи, которые нужно было бы встроить в ядро языка, если бы в Лиспе не было макросов. Когда вы используете их, вам не надо беспокоиться, что они сделаны в виде макросов, но они представляют из себя хороший пример того, что вы можете сделать с помощью макросов.2) В следующей главе я покажу вам, как вы можете определять свои собственные макросы.

WHEN и UNLESS

Как вы уже видели, наиболее базовую форму условного выражения – если x, делай y; иначе делай z – представляет специальный оператор IF, который имеет следующую базовую форму:

(if condition then-form [else-form])

condition вычисляется и, если его значение не NIL, тогда then-form выполняется и полученное значение возвращается. Иначе выполняется else-form, если она есть, и её значение возвращается. Если condition даёт NIL и нет else-form, тогда IF возвращает NIL.

(if (> 2 3) "Yup" "Nope") ==> "Nope"
(if (> 2 3) "Yup")        ==> NIL
(if (> 3 2) "Yup" "Nope") ==> "Yup"

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

(if (spam-p current-message)
    (file-in-spam-folder current-message)
    (update-spam-database current-message)
)

потому что вызов update-spam-database будет принят за случай else, а не как часть ветви then. Другой специальный оператор PROGN, выполняет любое число форм по порядку и возвращает значение последней формы. Так что вы могли бы получить желаемое, записав всё следующим образом:

(if (spam-p current-message)
    (progn
      (file-in-spam-folder current-message)
      (update-spam-database current-message)
)
)

Это не так уж и ужасно. Однако, учитывая количество раз, когда вам придётся использовать эту идиому, не трудно представить себе, что через некоторое время это станет утомительно. "Почему", вы можете спросить у себя, "Лисп не предоставляет возможность выразить то, что на самом деле мне надо, скажем 'Если x верно, делай то, то и ещё вот это'?" Другими словами, через некоторое время, вы заметите повторяемость сочетания IF плюс PROGN и захотите как-то абстрагироваться от этих деталей, вместо того, чтобы каждый раз заново переписывать их.

Это как раз то, что предоставляют макросы. В данном случае, Коммон Лисп поставляется со стандартным макросом WHEN, с которым всё можно написать так:

(when (spam-p current-message)
  (file-in-spam-folder current-message)
  (update-spam-database current-message)
)

Но если бы он не был встроен в стандартную библиотеку, вы могли бы самостоятельно определить WHEN как макрос, используя запись с обратной кавычкой, которую я обсуждал в Главе 3:3)

(defmacro when (condition &rest body)
  `(if ,condition (progn ,@body))
)

Сопутствующим макросу WHEN является UNLESS, который оборачивает условие, выполняя формы из тела, только если условие ложно. Другими словами:

(defmacro unless (condition &rest body)
  `(if (not ,condition) (progn ,@body))
)

Конечно, это довольно тривиальные макросы. Тут нет никакой страшной чёрной магии; они просто абстрагируют некоторые детали языковой бухгалтерии, позволяя вам выражать свои намерения немного более ясно. Но их тривиальность имеет важное значение: так как система макросов встроена в язык, вы можете писать тривиальные макросы вроде WHEN и UNLESS, которые дают вам небольшую, но реальную выгоду в ясности, которая затем умножается в тысячу раз когда вы используете их. В Главах 24, 26 и 31 вы увидите, как макросы могут быть использованы для серьёзных вещей, создавая целый предметно-ориентированный (domain-specific), встроенный язык. Но сначала, давайте закончим наше обсуждение стандартных макросов управления.

COND

Ещё один случай, когда непосредственное IF выражение может оказаться ужасным, это когда у вас есть условное выражение с множественными ветвлениями: если A, делай X, иначе, если B, делай Y; иначе делай Z. Нет никакой логической проблемы в написании такой цепочки условных выражений с IF, но получится не очень красиво.

(if a
    (do-x)
    (if b
       (do-y)
       (do-z)
)
)

И это будет выглядеть ещё более ужасно, если вам понадобится включить множество форм для then случаев, привлекая несколько PROGN. Так что ничего удивительного, что Коммон Лисп предоставляет макрос для выражения условия с множеством ветвлений: COND. Вот базовый вид:

(cond
  (test-1 form*)
      .
      .
      .
  (test-N form*)
)

Каждый элемент в теле представляет одну ветвь условия и состоит из списка, содержащего форму условия и ноль или более форм для выполнения, если выбрана эта ветвь. Условия вычисляются в том порядке, в каком расположены ветви до тех пор, пока одно из них не даст истину. В этой точке, оставшиеся формы из ветви выполняются и значение последней формы ветви возвращается как результат работы всего COND. Если ветвь не содержит форм после условия, то возвращается само значение условия. По соглашению, ветвь представляющая последний случай else в цепочке if/else-if записывается с условием T. Подойдёт любое не-NIL значение, но T служит дорожным знаком при чтении кода. Таким образом вы можете записать предыдущее вложенное IF выражение, используя COND, вот так:

(cond (a (do-x))
      (b (do-y))
      (t (do-z))
)

AND, OR и NOT

При написании условий в IF, WHEN, UNLESS и COND формах, три оператора оказываются очень полезны, это булевы логические операторы AND, OR и NOT.

NOT - это функция, которая строго говоря не относится к этой главе, но она очень тесно связана с AND и OR. Она берёт свой аргумент и обращает его значение истинности, возвращая T, если аргумент NIL и NIL в ином случае.

AND и OR, однако являются макросами. Они представляют логические конъюнкцию и дизъюнкцию произвольного числа подформ и определены как макросы, так что они оптимальны в выполнении. Это значит, что они вычисляют ровно столько своих подформ, в порядке слева направо, сколько необходимо для конечного значения. То есть AND останавливается и возвращает NIL сразу же, как только одна из подформ выдаст NIL. Если все подформы выдают не-NIL результат, она возвращает значение последней подформы. OR, с другой стороны, останавливается, как только одна из подформ выдаст не-NIL и возвращает полученное значение. Если ни одна из подформ не выдаст истину, OR возвращает NIL. Вот несколько примеров:

(not nil)             ==> T
(not (= 1 1))         ==> NIL
(and (= 1 2) (= 3 3)) ==> NIL
(or (= 1 2) (= 3 3))  ==> T

Циклы

Управляющие конструкции — ещё один вид циклических конструкций в LISP. 4) Циклические средства в Коммон Лисп, в дополнение к мощности и гибкости, являются интересным уроком по программированию в стиле "получить всё и сразу", чему поспособствовали макросы.

Как оказалось, ни один из 25 специальных операторов Лиспа не поддерживает напрямую структуру циклов. Все циклические конструкции контроля в Лиспе - это макросы, построенные на двух специальных операторах, которые представляют собой примитивное goto средство.5) Как многие хорошие абстракции, синтаксические или нет, циклические макросы в Лиспе построены как набор слоёв абстракций, начиная с основы, которой являются те два специальных оператора.

В самом низу (оставляя в стороне специальные операторы) находится наиболее общая конструкция контроля DO. Хотя и очень мощный, DO страдает, как и многие абстракции общего назначения, от чрезмерности для простых ситуаций. Так что Лисп предоставляет два других макроса, DOLIST and DOTIMES, которые менее гибки, чем DO, но лучше поддерживают наиболее распространённые случаи цикла по элементам списка или цикла с подсчётом. Хотя конкретная реализация LISP может реализовать эти макросы как ей угодно, обычно они реализованы как макросы, которые раскрываются в соответствующий DO цикл. Таким образом DO предоставляет базовую структурную конструкцию цикла поверх нижележащих примитивов, представленных специальными операторами Коммон Лиспа, а DOLIST и DOTIMES представляют две лёгкие в использовании, хотя и менее общие конструкции. И, как вы увидите в следующей главе, вы можете строить свои собственные конструкции цикла поверх DO в ситуациях, где DOLIST и DOTIMES вам не подходят.

Наконец, макрос LOOP представляет собой полномасштабный мини-язык для выражения циклических конструкций на не Лиспо-, а англо-подобном (или, как минимум, Алголо-подобном) языке. Некоторые хакеры Лиспа любят LOOP; другие ненавидят его. Фанаты LOOP любят его за то, что он предоставляет краткий способ выразить определённые, обычно необходимые циклические конструкции. Его недоброжелатели не любят его, потому что он недостаточно похож на остальной Лисп. Однако, к какому бы лагерю вы не примкнули, это замечательный пример возможностей макросов добавлять новые конструкции в язык.

DOLIST и DOTIMES

Я начну с лёгких для использования DOLIST и DOTIMES макросов.

DOLIST проходит по всем элементам списка, выполняя тело цикла с переменной, содержащей последовательно элементы списка.6) Вот базовый скелет (оставляя некоторые эзотерические опции):

(dolist (var list-form)
  body-form*
)

Когда цикл стартует, list-form выполняется один раз, чтобы создать список. Затем тело цикла выполняется для каждого элемента в списке, с переменной var, содержащей значение элемента. Например:

CL-USER> (dolist (x '(1 2 3)) (print x))
1
2
3
NIL

Использованная таким образом, форма DOLIST, в целом, возвращает NIL.

Если вы хотите прервать цикл DOLIST до окончания списка, можете использовать RETURN.

CL-USER> (dolist (x '(1 2 3)) (print x) (if (evenp x) (return)))
1
2
NIL

DOTIMES - это конструкция цикла верхнего уровня для циклов с подсчётом. Основной вид более-менее такой же, как у DOLIST.

(dotimes (var count-form)
  body-form*
)

count-form должна выдать целое число. Каждый раз, в процессе цикла, var содержит последовательные целые от 0 до на единицу меньшего, чем то число. Например:

CL-USER> (dotimes (i 4) (print i))
0
1
2
3
NIL

Так же, как и с DOLIST, вы можете использовать RETURN, чтобы прервать цикл раньше.

Так как тела обоих DOLIST и DOTIMES циклов могут содержать любые типы выражений, вы так же можете делать циклы вложенными. Например, чтобы напечатать таблицу умножения от 1x1 = 1 до 20x20 = 400, вы можете написать такую пару вложенных циклов DOTIMES:

(dotimes (x 20)
  (dotimes (y 20)
    (format t "~3d " (* (1+ x) (1+ y)))
)

  (format t "~%")
)

DO

Хотя DOLIST и DOTIMES удобны и легки в использовании, они недостаточно гибки, чтобы использоваться для любых циклов. Например, что если вы захотите менять на каждом шаге несколько переменных параллельно? Или использовать произвольное выражение для проверки окончания цикла? Если ни DOLIST, ни DOTIMES не подходят для ваших целей, у вас всё ещё есть доступ к наиболее общему циклу DO.

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

(do (variable-definition*)
    (end-test-form result-form*)
  statement*
)

Каждое variable-definition (определение переменной) вводит переменную, которая будет в поле видимости тела цикла. Полная форма одного определения переменной, это список, содержащий три элемента.

(var init-form step-form)

init-form будет выполнена в начале цикла и полученное значение присвоено переменной var. Перед каждой последующей итерацией цикла, step-form будет выполнена и её значение присвоено var. Форма step-form необязательна; если её не будет, переменная останется с тем же значением от итерации к итерации, пока вы прямо не назначите ей новое значение в теле цикла. Так же, как и с присвоением переменным значений в LET, если форма init-form не задана, переменной присваивается NIL. Так же, как и в LET, вы можете использовать просто имя переменной, вместо списка, содержащего только имя.

В начале каждой итерации, после того, как все переменные цикла получили свои новые значения, выполняется форма end-test-form. До тех пор, пока она вычисляется в NIL, итерация происходит, выполняя statement по порядку.

Когда вычисление формы end-test-form выдаст истину, будет вычислена форма result-form и значение, полученное в результате, будет возвращено как значение всего выражения DO.

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

(do ((n 0 (1+ n))
     (cur 0 next)
     (next 1 (+ cur next))
)

    ((= 10 n) cur)
)

шаговые формы (1+ n), next, и (+ cur next) все вычисляются, использую старое значение n, cur и next. Только после вычисления всех шаговых форм, переменные получают свои новые значения. (Математически образованные читатели могут заметить, что это частично эффективный способ подсчёта одиннадцатого числа Фибоначчи.)

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

(do (variable-definition*)
    (end-test-form result-form*)
  statement*
)

Шесть скобок в этом шаблоне, это только те, которые требуются самим DO. Вам нужна одна пара для закрытия описания переменных, одна пара для закрытия теста окончания и результирующей формы и одна пара для закрытия всего выражения. Другие формы внутри DO могут потребовать своих собственных пар скобок – определения переменных, это обычно списки, например. К тому же форма проверки окончания, это зачастую функция. Однако скелет DO цикла всегда будет одним и тем же. Вот несколько примеров цикла DO со скелетом, отмеченным фигурными скобочками вместо обычных (они используются только для демонстрационных целей – это не часть стандартного синтаксиса CL. – прим. ред.): FIXME

{do {(i 0 (1+ i))}
   {(>= i 4)}
 (print i)}

Заметьте, что форма для результата пропущена. Это, однако, не особо распространённое использование DO, так как такой цикл гораздо проще написать используя DOTIMES.8)

(dotimes (i 4) (print i))

Другой пример, в котором отсутствует тело цикла для вычисления чисел Фибоначчи:

{do {(n 0 (1+ n))
    (cur 0 next)
    (next 1 (+ cur next))}
   {(= 10 n) cur}}

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

{do {}
   {(> (get-universal-time) *some-future-date*)}
 (format t "Waiting~%")
 (sleep 60)}

Всемогущий LOOP

Для простых случаев у вас есть DOLIST и DOTIMES. И если они не удовлетворяют вашим нуждам, вы можете вернуться к совершенно общему DO. Чего ещё можно хотеть?

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

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

(loop
  body-form*
)

Формы внутри тела выполняются каждый раз в процессе цикла, который будет длиться вечно, пока вы не используете RETURN, чтобы прервать его. Например, вы могли бы записать предыдущий DO цикл с простым LOOP.

(loop
  (when (> (get-universal-time) *some-future-date*)
    (return)
)

  (format t "Waiting~%")
  (sleep 60)
)

Расширенный LOOP – несколько иной зверь. Он отличается использованием специальных ключевых слов, которые представляют язык специального назначения для выражения различных конструкций циклов. Это ничего не значит, что не все Лисповоды любят язык расширенного LOOP. Как минимум один из создателей Коммон Лиспа ненавидит его. Критики LOOP жалуются, что его синтаксис совсем не лисповский. (другими словами, в нём недостаточно скобок). Любители LOOP замечают, что в этом то и весь смысл: сложные циклические конструкции достаточно тяжелы для восприятия и без заворачивания их в туманный синтаксис DO. Лучше, говорят они, иметь немного более наглядный синтаксис, который давал бы вам какие-то подсказки о том, что происходит.

Вот, например, характерный DO цикл, который собирает числа от 1 до 10 в список:

(do ((nums nil) (i 1 (1+ i)))
    ((> i 10) (nreverse nums))
  (push i nums)
)
==> (1 2 3 4 5 6 7 8 9 10)

У опытного Лиспера не будет никаких проблем понять этот код – это просто вопрос понимания основной формы DO цикла и распознания PUSH/NREVERSE идиомы для построения списка. Но это не совсем прозрачно. Версия с LOOP, с другой стороны, почти понятна как предложение на английском. (цикл по i от 1 до 10, собирая i)

(loop for i from 1 to 10 collecting i) ==> (1 2 3 4 5 6 7 8 9 10)

Далее, ещё несколько примеров простого использования LOOP. Вот сумма квадратов первых десяти чисел:

(loop for x from 1 to 10 summing (expt x 2)) ==> 385

Это – подсчёт числа гласных в строке:

(loop for x across "the quick brown fox jumps over the lazy dog"
      counting (find x "aeiou")
)
==> 11

Вот вычисление одиннадцатого числа Фибоначчи, аналогично использованному ранее циклу DO:

(loop for i below 10
      and a = 0 then b
      and b = 1 then (+ b a)
      finally (return  a)
)

Символы across, and, below, collecting, counting, finally, for, from, summing, then и to являются некоторыми из ключевых слов цикла, чьё присутствие обозначает, что перед нами расширенная версия LOOP. 9)

Я приберегу подробности о LOOP для Главы 22, однако сейчас стоит заметить, что это ещё один пример того, как макросы могут быть использованы для расширения основы языка. В то время как LOOP предоставляет свой собственный язык для выражения циклических конструкций, он никак не отрезает вас от остального Лиспа. Ключевые слова LOOP разбираются в соответствии с его грамматикой, но остальной код внутри LOOP, это обычный Лисп-код.

И так же стоит отметить ещё раз, что хотя макрос LOOP гораздо более сложный, чем WHEN или UNLESS, он просто ещё один макрос. Если бы он не был включён в стандартную библиотеку, вы могли бы сделать это сами или взять стороннюю библиотеку, которая это сделает.

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

1)Чтобы посмотреть на что это недопонимание похоже, найдите самую длинную ветвь в Usenet из кросспостов между comp.lang.lisp и другой comp.lang.* группой со словом macro в заголовке. Примерные диалоги выглядят так: Лиспер: "Лисп является лучшим из-за своих макросов!"; Другой: "Ты думаешь Лисп хорош из-за макросов?! Но макросы ужасны и являются злом; Лисп должно быть ужасен и является злом."
2)Другим важным классом языковых конструкций, которые сделаны на макросах, являются все конструкции-определения, такие как: DEFUN, DEFPARAMETER, DEFVAR и другие. В Главе 24 вы будете определять ваши собственные макросы-определения, которые позволят вам осмысленно писать код для чтения и записи двоичных данных.
3)Вы не можете на самом деле использовать это определение в Лиспе, потому что незаконно переопределять имена из пакета COMMON-LISP, откуда и берётся WHEN. Но если вы по-настоящему хотите попробовать написать этот макрос, вам надо изменить его имя, поменять на какое-то другое, например на my-when.
4)В дополнение к рекурсии. – прим. ред.
5)Специальными операторами, если вы хотите знать, являются TAGBODY and GO. Сейчас нет необходимости обсуждать их, но я рассмотрю их в Главе 20.
6)DOLIST похож на foreach из Перла или for из Питона. Джава добавила похожую конструкцию цикла вместе с "улучшенным" for циклом в Java 1.5, как часть JSR-201. Заметьте, какое отличие есть у макросов. Программист на Лиспе, который заметил общий шаблон в своём коде, может написать макрос, чтобы получить для себя абстракцию этого шаблона на уровне исходников. Программист на Джаве, который заметил такой же шаблон, должен убедить Sun, что этот частный шаблон стоит добавления в язык. Затем Sun должна опубликовать JSR и созвать представителей промышленности, "экспертную группу", чтобы всё утвердить. Этот процесс, по словам Sun, занимает примерно 18 месяцев. После этого все авторы компиляторов, должны обновить свои компиляторы для поддержки нового свойства. И даже когда любимый компилятор программиста на Джаве, поддержит новую версию Джавы, он возможно не сможет использовать новую особенность до тех пора, пока ему не позволят нарушить совместимость исходных кодов с предыдущей версией Джавы. Так неудобство, которое программист на Коммон Лисп может разрешить для себя за пять минут, может портить жизнь программисту на Джаве годами.
7)Вариант DO, DO*, назначает каждой переменной её значение перед вычислением шаговой формы для последующих переменных. За деталями обратитесь к руководству по выбранному вами Коммон Лиспу.
8)DOTIMES так же предпочтительней, потому что раскрытие макроса будет скорее всего включать декларации, которые позволяют компилятору генерировать более эффективный код.
9)Ключевые слова цикла, это несколько неудачный термин, так как они не являются ключевыми словами-символами. Фактически, LOOP не волнует пакет, из которого эти символы. Когда макрос LOOP разбирает своё тело, он считает, что любые, с соответствующим названием символы, эквивалентны. Вы можете даже использовать настоящие ключевые слова, если хотите – :for, :across и так далее – потому что они также имеют правильное название. Но большинство людей просто используют простые символы. Потому что ключевые слова в цикле используются только как синтаксические маркеры, и даже не важно, если они уже использованы для других целей – как имена для функций или переменных.
Предыдущая Оглавление Следующая
@2009-2013 lisper.ru