Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Pol_Grem_-_ANSI_Common_Lisp_High_tech_-_2012.pdf
Скачиваний:
17
Добавлен:
12.03.2016
Размер:
4.85 Mб
Скачать

14.4. Пакеты

243

(set-dispatch-macro-character #\# #\{ #’(lambda (stream char1 char2)

(let ((accum nil)

(pair (read-delimited-list #\} stream t))) (do ((i (car pair) (+ i 1)))

((> i (cadr pair))

(list ’quote (nreverse accum))) (push i accum)))))

Они зада­ют­ чтение­ выра­же­ния­ #{x y} в каче­ст­ве­ списка­ целых­ чисел­ от x до y включи­тель­но:­

> #{2 7} (2 3 4 5 6 7)

Функция­ read-delimited-list опре­де­ле­на­ специ­аль­но­ для таких­ макро­­ сов чтения­. Ее первым­ аргу­мен­том­ явля­ет­ся­ знак, кото­рый­ расце­ни­ва­­ ется­ как конец­ списка­. Чтобы­ знак } был опознан­ как разгра­ни­чи­тель,­ необ­хо­ди­мо­ предва­ри­тель­но­ сооб­щить­ об этом с помо­щью­ set-macro-cha­ racter.

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

14.4. Пакеты

Пакет ­– это объект­ Лиспа,­ сопос­тав­ляю­щий­ именам­ симво­лы­. Теку­щий­ пакет­ всегда­ хранит­ся­ в глобаль­ной­ пере­мен­ной­ *package*. При запус­ке­ Common Lisp стар­товым­ паке­том­ явля­ет­ся­ common-lisp-user, нефор­маль­­ но извест­ный­ также­ как пользо­ва­тель­­ский пакет­. Функция­ package­- name возвра­ща­ет­ имя теку­ще­го­ паке­та,­ а find-package возвра­ща­ет­ пакет­

сзадан­ным­ именем:­

>(package-name *package*) "COMMON-LISP-USER"

>(find-package "COMMON-LISP-USER") #<Package "COMMON-LISP-USER" 4CD15E>

Обычно­ символ­ интер­ни­ру­ет­ся­ в пакет,­ являю­щий­ся­ теку­щим­ на мо­ мент его чтения­. Функция­ symbol-package прини­ма­ет­ символ­ и возвра­­ щает­ пакет,­ в кото­рый­ он был интер­ни­ро­ван­.

> (symbol-package ’sym)

#<Package "COMMON-LISP-USER" 4CD15E>

Инте­рес­но,­ что это выра­же­ние­ верну­ло­ именно­ такое­ значе­ние,­ посколь­­ ку оно должно­ было­ быть счита­но­ перед­ вычис­ле­ни­ем,­ а само­ считы­ва­­ ние приве­ло­ к интер­ни­ро­ва­нию­ sym. Для после­дую­ще­го­ приме­не­ния­ присво­им­ sym неко­то­рое­ значе­ние:­

244

Глава 14. Более сложные вопросы

> (setf sym 99) 99

Теперь­ мы созда­дим­ новый­ пакет­ и пере­клю­чим­ся­ в него:­

> (setf *package* (make-package ’mine

:use ’(common-lisp)))

#<Package "MINE" 63390E>

В этот момент­ должна­ зазву­чать­ злове­щая­ музы­ка,­ ибо теперь­ мы в ином мире,­ где sym пере­стал­ быть тем, чем был раньше:­

MINE> sym

Error: SYM has no value.

Поче­му­ это произош­ло?­ Чуть раньше­ мы уста­но­ви­ли­ sym в 99, но сдела­­ ли это в другом­ паке­те,­ а значит,­ для друго­го­ симво­ла,­ неже­ли­ sym в па­ кете­ mine.1 Чтобы­ сослать­ся­ на исход­ный­ sym из друго­го­ паке­та,­ необ­хо­­ димо­ его предва­рить­ именем­ его родно­го­ паке­та­ и парой­ двоето­чий:­

MINE> common-lisp-user::sym 99

