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

5.5. Множественные значения

103

HIP HOP FLIP FLOP SLIP SLOP (HIP FLIP SLIP)

Функция­ mapc всегда­ возвра­ща­ет­ свой второй­ аргу­мент­.

5.5. Множественные значения

Чтобы­ подчерк­нуть­ важность­ функцио­наль­но­го­ програм­ми­ро­ва­ния,­ часто­ гово­рят,­ что в Лиспе­ каждое­ выра­же­ние­ возвра­ща­ет­ значе­ние­. На самом­ деле,­ все несколь­ко­ сложнее­. В Common Lisp выра­же­ние­ может­ возвра­щать­ несколь­ко­ значе­ний­ или же не возвра­щать­ ни одно­го­. Мак­ сималь­ное­ их коли­че­ст­во­ может­ разли­чать­ся­ в разных­ реали­за­ци­ях,­ но долж­но быть не менее­ 19.

Для функции,­ кото­рая­ вычис­ля­ет­ несколь­ко­ значе­ний,­ эта возмож­ность­ позво­ля­ет­ возвра­щать­ их без заклю­че­ния­ в какую­-либо­ структу­ру­. На­ пример,­ встроен­ная­ функция­ get-decoded-time возвра­ща­ет­ теку­щее­ вре­ мя в виде­ девя­ти­ значе­ний:­ секун­ды,­ мину­ты,­ часы,­ день, месяц,­ год и еще два.

Множе­ст­вен­­ные значе­ния­ также­ позво­ля­ют­ функци­ям­ поис­ка­ разде­­ лять случаи,­ когда­ элемент­ не найден­ и когда­ найден­ элемент­ nil. Имен­ но поэто­му­ gethash возвра­ща­ет­ два значе­ния­. Второе­ значе­ние­ инфор­­ миру­ет­ об успеш­но­сти­ поис­ка,­ поэто­му­ мы можем­ хранить­ nil в хештабли­це­ точно­ так же, как и осталь­ные­ значе­ния­.

Множе­ст­вен­­ные значе­ния­ можно­ возвратить с помо­щью­ функции­ va­ lues. Она возвра­ща­ет­ в точно­сти­ те значе­ния,­ кото­рые­ были­ пере­да­ны­ ей в виде­ аргу­мен­тов:­

> (values ’a nil (+ 2 4)) A

NIL 6

Если­ вызов­ values явля­ет­ся­ послед­ним­ в теле­ функции,­ то эта функция­ возвра­ща­ет­ те же множе­ст­вен­­ные значе­ния,­ что и values. Множе­ст­вен­­ ные значе­ния­ могут­ пере­да­вать­ся­ через­ несколь­ко­ вызо­вов:­

> ((lambda () ((lambda () (values 1 2))))) 1 2

Одна­ко­ если­ в цепоч­ке­ пере­да­чи­ значе­ний­ что-либо­ прини­ма­ет­ лишь один аргу­мент,­ то все осталь­ные­ будут­ поте­ря­ны:­

> (let ((x (values 1 2))) x)

1

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

104

Глава 5. Управление

>(values)

>(let ((x (values))) x)

NIL

Чтобы­ полу­чить­ несколь­ко­ значе­ний,­ можно­ восполь­зо­вать­ся­ multiple- value-bind:

> (multiple-value-bind (x y z) (values 1 2 3) (list x y z))

(1 2 3)

> (multiple-value-bind (x y z) (values 1 2) (list x y z))

(1 2 NIL)

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

> (multiple-value-bind (s m h) (get-decoded-time) (format nil "~A:~A:~A" h m s))

"4:32:13"

Мож­но пере­дать­ какой­-либо­ функции­ множе­ст­вен­­ные значе­ния­ в каче­­ стве­ аргу­мен­тов­ с помо­щью­ multiple-value-call:

