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

6.4. Пример: утилиты

115

Ключе­вые­ слова­ и связан­ные­ с ними­ значе­ния­ могут­ соби­рать­ся­ с помо­­ щью &rest и в таком­ виде­ пере­да­вать­ся­ другим­ функци­ям,­ ожидаю­щим­ их. Напри­мер,­ мы могли­ бы опре­де­лить­ adjoin следую­щим­ обра­зом:­

(defun our-adjoin (obj lst &rest args) (if (apply #’member obj lst args)

lst

(cons obj lst)))

Так как adjoin прини­ма­ет­ те же пара­мет­ры­ по ключу, что и member, дос­ таточ­но­ просто­ собрать­ их в список­ и пере­дать­ member.

В разделе 5.2 был представ­лен­ макрос­ destructing-bind. В общем­ случае­ каждое­ подде­ре­во­ шабло­на,­ задан­но­го­ первым­ аргу­мен­том,­ может­ быть устрое­но­ так же комплекс­но,­ как и список­ аргу­мен­тов­ функции:­

> (destructing-bind ((&key w x) &rest y) ’((:w 3) a) (list w x y))

(3 NIL (A))

6.4. Пример: утилиты

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

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

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

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

Основ­ной­ секрет­ в напи­са­нии­ таких­ программ ­– подход­ снизу­-вверх, и програм­мы­ вовсе­ не обяза­ны­ быть объект­но­-ориен­ти­ро­ван­ны­ми,­

116

Глава 6. Функции

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

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

> (single? ’(a)) T

(defun single? (lst)

(and (consp lst) (null (cdr lst))))

(defun append1 (lst obj) (append lst (list obj)))

(defun map-int (fn n) (let ((acc nil))

(dotimes (i n)

(push (funcall fn i) acc)) (nreverse acc)))

(defun filter (fn lst) (let ((acc nil))

(dolist (x lst)

(let ((val (funcall fn x))) (if val (push val acc))))

(nreverse acc)))

(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))))

Рис. 6.1. Утили­ты­

6.4. Пример: утилиты

117

Вторая­ утили­та­ напо­ми­на­ет­ cons, одна­ко,­ в отли­чие­ от cons, она добав­­ ляет­ элемент­ в конец­ списка,­ а не в нача­ло:­

> (append1 ’(a b c) ’d) (A B C D)

Следую­щая­ утили­та,­ map-int, прини­ма­ет­ функцию­ и целое­ число­ n, воз­ вращая­ список,­ состоя­щий­ из значе­ний­ этой функции­ для всех целых­ чисел­ от 0 до n–1.

Такая­ функция­ может­ приго­дить­ся­ при тести­ро­ва­нии­ кода­. (Одно­ из преиму­ществ­ Лиспа­ заклю­ча­ет­ся­ в инте­рак­тив­но­сти­ разра­бот­ки,­ бла­ года­ря­ кото­рой­ вы може­те­ созда­вать­ одни­ функции­ для тести­ро­ва­ния­ других­.) Если­ нам вдруг пона­до­бит­ся­ список­ чисел­ от 0 до 9, мы сможем­ напи­сать:­

>(map-int #’identity 10) (0 1 2 3 4 5 6 7 8 9)

Аесли­ мы хотим­ полу­чить­ список­ из деся­ти­ случай­ных­ чисел­ между­ 0 и 99 (включи­тель­но),­ просто­ пропус­тим­ пара­метр­ лямбда­-выра­же­ния­ и напи­шем:­

>(map-int #’(lambda (x) (random 100))

10)

(85 40 73 64 28 21 67 5 32)

На приме­ре­ map-int пока­за­на­ распро­стра­нен­ная­ Лисп-идиома­ для по­ строения­ списков­. В ней созда­ет­ся­ акку­му­ля­тор­ acc, кото­рый­ исход­но­ содер­жит­ nil, и в него­ после­до­ва­тель­но­ добав­ля­ют­ся­ элемен­ты­ с помо­­ щью push. По завер­ше­нии­ возвра­ща­ет­ся­ пере­вер­ну­тый­ список­ acc.1

Эту же идиому­ мы видим­ и в filter. Функция­ filter при­нима­ет­ функ­ цию и список­ и возвра­ща­ет­ список­ резуль­та­тов­ при­мене­ния­ функции­ к элемен­там­ исход­но­го­ списка,­ причем­ возвра­щае­мый­ список­ состо­ит­ только­ из элемен­тов,­ отли­чаю­щих­ся­ от nil:

> (filter #’(lambda (x)

(and (evenp x) (+ x 10))) ’(1 2 3 4 5 6 7))