Итак, несколь­ко­ симво­лов­ с одина­ко­вы­ми­ имена­ми­ могут­ сосу­ще­ст­во­­ вать, если­ нахо­дят­ся­ в разных­ паке­тах­. Один символ­ нахо­дит­ся­ в паке­­ те common-lisp-user, другой ­– в mine, и это разные­ симво­лы­. В этом заклю­­ чает­ся­ весь смысл паке­тов­. Помес­тив­ свою програм­му­ в отдель­ный­ па­ кет, вы може­те­ не пере­жи­вать,­ что кто-то исполь­зу­ет­ выбран­ные­ вами­ имена­ функций­ и пере­мен­ных­ где-то еще. Даже­ если­ где-либо­ будет­ ис­ пользо­ва­но­ такое­ же имя, как у вас, это будет­ уже другой­ символ­.

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

Исполь­зо­ва­ние­ пары­ двоето­чий­ для обозна­че­ния­ симво­ла­ не из теку­ще­­ го паке­та­ счита­ет­ся­ плохим­ стилем­. Факти­че­ски­ вы нару­шае­те­ модуль­­ ность, кото­рая­ уста­нав­ли­ва­ет­ся­ паке­та­ми­. Если­ вам прихо­дит­ся­ ис­ пользо­вать­ двойное­ двоето­чие­ для ссылки­ на символ,­ это озна­ча­ет,­ что кто-то не хочет,­ чтобы­ вы на него­ ссыла­лись­.

Обычно­ следу­ет­ ссылать­ся­ лишь на экспор­ти­ро­ван­ные­ симво­лы­. Если­ мы вернем­ся­ назад­ в пользо­ва­тель­ский­ пакет­ (in-package уста­нав­ли­ва­ет­ *package*) и экспор­ти­ру­ем­ символ,­ интер­ни­ро­ван­ный­ в нем,

MINE> (in-package common-lisp-user) #<Package "COMMON-LISP-USER" 4CD15E> > (export ’bar)

T

1Неко­то­рые­ реали­за­ции­ Common Lisp печа­та­ют­ имя паке­та­ перед­ пригла­ше­­ нием­ toplevel, когда­ вы нахо­ди­тесь­ не в пользо­ва­тель­ском­ паке­те­.

14.4. Пакеты

245

> (setf bar 5) 5

то сдела­ем­ его види­мым­ и в других­ паке­тах­. Теперь,­ вернув­шись­ в па­ кет mine, мы сможем­ ссылать­ся­ на bar через­ одинар­ное­ двоето­чие,­ по­ скольку­ от­ныне­ это публич­но­ доступ­ное­ имя:

> (in-package mine) #<Package "MINE" 63390E> MINE> common-lisp-user:bar 5

Кроме­ того,­ мы можем­ сделать­ еще один шаг и импор­ти­ро­вать­ символ­ bar в теку­щий­ пакет,­ и тогда­ пакет­ mine будет­ делить­ его с пользо­ва­тель­­ ским паке­том:­

MINE> (import ’common-lisp-user:bar) T

MINE> bar 5

На импор­ти­ро­ван­ный­ символ­ можно­ ссылать­ся­ без како­го­-либо­ пре­ фикса­. Оба паке­та­ теперь­ делят­ этот символ,­ а значит,­ уже не может­ быть отдель­но­го­ симво­ла­ mine:bar.

А что произой­дет,­ если­ такой­ символ­ уже был? В таком­ случае­ импорт­ вызо­вет­ ошибку,­ подоб­ную­ той, кото­рую­ мы увидим,­ попы­тав­шись­ им­ порти­ро­вать­ sym:

MINE> (import ’common-lisp-user::sym)

Error: SYM is already present in MINE.

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

Другой­ способ­ полу­чить­ доступ­ к симво­лам­ друго­го­ паке­та ­– исполь­зо­­ вать сам пакет:­

MINE> (use-package ’common-lisp-user)

T

