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

5

Управление

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

5.1. Блоки

В Common Lisp есть три основ­ных­ опера­то­ра­ для созда­ния­ блоков­ кода:­ progn, block и tagbody. С первым­ из них мы уже знако­мы­. Выра­же­ния,­ со­ ставляю­щие­ тело­ опера­то­ра­ progn, вычис­ля­ют­ся­ после­до­ва­тель­но,­ при этом возвра­ща­ет­ся­ значе­ние­ послед­не­го:­°

> (progn

(format t "a") (format t "b") (+ 11 12))

ab 23

Так как возвра­ща­ет­ся­ значе­ние­ лишь послед­не­го­ выра­же­ния,­ то ис­ пользо­ва­ние­ progn (или других­ опера­то­ров­ блоков)­ предпо­ла­га­ет­ нали­­ чие побоч­ных­ эффек­тов­.

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

> (block head

(format t "Here we go.")

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

(return-from head ’idea)

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

IDEA

Вызов­ return-from позво­ля­ет­ вам внезап­но,­ но изящ­но выйти­ из любо­го­ места­ в коде­. Второй­ аргу­мент­ return-from служит­ в каче­ст­ве­ возвра­щае­­ мого­ значе­ния­ из блока,­ имя кото­ро­го­ пере­да­ет­ся­ в первом­ аргу­мен­те­. Все выра­же­ния,­ нахо­дя­щие­ся­ в соот­вет­ст­вую­щем­ блоке­ после­ returnfrom, не вычис­ля­ют­ся­.

Кроме­ того,­ суще­ст­ву­ет­ специ­аль­ный­ макрос­ return, выпол­няю­щий­ вы­ ход из блока­ с именем­ nil:

> (block nil (return 27))

27

Многие­ опера­то­ры­ в Common Lisp, при­нимаю­щие­ на вход блок кода,­ не­ явно­ обора­чи­ва­ют­ его в block с именем­ nil. В част­но­сти,­ так дела­ют­ все итера­ци­он­ные­ конст­рук­ции:­

> (dolist (x ’(a b c d e)) (format t "~A " x)

(if (eql x ’c) (return ’done)))

A B C

DONE

Тело­ функции,­ созда­вае­мой­ defun, явля­ет­ся­ блоком­ с тем же именем,­ что и сама­ функция:­

(defun foo () (return-from foo 27))

Вне явно­го­ или неяв­но­го­ block ни return, ни return-from не рабо­та­ют­.

С помо­щью­ return-from можно­ напи­сать­ улучшен­ный­ вари­ант­ функции­ read-integer:

(defun read-integer (str) (let ((accum 0))

(dotimes (pos (length str))

(let ((i (digit-char-p (char str pos)))) (if i

(setf accum (+ (* accum 10) i)) (return-from read-integer nil))))

accum))

Преды­ду­щий­ вари­ант­ read-integer (стр. 83) выпол­нял­ провер­ку­ всех зна­ ков до построе­ния­ цело­го­ числа­. Теперь­ же два шага­ соб­ра­ны­ воеди­но,­ так как исполь­зо­ва­ние­ return-from позво­ля­ет­ прервать­ выпол­не­ние,­ ко­ гда мы встретим­ нечи­сло­вой­ знак.

Третий­ основ­ной­ конст­рук­тор­ блоков,­ tagbody, допус­ка­ет­ внутри­ себя­ исполь­зо­ва­ние­ опера­то­ра­ go. Атомы,­ встречаю­щие­ся­ внутри­ блока,­ рас­

5.2. Контекст

97

цени­ва­ют­ся­ как метки,­ по кото­рым­ может­ выпол­нять­ся­ пере­ход­. Ниже­ приве­ден­ доволь­но­ коря­вый­ код для печа­ти­ чисел­ от 1 до 10:

> (tagbody (setf x 0)

top

(setf x (+ x 1)) (format t "~A " x)

(if (< x 10) (go top))) 1 2 3 4 5 6 7 8 9 10

NIL

Опера­тор­ tagbody – один из тех, кото­рые­ при­меня­ют­ся­ для построе­ния­ других­ опера­то­ров,­ но, как прави­ло,­ не годят­ся­ для непо­сред­ст­вен­­ного­ исполь­зо­ва­ния­. Большин­ст­во­ итера­тив­ных­ опера­то­ров­ построе­ны­ по­ верх tagbody, поэто­му­ иногда­ (но не всегда)­ есть возмож­ноть­ исполь­зо­­ вать внутри­ них метки­ и пере­ход­ по ним с помо­щью­ go.

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

5.2.Контекст

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

> (let ((x 7) (y 2))

(format t "Number") (+ x y))

Number 9

Опера­то­ры­ типа­ let созда­ют­ новый лекси­че­ский­ контекст­ (lexical context). В нашем­ приме­ре­ лексический контекст­ имеет­ две новые­ пере­­ менные,­ кото­рые­ вне его стано­вят­ся­ неви­ди­мы­ми­.

Вызов­ let можно­ пони­мать­ как вызов­ функции­. В разделе 2.14 пока­за­­ но, что на функцию­ можно­ ссылать­ся­ не только­ по имени,­ но и по лямб­­ да-выра­же­нию­. Раз лямбда­-выра­же­ние­ экви­ва­лент­но­ имени­ функции,­ то мы можем­ исполь­зо­вать­ его вместо­ имени,­ ставя­ первым­ элемен­том­ выра­же­ния:­

> ((lambda (x) (+ x 1)) 3) 4

Пример­ с let, приве­ден­ный­ в нача­ле­ разде­ла,­ может­ быть пере­пи­сан­ как лямбда­-вызов:­

((lambda (x y) (format t "Number")

98

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

(+ x y))

7

2)

Любой­ вопрос,­ возни­каю­щий­ у вас при исполь­зо­ва­нии­ let, легко­ разре­­ шится,­ если­ постро­ить­ анало­гич­ную­ конст­рук­цию­ с помо­щью­ lambda­.°

Приве­дем­ при­мер ситуа­ции,­ кото­рую­ разъяс­ня­ет­ наша­ анало­гия­. Рас­ смотрим­ выра­же­ние­ let, в кото­ром­ одна­ из пере­мен­ных­ зави­сит­ от дру­ гой пере­мен­ной­ той же let-конст­рук­ции:­

(let ((x 2)

(y (+ x 1))) (+ x y))

Значе­ние­ x в (+ x 1) не опре­де­ле­но,­ и это видно­ из соот­вет­ст­вую­ще­го­ лямбда­-вызо­ва:­

((lambda (x y) (+ x y)) 2

(+ x 1))

Совер­шен­но­ очевид­но,­ что выра­же­ние­ (+ x 1) не может­ ссылать­ся­ на пе­ ремен­ную­ x в лямбда­-выра­же­нии­.

Но как быть, если­ мы хотим,­ чтобы­ одна­ из пере­мен­ных­ в let-вызо­ве­ за­ висе­ла­ от другой?­ Для этой цели­ суще­ст­ву­ет­ опера­тор­ let*:

> (let* ((x 1)

(y (+ x 1))) (+ x y))

3

Вызов­ let* полно­стью­ экви­ва­лен­тен­ серии­ вложен­ных­ let. Наш при­мер можно­ пере­пи­сать­ так:

(let ((x 1))

(let ((y (+ x 1))) (+ x y)))

Как в let, так и в let* исход­ные­ значе­ния­ пере­мен­ных ­– nil. Если­ имен­ но эти значе­ния­ вам нужны,­ можно­ не заклю­чать­ объяв­ле­ния­ в скобки:­

> (let (x y) (list x y))

(NIL NIL)

Макрос­ destructing-bind явля­ет­ся­ обобще­ни­ем­ let. Вместо­ отдель­ных­ пере­мен­ных­ он прини­ма­ет­ шаблон­ (pattern) – одну­ или несколь­ко­ пере­­ менных,­ распо­ло­жен­ных­ в виде­ дере­ва, ­– и связы­­вает­ эти пере­мен­ные­ с соот­вет­ст­вую­щи­ми­ частя­ми­ реаль­но­го­ дере­ва­. Напри­мер:­

> (destructing-bind (w (x y) . z) ’(a (b c) d e) (list w x y z))

(A B C (D E))

В случае­ несо­от­вет­ст­вия­ шабло­на­ дере­ву,­ пере­дан­но­му­ во втором­ аргу­­ менте,­ возни­ка­ет­ ошибка­.

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