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

14

Более сложные вопросы

Эта глава­ необя­за­тель­на­. В ней описы­ва­ет­ся­ ряд эзоте­ри­че­ских­ особен­­ ностей­ язы­ка. Common Lisp похож­ на айсберг:­ огром­ная­ часть его воз­ можно­стей­ не видна­ для большин­ст­ва­ пользо­ва­те­лей­. Быть может,­ вам нико­гда­ не придет­ся­ опре­де­лять­ паке­ты­ или макро­сы­ чтения­ само­стоя­­ тельно,­ но знание­ подоб­ных­ момен­тов­ может­ сильно­ облег­чить­ жизнь.

14.1. Спецификаторы типов

В Common Lisp типы­ не явля­ют­ся­ объек­та­ми­. Так, к приме­ру,­ нет объ­ екта,­ соот­вет­ст­вую­ще­го­ типу­ integer. То, что мы полу­ча­ем­ при вызо­ве­ type-of и пере­да­ем­ в каче­ст­ве­ аргу­мен­та­ функции­ typep, явля­ет­ся­ не ти­ пом, а специ­фи­ка­то­ром­ типа­.

Специ­фи­ка­тор­ типа ­– это его имя. Простей­ши­ми­ специ­фи­ка­то­ра­ми­ ти­ пов явля­ют­ся­ симво­лы­ типа­ integer. Они форми­ру­ют­ иерар­хию­ типов­ во главе­ с типом­ t, к кото­ро­му­ принад­ле­жат­ все объек­ты­. Иерар­хия­ типов­ не явля­ет­ся­ дере­вом­. Напри­мер,­ от nil наверх­ ведут­ два пути:­ один че­ рез atom, а второй­ через­ list и sequence.

На самом­ деле,­ тип – это просто­ множе­ст­во­ объек­тов­. Это озна­ча­ет,­ что коли­че­ст­во­ типов,­ как и коли­че­ст­во­ множеств­ объек­тов,­ может­ быть беско­неч­ным­. Неко­то­рые­ ти­пы обозна­ча­ют­ся­ атомар­но,­ напри­мер­ inte­ ger опре­де­ля­ет­ множе­ст­во­ целых­ чисел­. Но мы можем­ также­ конст­руи­­ ровать­ состав­ные­ специ­фи­ка­то­ры­ типов,­ кото­рые­ ссыла­ют­ся­ на любые­ множе­ст­ва­ объек­тов­.

Напри­мер,­ пусть a и b – специ­фи­ка­то­ры­ неко­то­рых­ типов,­ тогда­ (or a b) обозна­ча­ет­ объеди­не­ние­ множеств­ объек­тов,­ соот­вет­ст­вую­щих­ типам­ a и b. Таким­ обра­зом,­ объект­ будет­ при­надле­жать­ типу­ (or a b), если­ он принад­ле­жит­ типу­ a или типу­ b.

240

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

Если­ бы circular? была­ функци­ей,­ возвра­щаю­щей­ исти­ну­ для цикли­че­­ ских по хвосту­ списков,­ то для опре­де­ле­ния­ набо­ра­ правиль­ных­ после­до­­ ватель­но­стей­ мож­но было­ бы исполь­зо­вать­ следую­щий­ специ­фи­ка­тор:­1

(or vector (and list (not (satisfies circular?))))

Неко­то­рые­ атомар­ные­ специ­фи­ка­то­ры­ типов­ могут­ встречать­ся­ и в со­ ставных­ типах­. Напри­мер,­ следую­щий­ специ­фи­ка­тор­ соот­вет­ст­ву­ет­ на­ бору­ целых­ чисел­ от 1 до 100 включи­тель­но:­

(integer 1 100)

Втаких­ случа­ях­ гово­рят,­ что специ­фи­ка­тор­ опре­де­ля­ет­ конеч­ный­ тип.

Всостав­ном­ специ­фи­ка­то­ре­ типа­ неко­то­рые­ поля­ могут­ оста­вать­ся­ не­ опре­де­лен­­ными­. Такое­ поле­ поме­ча­ет­ся­ * вместо­ пара­мет­ра­. Так,

(simple-array fixnum (* *))

описы­ва­ет­ набор­ двумер­ных­ простых­ масси­вов,­ специа­ли­зи­ро­ван­ных­ для fixnum, а