Теперь­ все симво­лы,­ экспор­ти­ро­ван­ные­ пользо­ва­тель­ским­ паке­том,­ при­ над­лежат­ mine. (Если­ sym экспор­ти­ро­вал­ся­ пользо­ва­тель­ским­ паке­том,­ то этот вызов­ приве­дет­ к ошибке­.)

common-lisp – это пакет,­ содер­жа­щий­ имена­ всех встроен­ных­ опера­то­ров­ и функций­. Так как мы пере­да­ли­ имя этого­ паке­та­ аргу­мен­ту­ :use вызо­­ ва make-package, кото­рый­ создал­ пакет­ mine, в нем будут­ види­мы­ все име­ на Common Lisp:

246 Глава 14. Более сложные вопросы

MINE> #’cons

#<Compiled-Function CONS 462A3E>

Как и компи­ля­ция,­ опера­ции­ с паке­та­ми­ редко­ выпол­ня­ют­ся­ непо­­ средст­вен­­но в toplevel. Гораз­до­ чаще­ их вызо­вы­ содер­жат­ся­ в файлах­ с исход­ным­ кодом­. Обычно­ доста­точ­но­ помес­тить­ в нача­ле­ файла­ def­ package и in-package, как на стр. 148.

Модуль­ность,­ созда­вае­мая­ паке­та­ми,­ несколь­ко­ своеоб­раз­на­. Она дает­ нам моду­ли­ не объек­тов,­ а имен. Каждый­ пакет,­ исполь­зую­щий­ commonlisp, имеет­ доступ­ к симво­лу­ cons, посколь­ку­ в паке­те­ common-lisp зада­­ на функция­ с таким­ именем­. Но как следст­вие­ этого­ и пере­мен­ная­ с именем­ cons будет­ види­ма­ во всех паке­тах,­ исполь­зую­щих­ common-lisp. Если­ паке­ты­ сбива­ют­ вас с толку,­ то основ­ная­ причи­на­ этого­ в том, что они осно­вы­ва­ют­ся­ не на объек­тах,­ а на их именах­.°

14.5. Loop

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

Если­ вы желае­те­ озна­ко­мить­ся­ с loop за один день, то для вас есть две ново­сти:­ хоро­шая­ и плохая­. Хоро­шая­ заклю­ча­ет­ся­ в том, что вы не оди­ ноки:­ мало­ кто в дейст­ви­тель­но­сти­ пони­ма­ет,­ как он рабо­та­ет­. Плохая­ же состо­ит­ в том, что вы, веро­ят­но,­ нико­гда­ этого­ не пой­мете,­ хотя­ бы пото­му,­ что стандарт­ ANSI факти­че­ски­ не предос­тав­ля­ет­ формаль­но­го­ описа­ния­ его пове­де­ния­.

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

Первое,­ что люди­ заме­ча­ют­ в макро­се­ loop, – это нали­чие­ у него­ син­ такси­са­. Его тело­ состо­ит­ не из подвы­ра­же­ний,­ а из предло­же­ний,­ ко­ торые­ не разде­ля­ют­ся­ скобка­ми­. Каждое­ предло­же­ние­ имеет­ свой син­ таксис­. В целом,­ loop-выра­же­ния­ напо­ми­на­ют­ Алгол­-подоб­ные­ язы­ки. Но есть одно­ серьез­ное­ отли­чие,­ сильно­ отда­ляю­щее­ loop от Алго­ла:­ по­ рядок,­ в кото­ром­ следу­ют­ предло­же­ния,­ весьма­ слабо­ опре­де­ля­ет­ поря­­ док, в кото­ром­ они будут­ испол­нять­ся­.

Вычис­ле­ние­ loop-выра­же­ния­ осуще­ст­в­ля­ет­ся­ в три этапа,­ причем­ каж­ дое предло­же­ние­ может­ вносить­ свой вклад более­ чем в один из них. Вот эти этапы:­

14.5. Loop

247

1.Пролог­. Вы­полня­ет­ся­ один раз за весь вызов­ loop; уста­нав­ли­ва­ет­ ис­ ходные­ значе­ния­ пере­мен­ных­.

