-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathchapter15.html
236 lines (214 loc) · 12.9 KB
/
chapter15.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html><head>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Successful Lisp - Chapter 15</title>
</head>
<body bgcolor="white" text="black">
<h1>Глава 15 - Замыкания</h1>
<P>
В этой главе мы подробно остановимся на обсуждении вопроса о замыканиях,
которое мы начали в <a href="chapter11.html#closures">Главе 11</a>.
Мы снова увидим, как (и почему) замыкания захватывают свободные переменные
для использования в других контекстах выполнения, а затем мы увидим некоторые
практические приложения. Мы закончим эту главу, рассмотрев функции, которые
возвращают функции.
<h2>Является ли это функцией с временем жизни, или временем жизни функции?</h2>
<p>
Common Lisp не раскрывает замыкания как таковые. Напомним из
<a href="chapter11.html#closures">Главы 11</a>, что замыкание -
это набор замкнутых переменных, сохраняемых функцией. (Замкнутая переменная - это
переменная, найденная "свободной "в функции; она "захватывается" замыканием.
Мы видели некоторые примеры этого в <a href="chapter11.html#closures">Главе 11</a>;
мы рассмотрим детали в следующем разделе, Если вы забыли.) По этой причине
программисты Lisp склонны называть "функцию, имеющую замкнутые переменные "просто"
замыкаием.- Или, может быть, они называют его так, потому что это экономит
им девять слогов.
<p>
Замыкание должно быть связано с функцией, поэтому оно должно иметь тот же
срок жизни - или экстент/протяженность - что и функция. Но все замкнутые переменные
идут вместе - замкнутая переменная имеет тот же экстент(время жизни), что и замыкание.
Это означает, что вы можете замкнуть лексическую переменную, которая обычно имеет
лексический экстент, и дать этой переменной неопределенный экстент. Это очень
полезная техника, как мы вскоре увидим.
<h2>Как определить свободную переменную и что с ней делать.</h2>
<p>
Переменная свободна в пределах функции (или в пределах любой формы,
если уж на то пошло), если нет привязки её имени в лексической области
- текстуальной границы функции. Вхождение привязки -
это вхождение имени, которое (в соответствии с определением формы,
включающей имя) связывает хранилище с именем.
<p>
Свободная переменная должна быть найдена в одном из двух мест. Либо функция
текстуально завернута в форму, которая обеспечивает связывание вхождения переменной,
либо переменная является специальной(<code>special</code>) (смотрите
<a href="chapter08.html#special">Главу 8</a> ) и содержится в глобальном окружении.
Если свободная переменная не найдена ни в одном из этих двух мест, она не считается
связанной (т. е. не имеет хранилища, связанного с именем) и вызовет ошибку при
ссылке на нее во время выполнения.
<h2>Использование замыканий для хранения конфиденциальной информации.</h2>
<p>
Если вы закрываете/замыкаете лексическую переменную, то эта переменная доступна только
изнутри замыкания. Вы можете использовать это в своих интересах для хранения
информации, которая действительно является частной, доступной только для функций,
которые имеют замыкание, содержащее вашу частную переменную(ные).
<pre>
? (let ((password nil)
(secret nil))
(defun set-password (new-passwd)
(if password
'|Can't - already set|
(setq password new-passwd)))
(defun change-password (old-passwd new-passwd)
(if (eq old-passwd password)
(setq password new-passwd)
'|Not changed|))
(defun set-secret (passwd new-secret)
(if (eq passwd password)
(setq secret new-secret)
'|Wrong password|))
(defun get-secret (passwd)
(if (eq passwd password)
secret
'|Sorry|)))
GET-SECRET
? (get-secret 'sesame)
|Sorry|
? (set-password 'valentine)
SECRET
? (set-secret 'sesame 'my-secret)
|Wrong password|
? (set-secret 'valentine 'my-secret)
MY-SECRET
? (get-secret 'fubar)
|Sorry|
? (get-secret 'valentine)
MY-SECRET
? (change-password 'fubar 'new-password)
|Not changed|
? (change-password 'valentine 'new-password)
NEW-PASSWORD
? (get-secret 'valentine)
|Sorry|
<i>; The closed-over lexical variables aren't in the global environment</i>
? password
Error: unbound variable
? secret
Error: unbound variable
<i>; The global environment doesn't affect the closed-over variables</i>
? (setq password 'cheat)
CHEAT
? (get-secret 'cheat)
|Sorry|
</pre>
<h2>Функции, которые возвращают функции и чем они отличаются от макросов.</h2>
<p>
Предыдущий пример хорош только для сохранения одного секрета, потому что
каждый раз, когда мы вычисляем внешнюю форму <code>LET</code>, мы
переопределяем все функции, которые замыкаются над нашими "частными"
переменными. Если мы хотим устранить нашу зависимость от глобального
пространства имен для функций, манипулирующих нашими закрытыми переменными,
нам придется найти способ создать новые замкнутые/закрытые переменные и
вернуть функцию, которую мы можем сохранить и позже использовать для
манипулирования переменными. Что-то вроде этого сработает:
<pre>
? (defun make-secret-keeper ()
(let ((password nil)
(secret nil))
#'(lambda (operation &rest arguments)
(ecase operation
(set-password
(let ((new-passwd (first arguments)))
(if password
'|Can't - already set|
(setq password new-passwd))))
(change-password
(let ((old-passwd (first arguments))
(new-passwd (second arguments)))
(if (eq old-passwd password)
(setq password new-passwd)
'|Not changed|)))
(set-secret
(let ((passwd (first arguments))
(new-secret (second arguments)))
(if (eq passwd password)
(setq secret new-secret)
'|Wrong password|)))
(get-secret
(let ((passwd (first arguments)))
(if (eq passwd password)
secret
'|Sorry|)))))))
MAKE-SECRET-KEEPER
? (defparameter secret-1 (make-secret-keeper))
SECRET-1
? secret-1
#<LEXICAL-CLOSURE #x36AE056>
? (funcall secret-1 'set-password 'valentine)
VALENTINE
? (funcall secret-1 'set-secret 'valentine 'deep-dark)
DEEP-DARK
? (defparameter secret-2 (make-secret-keeper))
SECRET-2
? (funcall secret-2 'set-password 'bloody)
BLOODY
? (funcall secret-2 'set-secret 'bloody 'mysterious)
MYSTERIOUS
? (funcall secret-2 'get-secret 'valentine)
|Wrong password|
? (funcall secret-1 'get-secret 'valentine)
DEEP-DARK
</pre>
<p>
Форма <code>ECASE</code> является исчерпывающим изложением дела.
В нашей программе операция <code>OPERATION</code> должна быть найдена
в одном из предложений <code>ECASE</code>, иначе Lisp будет сигнализировать
об ошибке.
<p>
Форма <code>#'(LAMBDA ...</code> создает замыкание над свободными переменными
<code>PASSWORD</code> and <code>SECRET</code>. Каждый раз, когда мы вычисляем
<code>MAKE-SECRET-KEEPER</code>, самая внешняя форма <code>LET</code> создает
новые привязки для этих переменных; замыкание затем создается и возвращается в
результате функции <code>MAKE-SECRET-KEEPER</code>.
<blockquote>
В пред-ANSI Common Lisp <code>LAMBDA</code> - это просто символ,
который распознается как маркер для определения лямбда-выражения.
Сама по себе <code>LAMBDA</code> не создает замыкания;
это функция макроса чтения <code>#'</code> (который расширяется
в форму <code>(FUNCTION ...</code> form).
<p>
ANSI Common Lisp определяет макрос <code>LAMBDA</code>, который расширяется
в <code>(FUNCTION (LAMBDA ...</code>, который вы можете использовать вместо
<code>#'(LAMBDA</code> везде, где он появляется в этом примере. Для обратной
совместимости с реализациями Пред-ANSI Common Lisp вы всегда должны писать
<code>#'(LAMBDA ...</code> -- избыточная <code>(FUNCTION ...</code> в
расширении не причинити никакого вреда.
</blockquote>
<p>
В каждом предложении <code>ECASE</code> мы извлекаем аргументы из
остатков(<code>&REST</code>) переменной <code>ARGUMENTS</code>
и затем выполняем точно такую же обработку, как и в нашем предыдущем
примере.
<p>
После того, как мы вызвали <code>MAKE-SECRET-KEEPER</code> и сохранили
результирующее замыкание, мы можем <code>FUNCALL</code>(выполнить) замыкание,
передавая символ операции и любые дополнительные аргументы. Обратите внимание,
что каждое замыкание, созданное <code>MAKE-SECRET-KEEPER</code>, полностью
независимо; поэтому мы достигли цели хранить множество секретов
<p>
Функции, возвращающие замыкания, отличаются от макросов. Макрос - это функция,
которая создает форму; затем форма вычисляется для получения результата.
Функция, возвращающая замыкание, просто возвращает объект: замыкание.
Возвращаемое замыкание не вычисляется автоматически Lisp вычислителем.
<hr>
<div align="center">
<a href="contents.html">Contents</a> | <a href="cover.html">Cover</a> <br>
<a href="chapter14.html">Chapter 14</a> | Chapter 15 | <a href="chapter16.html">Chapter 16</a>
</div>
<hr>
<address>
Copyright © 1995-2001, David B. Lamkins<br>
All Rights Reserved Worldwide<br>
<br>
This book may not be reproduced without the written consent of its author. Online distribution is restricted to the author's site.
</address>
</body> </html>