(simple-array fixnum *)

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

(simple-array fixnum)

Если­ состав­ной­ тип не содер­жит­ аргу­мен­тов,­ то он экви­ва­лен­тен­ ато­ марно­му­. Таким­ обра­зом,­ тип simple-array описы­ва­ет­ множе­ст­во­ всех простых­ масси­вов­.

Чтобы­ посто­ян­но­ не повто­рять­ громозд­кие­ опре­де­ле­ния­ состав­ных­ спе­ цифи­ка­то­ров,­ кото­рые­ вы часто­ исполь­зуе­те,­ для них можно­ опре­де­­ лить аббре­виа­ту­ры­ с помо­щью­ deftype. Его исполь­зо­ва­ние­ напо­ми­на­ет­ defmacro, только­ он раскры­ва­ет­ся­ не в выра­же­ние,­ а в специ­фи­ка­тор­ ти­ па. С помо­щью­ следую­ще­го­ опре­де­ле­ния:­

(deftype proseq ()

’(or vector (and list (not (satisfies circular?)))))

мы созда­ем­ новый­ атомар­ный­ специ­фи­ка­тор­ proseq:

> (typep #(1 2) ’proseq) T

Если­ вы опре­де­ли­те­ состав­ной­ специ­фи­ка­тор­ с аргу­мен­та­ми,­ то они не будут­ вычис­лять­ся,­ как и в случае­ defmacro. Так,

(deftype multiple-of (n)

‘(and integer (satisfies (lambda (x)

(zerop (mod x ,n))))))

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

14.2. Бинарные потоки

241

опре­де­ля­ет­ (multiple-of n) как специ­фи­ка­тор­ для всех множи­те­лей­ n:

> (typep 12 ’(multiple-of 4)) T

Специ­фи­ка­то­ры­ типов­ интер­пре­ти­ру­ют­ся­ и поэто­му­ рабо­та­ют­ медлен­­ но, так что в общем­ случае­ для подоб­ных­ прове­рок­ лучше­ опре­де­лить­ функцию­.

14.2.Бинарные потоки

Вглаве 7 поми­мо­ пото­ков­ знаков­ упоми­на­лись­ также­ и бинар­ные­ пото­­ ки. Бинар­ный­ поток ­– это источ­ник­ и/или полу­ча­тель­ не знаков,­ а це­ лых чисел­. Для созда­ния­ бинар­но­го­ пото­ка­ нужно­ задать­ подтип­ integer (чаще­ всего­ это unsigned-byte) как значе­ние­ пара­мет­ра­ :element-type при откры­тии­ пото­ка­.

Для рабо­ты­ с бинар­ны­ми­ пото­ка­ми­ имеют­ся­ лишь две функции:­ readbyte и write-byte. Функцию­ для копи­ро­ва­ния­ содер­жи­мо­го­ файла­ мож­ но опре­де­лить­ следую­щим­ обра­зом:­

(defun copy-file (from to)

(with-open-file (in from :direction :input :element-type ’unsigned-byte)

(with-open-file (out to :direction :output :element-type ’unsigned-byte)

(do ((i (read-byte in nil -1) (read-byte in nil -1)))

((minusp i)) (declare (fixnum i)) (write-byte i out)))))

Зада­вая­ тип unsigned-byte в каче­ст­ве­ аргу­мен­та­ :element-type, вы указы­­ ваете­ опера­ци­он­ной­ систе­ме­ тип элемен­тов,­ для кото­рых­ будет­ произ­во­­ диться­ ввод-вывод­. При жела­нии­ рабо­тать­ конкрет­но,­ к приме­ру,­ с 7-бит­ ными­ числа­ми,­ можно­ пере­дать­ в :element-type

(unsigned-byte 7)

14.3.Макросы чтения

Вразделе 7.5 была­ представ­ле­на­ концеп­ция­ макро­зна­ков ­– знаков,­ имеющих­ особое­ значе­ние­ для read. С каждым­ из них связа­на­ функция,­ указы­­вающая­ read, что делать­ при встрече­ этого­ знака­. Вы може­те­ из­ менять­ опре­де­ле­ния­ функций,­ связан­ных­ с уже суще­ст­вую­щи­ми­ мак­ розна­ка­ми,­ а также­ опре­де­лять­ собст­вен­­ные макро­сы­ чтения­.

Функция­ set-macro-character предос­тав­ля­ет­ один из спосо­бов­ опре­де­ле­­ ния макро­сов­ чтения­. Она прини­ма­ет­ знак и функцию,­ вызы­­ваемую­ read при встрече­ этого­ знака­.

242

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

Одним­ из старей­ших­ макро­сов­ чтения­ в Лиспе­ явля­ет­ся­ ’, quote. Мы могли­ бы опре­де­лить­ его следую­щим­ обра­зом:­

(set-macro-character #\’ #’(lambda (stream char)

(list (quote quote) (read stream t nil t))))