2.Тело­. Вычис­ля­ет­ся­ на каждой­ итера­ции­. Начи­на­ет­ся­ с прове­рок­ на завер­ше­ние,­ следом­ за ними­ идут вычис­ляе­мые­ выра­же­ния,­ после­ кото­рых­ обнов­ля­ют­ся­ итери­руе­мые­ пере­мен­ные­.

3.Эпилог­. Вычис­ля­ет­ся­ по завер­ше­нии­ всех итера­ций;­ опре­де­ля­ет­ воз­ вращае­мое­ значе­ние­.

Рассмот­рим­ неко­то­рые­ при­меры­ loop-выра­же­ний­ и прики­нем,­ к каким­ этапам­ отно­сят­ся­ их части­. Один из простей­ших­ приме­ров:­

> (loop for x from 0 to 9 do (princ x))

0123456789 NIL

Данное­ выра­же­ние­ печа­та­ет­ целые­ числа­ от 0 до 9 и возвра­ща­ет­ nil. Первое­ предло­же­ние:­

for x from 0 to 9

вносит­ вклад в два первых­ этапа:­ уста­нав­ли­ва­ет­ значе­ние­ пере­мен­ной­ x

в0 в проло­ге,­ сравни­ва­ет­ с 9 в нача­ле­ тела­ и увели­чи­ва­ет­ пере­мен­ную­

вконце­. Второе­ предло­же­ние­

do (princ x)

добав­ля­ет­ код (princ-выра­же­ние)­ в тело­.

В более­ общем­ виде­ предло­же­ние­ с for опре­де­ля­ет­ форму­ уста­нов­ле­ния­ исход­но­го­ значе­ния­ и форму­ обнов­ле­ния­. Завер­ше­ние­ же может­ управ­ ляться­ чем-нибудь­ типа­ while или until:

> (loop for x = 8 then (/ x 2) until (< x 1)

do (princ x))

8421 NIL

С помо­щью­ and вы може­те­ созда­вать­ состав­ные­ конст­рук­ции­ с for, со­ держа­щие­ несколь­ко­ пере­мен­ных­ и обнов­ляю­щие­ их парал­лель­но:­

> (loop for x from 1 to 4 and y from 1 to 4

do (princ (list x y))) (1 1) (2 2) (3 3) (4 4)

NIL

А если­ предло­же­ний­ с for будет­ несколь­ко,­ то пере­мен­ные­ будут­ обнов­­ ляться­ пооче­ред­но­.

Еще одной­ ти­пичной­ зада­чей,­ решае­мой­ во время­ итера­ции,­ явля­ет­ся­ нако­п­ле­ние­ каких­-либо­ значе­ний­. Напри­мер:­

248

Глава 14. Более сложные вопросы

> (loop for x in ’(1 2 3 4) collect (1+ x))

(2 3 4 5)

При исполь­зо­ва­нии­ in вместо­ from в for-предло­же­нии­ пере­мен­ная­ по очере­ди­ прини­ма­ет­ значе­ния­ каждо­го­ из элемен­тов­ списка,­ а не после­­ дова­тель­ных­ целых­ чисел­.

Вприве­ден­ном­ приме­ре­ collect оказы­­вает­ влияние­ на все три этапа­.

Впроло­ге­ созда­ет­ся­ аноним­ный­ акку­му­ля­тор,­ имеющий­ значе­ние­ nil; в теле­ (1+ x) к акку­му­ля­то­ру­ прибав­ля­ет­ся­ 1, а в эпило­ге­ возвра­ща­ет­ся­ значе­ние­ акку­му­ля­то­ра­.

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

Наибо­лее­ часто­ loop исполь­зу­ет­ся­ для сбора­ резуль­та­тов­ вызо­ва­ неко­то­­ рой функции­ опре­де­лен­­ное коли­че­ст­во­ раз:

> (loop for x from 1 to 5 collect (random 10))

(3 8 6 5 0)

