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

5.3. Условные выражения

99

5.3. Условные выражения

Наибо­лее­ простым­ услов­ным­ опера­то­ром­ можно­ считать­ if. Все осталь­­ ные яв­ляют­ся­ его расши­ре­ния­ми­. Опера­тор­ when явля­ет­ся­ упро­ще­ни­ем­ if. Его тело,­ кото­рое­ может­ состо­ять­ из несколь­ких­ выра­же­ний,­ вычис­­ ляет­ся­ лишь в случае­ истин­но­сти­ тесто­во­го­ выра­же­ния­. Таким­ обра­­зом,

(when (oddp that)

(format t "Hmm, that’s odd. ") (+ that 1))

экви­ва­лент­но­

(if (oddp that) (progn

(format t "Hmm, that’s odd. ") (+ that 1)))

Опера­тор­ unless дейст­ву­ет­ проти­во­по­лож­но­ when. Его тело­ вычис­ля­ет­ся­ лишь в случае­ ложно­сти­ тесто­во­го­ выра­же­ния­.

Мате­рью­ всех услов­ных­ выра­же­ний­ (в обоих­ смыслах)­ явля­ет­ся­ cond, ко­ торый­ предла­га­ет­ два новых­ преиму­ще­ст­ва:­ неог­ра­ни­чен­ное­ коли­че­ст­­ во услов­ных­ пере­хо­дов­ и неяв­ное­ исполь­зо­ва­ние­ progn в каждом­ из них. Его имеет­ смысл исполь­зо­вать­ в случае,­ когда­ третий­ аргу­мент­ if – дру­ гое услов­ное­ выра­же­ние­. Напри­мер,­ следую­щее­ опре­де­ле­ние­ our-member­:

(defun our-member (obj lst) (if (atom lst)

nil

(if (eql (car lst) obj) lst

(our-member obj (cdr lst)))))

может­ быть пере­пи­са­но­ с помо­щью­ cond:

(defun our-member (obj lst) (cond ((atom lst) nil)

((eql (car lst) obj) lst)

(t (our-member obj (cdr lst)))))

Вдейст­ви­тель­но­сти,­ реали­за­ция­ Common Lisp, веро­ят­но,­ оттранс­ли­ру­­ ет второй­ вари­ант­ в первый­.

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

100

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

> (cond (99)) 99

Если­ усло­вие­ имеет­ тесто­вое­ выра­же­ние­ t, оно будет­ истин­ным­ всегда­. Этот факт удобно­ исполь­зо­вать­ для указа­ния­ усло­вия,­ кото­рое­ будет­ выпол­нять­ся­ в случае,­ когда­ все осталь­ные­ не подо­шли­. По умолча­нию­ в таком­ случае­ возвра­ща­ет­ся­ nil, но счита­ет­ся­ плохим­ стилем­ исполь­­ зовать­ этот вари­ант­. (Подоб­ная­ пробле­ма­ пока­за­на­ на стр. 132.)

Если­ вы сравни­вае­те­ выра­же­ние­ с набо­ром­ посто­ян­ных,­ уже вычис­лен­­ ных объек­тов,­ правиль­нее­ будет­ исполь­зо­вать­ конст­рук­цию­ case. На­ пример,­ мы можем­ восполь­зо­вать­ся­ case для преоб­ра­зо­ва­ния­ имени­ ме­ сяца­ в его продол­жи­тель­ность­ в днях:

(defun month-length (mon) (case mon

((jan mar may jul aug oct dec) 31) ((apr jun sept nov) 30)

(feb (if (leap-year) 29 28)) (otherwise "unknown month")))

Конст­рук­ция­ case начи­на­ет­ся­ с выра­же­ния,­ значе­ние­ кото­ро­го­ будет­ сравни­вать­ся­ с предло­жен­ны­ми­ вари­ан­та­ми­. За ним следу­ют­ выра­же­­ ния, начи­наю­щие­ся­ либо­ с ключа,­ либо­ со списка­ ключей,­ за кото­рым­ следу­ет­ произ­воль­ное­ коли­че­ст­во­ выра­же­ний­. Сами­ ключи­ рассмат­ри­­ вают­ся­ как констан­ты­ и не вычис­ля­ют­ся­. Значе­ние­ ключа­ (ключей)­ сравни­ва­ет­ся­ с ориги­наль­ным­ объек­том­ с помо­щью­ eql. В случае­ совпа­­ дения­ вычис­ля­ют­ся­ следую­щие­ за ключом­ выра­же­ния­. Резуль­та­том­ вызо­ва­ case явля­ет­ся­ значе­ние­ послед­не­го­ вычис­лен­­ного­ выра­же­ния­.

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

> (case 99 (99)) NIL

Макрос­ typecase похож­ на case, но вместо­ ключей­ исполь­зу­ет­ специ­фи­­ като­ры­ типов,­ а иско­мое­ выра­же­ние­ сверя­ет­ся­ с ними­ через­ typep вме­ сто eql. (Пример­ с исполь­зо­ва­ни­ем­ typecase приве­ден­ на стр. 118.)

5.4. Итерации

Основ­ной­ итера­ци­он­ный­ опера­тор,­ do, был представ­лен­ в разделе 2.13. Посколь­ку­ do неяв­ным­ обра­зом­ исполь­зу­ет­ block и tagbody, внутри­ него­ можно­ исполь­зо­вать­ return, return-from и go.

В разде­ле­ 2.13 было­ указа­но,­ что первый­ аргу­мент­ do должен­ быть спи­ ском, содер­жа­щим­ описа­ния­ пере­мен­ных­ вида­