Когда­ read встреча­ет­ ’ в обычном­ контек­сте,­ она возвра­ща­ет­ резуль­тат­ вызо­ва­ данной­ функции­ для теку­ще­го­ состоя­ния­ пото­ка­ и знака­. (В дан­ ном случае­ функция­ игно­ри­ру­ет­ свой второй­ аргу­мент,­ так как он всегда­ явля­ет­ся­ кавыч­кой­.) Поэто­му­ когда­ read увидит­ ’a, она вернет­ (quote a).

Теперь­ можно­ понять­ смысл послед­не­го­ аргу­мен­та­ read. Он сооб­ща­ет,­ произ­ве­ден­ ли вызов­ read внутри­ друго­го­ вызо­ва­ read. Следую­щие­ аргу­­ менты­ read будут­ одина­ко­вы­ми­ практи­че­ски­ для всех макро­сов­ чтения:­ первый­ аргу­мент ­– поток;­ второй­ аргу­мент,­ t, сооб­ща­ет,­ что при дости­­ жении­ конца­ файла­ read будет­ сигна­ли­зи­ро­вать­ об ошибке;­ третий­ ар­ гумент­ зада­ет­ значе­ние,­ возвра­щае­мое­ вместо­ данной­ ошибки­ в случае­ истин­но­сти­ второ­го­ аргу­мен­та­ (в нашем­ приме­ре­ он не приме­ня­ет­ся);­ четвер­тый­ аргу­мент,­ t, сооб­ща­ет­ о рекур­сив­но­сти­ вызо­ва­ read.

Вы може­те­ опре­де­лять­ (с помо­щью­ make-dispatch-macro-character) свои собст­вен­­ные диспет­че­ри­зи­рую­щие­ макро­зна­ки,­ но раз # уже опре­де­лен­ для этой цели,­ можно­ пользо­вать­ся­ и им. Шесть комби­на­ций,­ начи­наю­­ щихся­ с #, явным­ обра­зом­ заре­зер­ви­ро­ва­ны­ для ваших­ нужд: #!, #?, #[,

#], #{ и #}.

Новую­ комби­на­цию­ с управляю­щим­ макро­зна­ком­ можно­ опре­де­лить­

спомо­щью­ вызо­ва­ функции­ set-dispatch-macro-character, кото­рая­ схожа­

сset-macro-character, но set-dispatch-macro-character прини­ма­ет­ не один, а два знака­. Следую­щий­ код опре­де­ля­ет­ #? в каче­ст­ве­ макро­са­ чтения,­ возвра­щаю­ще­го­ список­ целых­ чисел:­

(setf-dispatch-macro-character #\# #\? #’(lambda (stream char1 char2)

(list ’quote

(let ((lst nil))

(dotimes (i (+ (read stream t nil t) 1)) (push i lst))

(nreverse lst)))))

Теперь­ #?n будет­ прочи­тан­ как список­ всех целых­ чисел­ от 0 до n. На­ пример:­

> #?7 (0 1 2 3 4 5 6 7)

Второе­ место­ по распро­стра­нен­но­сти­ после­ простых­ макро­зна­ков­ зани­­ мают­ знаки­-огра­ни­чи­те­ли­ списков­. Еще одна­ комби­на­ция,­ заре­зер­ви­­ рован­ная­ для пользо­ва­те­ля, ­– #{. Вот пример­ опре­де­ле­ния­ более­ про­ двину­тых­ скобок:­

(set-macro-character #\} (get-macro-character #\)))

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