В данном­ приме­ре­ мы полу­чи­ли­ список­ из пяти­ случай­ных­ чисел­. Имен­но для этой цели­ мы уже опре­де­ля­ли­ функцию­ map-int (стр. 117). Тогда­ зачем­ же мы это дела­ли,­ если­ у нас есть loop? Но точно­ так же можно­ спросить:­ зачем­ нам нужен­ loop, когда­ у нас есть map-int?°

С помо­щью­ collect мож­но также­ акку­му­ли­ро­вать­ значе­ния­ в имено­ван­­ ную пере­мен­ную­. Следую­щая­ функция­ прини­ма­ет­ список­ чисел­ и воз­ враща­ет­ списки­ его четных­ и нечет­ных­ элемен­тов:­

(defun even/odd (ns) (loop for n in ns

if (evenp n)

collect n into evens else collect n into odds

finally (return (values evens odds))))

Предло­же­ние­ finally добав­ля­ет­ код в эпилог­. В данном­ случае­ оно опре­­ деля­ет­ возвра­щае­мое­ выра­же­ние­.

Предло­же­ние­ sum похо­же­ на collect, но нака­п­ли­ва­ет­ значе­ния­ в виде­ числа,­ а не списка­. Сумму­ всех чисел­ от 1 до n мы можем­ полу­чить­ так:

(defun sum (n)

(loop for x from 1 to n sum x))

Более­ деталь­ное­ рассмот­ре­ние­ loop содер­жит­ся­ в прило­же­нии­ D, начи­­ ная со стр. 331. Приве­дем­ несколь­ко­ приме­ров:­ рис. 14.1 содер­жит­ две итератив­ные­ функции­ из преды­ду­щих­ глав, а на рис. 14.2 пока­за­на­ анало­гич­ная­ их реали­за­ция­ с помо­щью­ loop.

14.5. Loop

249

(defun most (fn lst) (if (null lst)

(values nil nil)

(let* ((wins (car lst))

(max (funcall fn wins))) (dolist (obj (cdr lst))

(let ((score (funcall fn obj))) (when (> score max)

(setf wins obj

max score)))) (values wins max))))

(defun num-year (n) (if (< n 0)

(do* ((y (- yzero 1) (- y 1))

(d (- (year-days y)) (- d (year-days y)))) ((<= d n) (values y (- n d))))

(do* ((y yzero (+ y 1)) (prev 0 d)

(d (year-days y) (+ d (year-days y)))) ((> d n) (values y (- n prev))))))

Рис. 14.1. Итера­ция­ без loop

Одно­ loop-предло­же­ние­ может­ ссылать­ся­ на пере­мен­ные,­ уста­нав­ли­вае­­ мые другим­. Напри­мер,­ в опре­де­ле­нии­ even/odd предло­же­ние­ finally ссыла­ет­ся­ на пере­мен­ные,­ уста­нов­лен­­ные в двух преды­ду­щих­ collectпредло­же­ни­ях­. Отно­ше­ния­ между­ таки­ми­ пере­мен­ны­ми­ явля­ют­ся­ од­ ним из самых­ неяс­ных­ момен­тов­ при исполь­зо­ва­нии­ loop. Сравни­те­ два выра­же­ния:­

(loop for y = 0 then z for x from 1 to 5 sum 1 into z

finally (return (values y z)))

(loop for x from 1 to 5 for y = 0 then z sum 1 into z

finally (return (values y z)))

Они содер­жат­ всего­ четы­ре­ предло­же­ния­ и пото­му­ кажут­ся­ доволь­но­ просты­ми­. Возвра­ща­ют­ ли они одина­ко­вые­ значе­ния?­ Какие­ значе­ния­ они вернут?­ Попыт­ки­ найти­ ответ­ в стандар­те­ язы­ка окажут­ся­ тщет­ ными­. Каждое­ предло­же­ние­ внутри­ loop само­ по себе­ неза­тей­ли­во,­ но соче­тать­ся­ они могут­ доволь­но­ причуд­ли­вым­ и в конеч­ном­ итоге­ четко­ не задан­ным­ обра­зом­.

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

Тут вы можете оставить комментарий к выбранному абзацу или сообщить об ошибке.

Оставленные комментарии видны всем.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]