(variable initial update)

5.4. Итерации

101

Выра­же­ния­ initial и update не обяза­тель­ны­. Если­ выра­же­ние­ update пропу­ще­но,­ значе­ние­ соот­вет­ст­вую­щей­ пере­мен­ной­ не будет­ менять­ся­. Если­ пропу­ще­но­ initial, то пере­мен­ной­ присваи­ва­ет­ся­ перво­на­чаль­ное­ значе­ние­ nil.

В приме­ре­ на стр. 40:

(defun show-squares (start end) (do ((i start (+ i 1)))

((> i end) ’done)

(format t "~A ~A~%" i (* i i))))

для пере­мен­ной,­ созда­вае­мой­ do, зада­но­ выра­же­ние­ update. Практи­че­­ ски всегда­ в процес­се­ вы­полне­ния­ выра­же­ния­ do изме­ня­ет­ся­ хотя­ бы одна­ пере­мен­ная­.

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

> (let ((x ’a))

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

((> x 5))

(format t "(~A ~A) " x y))) (1 A) (2 1) (3 2) (4 3) (5 4)

NIL

На каждой­ итера­ции­ значе­ние­ x увели­чи­ва­ет­ся­ на 1, а y полу­ча­ет­ значе­­ ние x из преды­ду­щей­ итера­ции­.

Суще­ст­ву­ет­ также­ опера­тор­ do*, отно­ся­щий­ся­ к do, как let* к let. В нем началь­ные­ (initial) или после­дую­щие­ (update) выра­же­ния­ для вычис­ле­­ ния пере­мен­ных­ могут­ ссылать­ся­ на преды­ду­щие­ пере­мен­ные,­ и при этом они будут­ полу­чать­ их теку­щие­ значе­ния:­

> (do* ((x 1 (+ x 1)) (y x x))

((> x 5))

(format t "(~A ~A) " x y)) (1 1) (2 2) (3 3) (4 4) (5 5) NIL

Поми­мо­ do и do* суще­ст­ву­ют­ и более­ специа­ли­зи­ро­ван­ные­ опера­то­ры­. Итера­ция­ по списку­ выпол­ня­ет­ся­ с помо­щью­ dolist:

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

A B C D DONE

102

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

Суть оператора do

В книге­ «The Evolution of Lisp» Стил и Гэбри­эл­ выра­жа­ют­ суть do настоль­ко­ точно,­ что соот­вет­ст­вую­щий­ отры­вок­ из нее до­ стоин­ цити­ро­ва­ния:­

Отло­жим­ обсу­ж­де­ние­ синтак­си­са­ в сторо­ну­. Нуж­но признать,­ что цикл, выпол­няю­щий­ лишь итера­цию­ по одной­ пере­мен­ной,­ до­ вольно­ беспо­ле­зен­ в любом­ язы­ке програм­ми­ро­ва­ния­. Практи­че­­ ски всегда­ в таких­ случа­ях­ одна­ пере­мен­ная­ исполь­зу­ет­ся­ для полу­че­ния­ после­до­ва­тель­ных­ значе­ний,­ в то время­ как еще одна­ соби­ра­ет­ их вместе­. Если­ цикл лишь увели­чи­ва­ет­ значе­ние­ пере­­ менной­ на задан­ную­ вели­чи­ну,­ то каждое­ новое­ значе­ние­ долж­но уста­нав­ли­вать­ся­ благо­да­ря­ присваиванию­… или иным побоч­ным­ эффек­там­. Цикл do с несколь­ки­ми­ пере­мен­ны­ми­ выра­жа­ет­ внут­ реннюю­ симмет­рию­ гене­ра­ции­ и акку­му­ля­ции,­ позво­ляя­ выра­­ зить итера­цию­ без явных­ побоч­ных­ эффек­тов:­

(defun factorial (n) (do ((j n (- j 1))

(f 1 (* j f))) ((= j 0) f)))

И вправду,­ нет ниче­го­ необыч­но­го­ в циклах­ do с пустым­ телом,­ кото­рые­ вы­полня­ют­ всю рабо­ту­ в самих­ шагах­ итера­ции­.°

Третье выра­же­ние­ в началь­ном­ списке­ будет­ вычис­ле­но­ на вы­ходе­ из цикла­. По умолча­нию­ это nil.

Похо­жим­ обра­зом­ дейст­ву­ет­ dotimes, пробе­гаю­щий­ для задан­но­го­ числа­ n значе­ния­ от 0 до n–1:

> (dotimes (x 5 x) (format t "~A " x))

0 1 2 3 4

5

Как и в dolist, третье выра­же­ние­ в началь­ном­ списке­ не обяза­тель­но­ и по умолча­нию­ уста­нов­ле­но­ как nil. Обра­ти­те­ внима­ние,­ что это выра­­ жение­ может­ содер­жать­ саму­ итери­руе­мую­ пере­мен­ную­.

Функция mapc похо­жа­ на mapcar, одна­ко­ она не строит­ список­ из вычис­­ лен­ных значе­ний,­ поэто­му­ может­ исполь­зо­вать­ся­ лишь ради­ побоч­ных­ эффек­тов­. Она более­ гибка,­ чем dolist, пото­му­ что может­ вы­полнять­ итера­цию­ парал­лель­но­ сразу­ по несколь­ким­ спискам:­

> (mapc #’(lambda (x y)

(format t "~A ~A " x y)) ’(hip flip slip)

’(hop flop slop))

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