> (multiple-value-call #’+ (values 1 2 3)) 6

Кроме­ того,­ есть функция­ multiple-value-list:

> (multiple-value-list (values ’a ’b ’c)) (A B C)

кото­рая­ дейст­ву­ет­ так же, как multiple-value-call с функци­ей­ #’list

в каче­ст­ве­ перво­го­ аргу­мен­та­.

5.6.Прерывание выполнения

Чтобы­ выйти­ из блока­ в любой­ момент,­ доста­точ­но­ вызвать­ return. Одна­­ ко иногда­ может­ потре­бо­вать­ся­ нечто­ более­ ради­каль­ное,­ напри­мер­ пе­ реда­ча­ управле­ния­ через­ несколь­ко­ вызо­вов­ функций­. Для этой цели­ суще­ст­ву­ют­ специ­аль­ные­ опера­то­ры­ catch и throw. Конст­рук­ция­ catch исполь­зу­ет­ метку,­ кото­рой­ может­ быть любой­ объект­. За меткой­ следу­­ ет набор­ выра­же­ний­.

(defun super () (catch ’abort (sub)

(format t "We’ll never see this.")))

5.6. Прерывание выполнения

105

(defun sub ()

(throw ’abort 99))

Выра­же­ния­ после­ метки­ вычис­ля­ют­ся­ так же, как в progn. Вызов­ throw внутри­ любо­го­ из этих выра­же­ний­ приве­дет­ к немед­лен­­ному­ выхо­ду­ из catch:

> (super) 99

Опера­тор­ throw с задан­ной­ меткой­ пере­даст­ управле­ние­ соот­вет­ст­вую­­ щей конст­рук­ции­ catch (то есть завер­шит­ ее выпол­не­ние)­ через­ любое­ коли­че­ст­во­ catch с други­ми­ метка­ми­ (соот­вет­ст­вен­­но «убив» их). Если­ нет ожидаю­ще­го­ catch с требуе­мой­ меткой,­ throw вызо­вет­ ошибку­.

Вызов­ error также­ преры­­вает­ выпол­не­ние,­ но вместо­ пере­да­чи­ управле­­ ния вверх по дере­ву­ вызо­вов­ он пере­да­ет­ его обра­бот­чи­ку­ ошибок­ Лис­ па. В резуль­та­те­ вы, скорее­ всего,­ попа­де­те­ в отлад­чик­ (break loop). Вот что произой­дет­ в некой­ абст­ракт­ной­ реали­за­ции­ Common Lisp:

> (progn

(error "Oops! ")

(format t "After the error. ")) Error: Oops!

Options: :abort, :backtrace

>>

Более­ подроб­ную­ инфор­ма­цию­ об ошибках­ и усло­ви­ях­ можно­ найти­ в прило­же­нии­ A.

Иногда­ вам может­ пона­до­бить­ся­ некая­ гаран­тия­ защи­ты­ от ошибок­ и преры­ва­ний­ типа­ throw. Исполь­зуя­ unwind-protect, вы може­те­ быть уве­ рены,­ что в резуль­та­те­ подоб­ных­ явле­ний­ програм­ма­ не окажет­ся­ в про­ тиво­ре­чи­вом­ состоя­нии­. Конст­рук­ция­ unwind-protect прини­ма­ет­ любое­ коли­че­ст­во­ аргу­мен­тов­ и возвра­ща­ет­ значе­ние­ перво­го­. Осталь­ные­ выра­­ жения­ будут­ вычис­ле­ны,­ даже­ если­ вычис­ле­ние­ перво­го­ будет­ прерва­но­.

>(setf x 1)

1

>(catch ’abort (unwind-protect

(throw ’abort 99) (setf x 2)))

99 > x 2

Здесь, даже­ несмот­ря­ на то что throw пере­дал­ управле­ние­ catch, второе­ выра­же­ние­ было­ вычис­ле­но­ преж­де,­ чем был осуще­ст­в­лен­ вы­ход из catch. В случае­ когда­ перед­ прежде­вре­мен­ным­ завер­ше­ни­ем­ необ­хо­ди­­ ма какая­-то очист­ка­ или сброс, unwind-protect может­ быть очень полез­­ на. Один из таких­ приме­ров­ представ­лен­ на стр. 295.

106 Глава 5. Управление

5.7. Пример: арифметика над датами

В неко­то­рых­ прило­же­ни­ях­ полез­но­ иметь возмож­ность­ склады­вать­ и вычи­тать­ даты,­ напри­мер,­ чтобы­ сказать,­ что через­ 60 дней после­ 17 де­ кабря­ 1997 года­­ наста­нет­ 15 февра­ля­ 1998. В этом разде­ле­ мы созда­дим­ необ­хо­ди­мые­ для этого­ инст­ру­мен­ты­. Нам потре­бу­ет­ся­ конвер­ти­ро­вать­ даты­ в целые­ числа,­ при этом за ноль будет­ приня­та­ дата­ 1 янва­ря­ 2000 го­ ­да. Затем­ мы сможем­ рабо­тать­ с дата­ми­ как с целы­ми­ числа­ми,­ исполь­­ зуя функции­ + и –. Кроме­ того,­ необ­хо­ди­мо­ уметь конвер­ти­ро­вать­ целое­ число­ обрат­но­ в дату­.

Чтобы­ преоб­ра­зо­вать­ дату­ в целое­ число,­ будем­ склады­вать­ коли­че­ст­ва­ дней, соот­вет­ст­вую­щие­ различ­ным­ компо­нен­там­ даты­. Напри­мер,­ чис­ ло, соот­вет­ст­вую­щее­ 13 нояб­ря­ 2004 года,­­ полу­чим,­ склады­вая­ коли­че­­ ство­ дней до 2004 года,­­ коли­че­ст­во­ дней в году­ до нояб­ря­ и число­ 13.

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

> (setf mon ’(31 28 31 30 31 30 31 31 30 31 30 31)) (31 28 31 30 31 30 31 31 30 31 30 31)

Прове­дем­ неслож­ную­ провер­ку,­ сложив­ все эти числа:­

> (apply #’+ mon) 365

Теперь­ пере­вер­нем­ этот список­ и приме­ним­ с помо­щью­ maplist функцию­ + к после­до­ва­тель­но­сти­ хвостов­ (cdr) списка­. Мы полу­чим­ коли­че­ст­ва­ дней, прошед­шие­ до нача­ла­ каждо­го­ после­дую­ще­го­ меся­ца:­

> (setf nom (reverse mon))

(31 30 31 30 31 31 30 31 30 31 28 31) > (setf sums (maplist #’(lambda (x)

(apply #’+ x)) nom))

(365 334 304 273 243 212 181 151 90 59 31) > (reverse sums)

(31 59 90 120 151 181 212 243 273 304 334 365)

Эти цифры­ озна­ча­ют,­ что до нача­ла­ февра­ля­ пройдет­ 31 день, до нача­ла­ марта­ 59 и т. д.

На рис. 5.1. приве­ден­ код, вы­полняю­щий­ преоб­ра­зо­ва­ния­ дат. В нем по­ лучен­­ный нами­ список­ преоб­ра­зо­ван­ в вектор­.

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

5.7. Пример: арифметика над датами

107

(раздел 14.3). То, каким­ обра­зом­ мы созда­ли­ вектор­ month, свиде­тель­ст­ву­­ ет о том, что мы исполь­зу­ем­ Лисп даже­ во время­ напи­са­ния­ програм­мы­.

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

(defconstant month

#(0 31 59 90 120 151 181 212 243 273 304 334 365))

(defconstant yzero 2000)

(defun leap? (y)

(and (zerop (mod y 4))

(or (zerop (mod y 400))

(not (zerop (mod y 100))))))

(defun date->num (d m y)

(+ (- d 1) (month-num m y) (year-num y)))

(defun month-num (m y)

(+ (svref month ( - m 1))

(if (and (> m 2) (leap? y)) 1 0)))

(defun year-num (y) (let ((d 0))

(if (>= y yzero)

(dotimes (i (- y yzero) d)

(incf d (year-days (+ yzero i)))) (dotimes (i (- yzero y) (- d))

(incf d (year-days (+ y i)))))))

(defun year-days (y) (if (leap? y) 366 365))

Рис. 5.1. Опера­ции­ с дата­ми:­ преоб­ра­зо­ва­ние­ дат в целые­ числа­

Если­ вы вздумае­те­ исполь­зо­вать­ этот код для управле­ния­ маши­ной­ вре­ мени,­ то люди­ могут­ не согла­сить­ся­ с вами­ по пово­ду­ теку­щей­ даты,­ ес­ ли вы попа­де­те­ в прошлое­. По мере­ нако­п­ле­ния­ инфор­ма­ции­ о продол­­ житель­но­сти­ года­ люди­ вноси­ли­ изме­не­ния­ в кален­дарь­. В англо­го­во­ря­­ щих странах­ подоб­ная­ несты­ков­ка­ послед­ний­ раз уст­раня­лась­ в 1752 го­ ду,­ когда­ сразу­ после­ 2 сентяб­ря­ после­до­ва­ло­ 14 сентяб­ря­.°

Коли­че­ст­во­ дней в году­ зави­сит­ от того,­ явля­ет­ся­ ли он висо­кос­ным­. Год не явля­ет­ся­ висо­кос­ным,­ если­ он не кратен­ 4 либо­ кратен­ 100 и не кратен­ 400. 1904 год был висо­кос­ным,­ 1900 не был, а 1600 был.

Для опре­де­ле­ния­ дели­мо­сти­ суще­ст­ву­ет­ функци­я­ mod, возвра­щаю­щая­ оста­ток­ от деле­ния­ перво­го­ аргу­мен­та­ на второй:­

> (mod 23 5) 3

108

Глава 5. Управление

> (mod 25 5) 0

Одно­ число­ счита­ет­ся­ дели­мым­ на другое,­ если­ оста­ток­ от деле­ния­ на него­ равен­ нулю­. Функция­ leap? исполь­зу­ет­ этот подход­ для опре­де­ле­­ ния висо­кос­но­сти­ года:­

> (mapcar #’leap? ’(1904 1900 1600)) (T NIL T)

Дату­ в целое­ число­ преоб­ра­зу­ет­ функция­ date->num. Она возвра­ща­ет­ сумму­ числен­­ных представ­ле­ний­ каждо­го­ компо­нен­та­ даты­. Чтобы­ вы­ яснить,­ сколько­ дней прошло­ до нача­ла­ задан­но­го­ меся­ца,­ она исполь­­ зует­ функцию­ month-num, кото­рая­ обра­ща­ет­ся­ к соот­вет­ст­вую­ще­му­ ком­ понен­ту­ векто­ра­ month, добав­ляя­ к нему­ 1, если­ год висо­кос­ный­ и задан­­ ный месяц­ нахо­дит­ся­ после­ февра­ля­.

Чтобы­ найти­ коли­че­ст­во­ дней до насту­п­ле­ния­ задан­но­го­ года,­ date->num вызы­­вает­ year-num, кото­рая­ возвра­ща­ет­ числен­­ное представ­ле­ние­ 1 ян­ варя­ этого­ года­. Эта функция­ отсчи­ты­ва­ет­ годы­ до нуле­во­го­ года (то есть 2000).

На рис. 5.2 пока­зан­ код второй­ части­ програм­мы­. Функция­ num->date преоб­ра­зу­ет­ целые­ числа­ обрат­но­ в даты­. Вызы­­вая num-year, она полу­ча­­ ет год и коли­че­ст­во­ дней в остат­ке,­ по кото­ро­му­ num-month вычис­ля­ет­ месяц­ и день.

Как и year-num, num-year ведет­ отсчет­ от 2000 года,­­ склады­вая­ продол­жи­­ тельно­сти­ каждо­го­ года­ до тех пор, пока­ эта сумма­ не превы­сит­ задан­ное­ число­ n (или не будет­ равна­ ему). Если­ сумма,­ полу­чен­ная­ на какой­-то итера­ции,­ превы­сит­ его, то num-year возвра­ща­ет­ год, полу­чен­ный­ на пре­ дыду­щей­ итера­ции­. Для этого­ введе­на­ пере­мен­ная­ prev, кото­рая­ хранит­ число­ дней, полу­чен­­ное на преды­ду­щей­ итера­ции­.

Функция­ num-month и ее подпро­це­ду­ра­ nmon ведут­ себя­ обрат­но­ month-num. Они вычис­ля­ют­ пози­цию­ в векто­ре­ month по числен­­ному­ значе­нию,­ то­ гда как month-num вычис­ля­ет­ значе­ние­ исхо­дя­ из задан­но­го­ элемен­та­ векто­ра­.

Первые­ две функции­ на рис. 5.2 могут­ быть объеди­не­ны­ в одну­. Вместо­ вызо­ва­ отдель­ной­ функции­ num-year могла­ бы вызы­­вать непо­сред­ст­вен­­ но num-month. Одна­ко­ на этапе­ тести­ро­ва­ния­ и отлад­ки­ теку­щий­ вари­ант­ более­ удобен,­ и объеди­не­ние­ функций­ может­ стать следую­щим­ шагом­ после­ тести­ро­ва­ния­.

Функции­ date->num и num->date упро­ща­ют­ арифме­ти­ку­ дат.° С их помо­­ щью рабо­та­ет­ функция­ date+, позво­ляю­щая­ склады­вать­ и вычи­тать­ дни из задан­ной­ даты­. Теперь­ мы сможем­ вычис­лить­ дату­ по проше­ст­вии­ 60 дней с 17 декаб­ря­ 1997 года:­

> (multiple-value-list (date+ 17 12 1997 60)) (15 2 1998)

Мы полу­чи­ли­ 15 февра­ля­ 1998 года­.

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