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

120

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

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

(let ((counter 0)) (defun reset ()

(setf counter 0)) (defun stamp ()

(setf counter (+ counter 1))))

Такая­ пара­ функций­ может­ исполь­зо­вать­ся­ для созда­ния­ меток­ време­­ ни. При каждом­ вызо­ве­ stamp мы полу­ча­ем­ число,­ на едини­цу­ большее,­ чем преды­ду­щее­. Вызы­­вая reset, мы обну­ля­ем­ счетчик:­

> (list (stamp) (stamp) (reset) (stamp)) (1 2 0 1)

Несо­мнен­но,­ можно­ сделать­ то же самое­ с исполь­зо­ва­ни­ем­ глобаль­ной­ пере­мен­ной,­ одна­ко­ послед­няя­ не защи­ще­на­ от воздей­ст­вий­ извне­.

В Common Lisp суще­ст­ву­ет­ встроен­ная­ функция­ complement, прини­маю­­ щая преди­кат­ и возвра­щаю­щая­ проти­во­по­лож­ный­ ему. Напри­мер:­

> (mapcar (complement #’oddp) ’(1 2 3 4 5 6))

(NIL T NIL T NIL T)

Благо­да­ря­ замы­ка­ни­ям­ напи­сать­ такую­ функцию­ очень просто:­

(defun our-complement (f) #’(lambda (&rest args)

(not (apply f args))))

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

6.6. Пример: строители функций

Язык Dylan извес­тен­ как гибрид­ Scheme и Common Lisp, исполь­зую­щий­ синтак­сис­ Pascal.° Он имеет­ большой­ набор­ функций,­ кото­рые­ возвра­­ щают­ функции­. Поми­мо­ complement, рассмот­рен­ной­ нами­ в преды­ду­щем­ разде­ле,­ Dylan включа­ет­ compose, disjoin, conjoin, curry, rcurry и alwa­ys­. На рис. 6.2 при­веде­ны­ реали­за­ции­ этих функций­ в Common Lisp, а на рис. 6.3 пока­за­ны­ экви­ва­лен­ты,­ следую­щие­ из этих опре­де­ле­ний­.

Первая­ из них, compose, прини­ма­ет­ одну­ или несколь­ко­ функций­ и воз­ враща­ет­ новую­ функцию,­ кото­рая­ приме­ня­ет­ их по очере­ди­. Таким­ об­ разом,­

(compose #’a #’b #’c)

6.6. Пример: строители функций

121

 

 

 

 

(defun compose (&rest fns)

 

 

(destructuring-bind (fn1 . rest) (reverse fns)

 

 

#’(lambda (&rest args)

 

 

(reduce #’(lambda (v f) (funcall f v))

 

 

rest

 

 

:initial-value (apply fn1 args)))))

 

 

(defun disjoin (fn &rest fns)

 

 

(if (null fns)

 

 

fn

 

 

(let ((disj (apply #’disjoin fns)))

 

 

#’(lambda (&rest args)

 

 

(or (apply fn args) (apply disj args))))))

 

 

(defun conjoin (fn &rest fns)

 

 

(if (null fns)

 

 

fn

 

 

(let ((conj (apply #’conjoin fns)))

 

 

#’(lambda (&rest args)

 

 

(and (apply fn args) (apply conj args))))))

 

 

(defun curry (fn &rest args)

 

 

#’(lambda (&rest args2)

 

 

(apply fn (append args args2))))

 

 

(defun rcurry (fn &rest args)

 

 

#’(lambda (&rest args2)

 

 

(apply fn (append args2 args))))

 

 

(defun always (x) #’(lambda (&rest args) x))

 

 

 

 

Рис. 6.2. Компо­нов­щи­ки­ функций­ из Dylan

cddr =

(compose #’cdr #’cdr)

nth =

(compose #’car #’nthcdr)

atom =

(compose #’not #’consp)

=

(rcurry #’typep ’atom)

<= =

(disjoin #’< #’=)

listp =

(disjoin #’null #’consp)

=(rcurry #’typep ’list) 1+ = (curry #’+ 1)

=(rcurry #’+ 1)

1- = (rcurry #’- 1)

mapcan = (compose (curry #’apply #’nconc) #’mapcar) complement = (curry #’compose #’not)

Рис. 6.3. Неко­то­рые­ экви­ва­лен­ты­

122

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

возвра­ща­ет­ функцию,­ экви­ва­лент­ную­ вызо­ву:­

#’(lambda (&rest args) (a (b (apply #’c args))))

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

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

> (mapcar (compose #’list #’round #’sqrt) ’(4 9 16 25))

((2) (3) (4) (5))

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

>

(mapcar

(disjoin #’integerp #’symbolp)

 

 

’(a "a" 2

3))

(T NIL T

T)

 

>

(mapcar

(conjoin #’integerp #’oddp)

 

 

’(a "a" 2

3))

(NIL NIL NIL T)

Рассмат­ри­вая­ преди­ка­ты­ как множе­ст­ва,­ можно­ сказать,­ что disjoin возвра­ща­ет­ объеди­не­ние­ множеств,­ а conjoin – их пере­се­че­ние­.

Функции­ curry и rcurry («right curry») имеют­ общую­ идею с опре­де­лен­­ ной в преды­ду­щем­ разде­ле­ make-adder. Они обе прини­ма­ют­ функцию­ и неко­то­рые­ ее аргу­мен­ты­ и возвра­ща­ют­ функцию,­ кото­рая­ ожида­ет­ полу­чить­ лишь остаю­щие­ся­ аргу­мен­ты­. Каждое­ из следую­щих­ выра­­ жений­ равно­силь­но­ (make-adder 3):

(curry #’+ 3) (rcurry #’+ 3)

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

>(funcall (curry #’- 3) 2)

1

вто время­ как для rcurry полу­чен­­ная функция­ будет­ вычи­тать­ задан­­ ное число­ из своего­ аргу­мен­та:­

>(funcall (rcurry #’- 3) 2)

-1

Нако­нец,­ always имеет­ свой аналог­ в Common Lisp – функцию­ constanly. Она прини­ма­ет­ любой­ аргу­мент­ и возвра­ща­ет­ функцию,­ всегда­ возвра­­

6.7. Динамический диапазон

123

щающую­ этот аргу­мент­. Как и identity, она может­ исполь­зо­вать­ся­ в тех случа­ях,­ когда­ требу­ет­ся­ пере­дать­ аргу­мент­-функцию­.

6.7. Динамический диапазон

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

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

(let ((x 10)) (defun foo ()

x))

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

> (let ((x 20)) (foo)) 10

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

(let ((x 10)) (defun foo ()

(declare (special x)) x))

то пере­мен­ная­ x больше­ не будет­ ссылать­ся­ на лекси­че­скую­ пере­мен­­ ную, суще­ст­вую­щую­ в контек­сте,­ в кото­ром­ функция­ была­ опре­де­ле­на,­ а будет­ ссылать­ся­ на любую­ дина­ми­че­скую­ пере­мен­ную­ x, кото­рая­ су­ щест­ву­ет­ в момент­ вызо­ва­ функции:­

> (let ((x 20))

(declare (special x))

1Под диапазоном (scope) следует понимать область видимости; соответственно лексическая область видимости и динамическая область видимости. –

Прим. науч. ред.

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