(12 14 16)

Функцию­ filter можно­ также­ рассмат­ри­вать­ как обобще­ние­ remove-if.

Послед­няя­ функция­ на рис. 6.1, most, возвра­ща­ет­ элемент­ списка,­ имею­ щий наивыс­ший­ рейтинг­ с точки­ зрения­ задан­ной­ рейтин­го­вой­ функ­ ции. Поми­мо­ этого­ элемен­та­ она также­ возвра­ща­ет­ соот­вет­ст­вую­щее­ ему значе­ние­.

> (most #’length ’((a b) (a b c) (a))) (A B C)

3

1В данном­ контек­сте­ nreverse (стр. 229) дела­ет­ то же, что и reverse, одна­ко­ она более­ эффек­тив­на­.

118

Глава 6. Функции

Если­ таких­ элемен­тов­ несколь­ко,­ most возвра­ща­ет­ первый­ из них.

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

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

6.5. Замыкания

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

(defun combiner (x)

(typecase

x

(number

#’+)

(list

#’append)

(t

#’list)))

С ее помо­щью­ мы сможем­ создать­ комби­ни­рую­щую­ функцию­ обще­го­ вида:­

(defun combine (&rest args) (apply (combiner (car args))

args))

Она при­нима­ет­ аргу­мен­ты­ любо­го­ типа­ и объеди­ня­ет­ их в соот­вет­ст­вии­ с типом­. (Для упро­ще­ния­ рассмат­ри­ва­ет­ся­ лишь тот случай,­ когда­ все аргу­мен­ты­ имеют­ один тип.)

>(combine 2 3)

5

>(combine ’(a b) ’(c d)) (A B C D)

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

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

6.5. Замыкания

119

этой пере­мен­ной­. При­ведем­ функцию,­ добав­ляю­щую­ 3 к своему­ аргу­­ менту:­

> (setf fn (let ((i 3))

#’(lambda (x) (+ x i)))) #<Interpreted-Function C0A51E>

> (funcall fn 2) 5

Если­ функция­ ссыла­ет­ся­ на опре­де­лен­ную­ вне нее пере­мен­ную,­ то та­ кая пере­мен­ная­ назы­ва­ет­ся­ сво­бодной­. Функцию,­ ссылаю­щую­ся­ на сво­ бодную­ лекси­че­скую­ пере­мен­ную,­ приня­то­ назы­вать­ замы­ка­ни­ем­ (clo­ sure)1. Свобод­ная­ пере­мен­ная­ будет­ суще­ст­во­вать­ до тех пор, пока­ дос­ тупна­ исполь­зую­щая­ ее функция­.

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

(defun add-to-list (num lst) (mapcar #’(lambda (x)

(+ x num)) lst))

Функция­ прини­ма­ет­ число­ и список­ и возвра­ща­ет­ новый­ список,­ в ко­ тором­ к каждо­му­ элемен­ту­ исход­но­го­ списка­ добав­ля­ет­ся­ задан­ное­ чис­ ло. Пере­мен­ная­ num в лямбда­-выра­же­нии­ явля­ет­ся­ свобод­ной,­ значит,­ функции­ mapcar пере­да­ет­ся­ замы­ка­ние­.

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

(defun make-adder (n) #’(lambda (x)

(+ x n)))

Прини­ма­ет­ся­ число­ n и возвра­ща­ет­ся­ функция,­ кото­рая­ склады­ва­ет­ число­ n с ее аргу­мен­том:­

>(setf add3 (make-adder 3)) #<Interpreted-Function C0EBF6>

>(funcall add3 2)

5

>(setf add27 (make-adder 27)) #<Interpreted-Function C0EE4E>

>(funcall add27 2)

29

1Назва­ние­ «замы­ка­ние»­ взято­ из ранних­ диалек­тов­ Лиспа­. Оно проис­хо­дит­ из мето­да,­ с помо­щью­ кото­ро­го­ замы­ка­ния­ реали­зо­вы­ва­лись­ в дина­ми­че­­ ском окру­же­нии­.

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