-
Notifications
You must be signed in to change notification settings - Fork 8
/
chapter-05.tex
809 lines (670 loc) · 64.7 KB
/
chapter-05.tex
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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
\chapter{Функции}
\label{ch:05}
\thispagestyle{empty}
Кроме правил синтаксиса и семантики, основу всех программ на Lisp составляют следующие три
компонента~-- функции, переменные и макросы. Вы использовали их во время создания базы
данных в главе~\ref{ch:03}, но я опустил много подробностей о том, как они работают и как
их лучше всего использовать. Я посвящу следующие главы этим вопросам, начав с функций,
которые, так же как и их аналоги в других языках программирования, обеспечивают основные
возможности абстракции.
Большая часть самого Lisp состоит из функций. Более трёх четвертей имён, указанных в
стандарте, являются именами функций. Все базовые типы данных полностью определены в
терминах функций, работающих с ними. Даже мощная объектная система языка Lisp построена на
концептуальном развитии понятий функции и обобщённой функции, которые будут описаны в
главе~\ref{ch:16}.
В~конце концов, несмотря на важность макросов (\textit{The Lisp Way!}), вся реальная
функциональность обеспечивается функциями. Макросы выполняются во время компиляции и
создают код программы. После того как все макросы будут раскрыты, этот код полностью будет
состоять из обращения к функциям и специальным операторам. Я не упоминаю, что макросы
сами являются функциями, которые используются для генерации кода, а не для выполнения
действий в программе\pclfootnote{Несмотря на важность функций в Common Lisp, не совсем
правильно называть его \emph{функциональным} языком. Это правильно для некоторой части
возможностей Common Lisp, таких как функции работы со списками, которые созданы для
использования в стиле \texttt{body-form*}. Конечно же Lisp занимает значительное место
в истории функционального программирования~-- McCarthy ввёл в обращение много идей,
которые считаются очень важными для функционального программирования, но Common Lisp был
умышленно спроектирован для поддержки разных стилей программирования. В~семействе
Lisp-подобных языков язык Scheme является наиболее близким к понятию <<чистого>>
функционального языка, но даже он имеет несколько возможностей, которые отделяют его от
чистоты таких языков, как Haskell и ML.}.
\section{Определение новых функций}
Обычно функции определяются при помощи макроса \lstinline{DEFUN}. Типовое использование
\lstinline{DEFUN} выглядит вот так:
\begin{myverb}
(defun name (parameter*)
"Optional documentation string."
тело-функции*)
\end{myverb}
В~качестве имени может использоваться любой символ\pclfootnote{Хорошо, почти любой символ.
Неопределённым является поведение, когда вы в качестве имени для ваших функций
используете одно из имён, указанных в стандарте. Однако, как вы увидите в главе~\ref{ch:21},
система пакетов Lisp позволяет вам создавать имена в разных пространствах имён, так что
это не является проблемой.}. Как правило, имена функций содержат только буквы, цифры и
знак минус, но, кроме того, разрешено использование других символов, и они используются в
определённых случаях. Например, функции, которые преобразуют значения из одного типа в
другой, иногда используют символ \lstinline{->} в имени. Или функция, которая преобразует
строку в виджет, может быть названа \lstinline{string->widget}. Наиболее важное соглашение по
именованию, затронутое в главе~\ref{ch:02}, заключается в том, что лучше создавать составные имена,
используя знак минус вместо подчёркивания или заглавные буквы внутри имени.
Так что \lstinline{frob-widget} лучше соответствует стилю Lisp, чем \lstinline!frob_widget! или
\lstinline{frobWidget}.
Список параметров функции определяет переменные, которые будут использоваться для хранения
аргументов, переданных при вызове функции\pclfootnote{Списки параметров иногда называются
лямбда-списками из-за исторического отношения между понятием функции в Lisp и
лямбда-исчислением.}. Если функция не принимает аргументов, то список пуст и
записывается как \lstinline{()}. Различают обязательные, необязательные, множественные и
именованные (keyword) параметры. Эти вопросы будут обсуждаться подробнее в следующем
разделе.
За списком параметров может находиться строка, которая описывает назначение функции.
После того как функция определена, эта строка (строка документации) будет ассоциирована
с именем функции и может быть позже получена с помощью функции
\lstinline{DOCUMENTATION}\footnote{Например, следующий код:
\begin{myverb}
(documentation 'foo 'function)
\end{myverb}
\noindent{}вернёт строку документации для функции \lstinline{foo}. Однако заметьте, что документация
предназначается для людей, а не для работы программ. Реализации Lisp не обязаны сохранять
их и могут удалить их в любое время, так что переносимые программы не должны
зависеть от наличия документации к функции. В~некоторых реализациях требуется установка
специальных переменных, имена которых зависят от конкретной реализации, чтобы они начали
хранить документацию к функциям.}\hspace{\footnotenegspace}.
Тело \lstinline{DEFUN} состоит из любого числа выражений Lisp. При вызове функции они
вычисляются по порядку, и результат вычисления последнего выражения возвращается как
значение функции. Для возврата из любой точки функции может использоваться специальный
оператор \lstinline{RETURN-FROM}, что я продемонстрирую через некоторое время.
В~главе~\ref{ch:02} мы написали функцию \lstinline{hello-world}, которая выглядела вот так:
\begin{myverb}
(defun hello-world () (format t "hello, world"))
\end{myverb}
Теперь вы можете проанализировать части этой функции. Она называется \lstinline{hello-world},
список параметров пуст, потому что она не принимает аргументов, в ней нет строки
документации и её тело состоит из одного выражения:
\begin{myverb}
(format t "hello, world")
\end{myverb}
Вот пример немного более сложной функции:
\begin{myverb}
(defun verbose-sum (x y)
"Sum any two numbers after printing a message."
(format t "Summing ~d and ~d.~%" x y)
(+ x y))
\end{myverb}
Эта функция называется \lstinline{verbose-sum}, получает два аргумента, которые связываются с
параметрами \lstinline{x} и \lstinline{y}, имеет строку документации, и её тело состоит из двух
выражений. Значение, возвращённое вызовом функции \lstinline{+}, становится значением функции
\lstinline{verbose-sum}.
\section{Списки параметров функций}
Это всё, больше нечего сказать об именах функций или о строках документации. В~оставшейся
части книги мы будем описывать то, что можно написать в теле функции, поэтому мы остаёмся
наедине со списками параметров функций.
Основное назначение списков параметров~-- объявление переменных, которые будут
использоваться для хранения аргументов, переданных функции. Когда список параметров
является простым списком имён переменных, как в \lstinline{verbose-sum}, то параметры
называются \textit{обязательными}. Когда функция вызывается, она должна получить ровно по
одному аргументу для каждого из обязательных параметров. Каждый параметр связывается с
соответствующим аргументом. Если функция вызывается с меньшим или большим количеством
аргументов, чем требуется, то Lisp сообщит об ошибке.
Однако списки параметров в Common Lisp предоставляют более удобные способы отображения
аргументов функции в параметры функции. В~дополнение к обязательным параметрам функция
может иметь \textit{необязательные} параметры. Или функция может иметь один параметр,
который будет связан со списком, содержащим все дополнительные аргументы. И в заключение
аргументы могут быть связаны с параметрами путём использования \textit{ключевых слов}
(keywords), а не путём соответствия позиции параметра и аргумента в списке. Таким
образом, списки параметров Common Lisp предоставляют удобное решение для некоторых общих
задач кодирования.
\section{Необязательные параметры}
В~то время как многие функции, подобно \lstinline{verbose-sum}, нуждаются только в
обязательных параметрах, не все функции являются настолько простыми. Иногда функции
должны иметь параметр, который будет использоваться лишь при некоторых вызовах, поскольку
он имеет <<правильное>> значение по умолчанию. Таким примером может быть функция,
создающая структуру данных, которая будет при необходимости расти. Поскольку структура
данных может расти, то часто не имеет значения какой начальный размер она имеет. Но
пользователь функции, который имеет понятие о том, сколько данных будет помещено в
подобную структуру, может улучшить производительность программы путём указания начального
размера этой структуры. Однако большинство пользователей данной функции, скорее всего,
позволят выбрать наиболее подходящий размер автоматически. В~Common Lisp вы можете
предоставить этим пользователям одинаковые возможности с помощью необязательных
параметров; пользователи, которые не хотят устанавливать значение сами, получат разумное
значение по умолчанию, а остальные пользователи смогут подставить нужное
значение\footnote{В~языках, которые явно не поддерживают необязательных параметров,
программисты обычно находят методы их эмуляции. Одна из техник заключается в
использовании предопределённых значений <<no-value>>, которые пользователь может
передать, показывая, что он хочет использовать значение по умолчанию. В~языке C,
например, часто используют \lstinline{NULL} в качестве такого предопределённого
значения. Однако подобная договорённость между функцией и её пользователями является
лишь подпоркой~-- в некоторых функциях или для некоторых аргументов предопределённым
значением может быть \lstinline{NULL}, в то время как для других функций или для других
аргументов таким значением может быть~$-1$ или некоторая другая предопределённая
константа (часто заданная с помощью \lstinline!#define!).}\hspace{\footnotenegspace}.
Для определения функции с необязательными параметрами после списка обязательных параметров
поместите символ \lstinline!&optional!, за которым перечислите имена необязательных
параметров. Простой пример использования выглядит так:
\begin{myverb}
(defun foo (a b &optional c d)
(list a b c d))
\end{myverb}
Когда функция будет вызвана, сначала аргументы связываются с обязательными параметрами.
После того как обязательные параметры получили переданные значения и остались ещё
аргументы, то они будут присвоены необязательным параметрам. Если аргументы закончатся до
того, как иссякнет список необязательных параметров, то оставшиеся параметры получат
значение \lstinline{NIL}. Таким образом, предыдущая функция будет выдавать следующие
результаты:
\begin{myverb}
(foo 1 2) ==> (1 2 NIL NIL)
(foo 1 2 3) ==> (1 2 3 NIL)
(foo 1 2 3 4) ==> (1 2 3 4)
\end{myverb}
Lisp все равно будет проверять количество аргументов, переданных функции (в нашем случае
это число от~2 до~4 включительно), и будет выдавать ошибку, если функция вызвана с
лишними аргументами, или их, наоборот, недостаёт.
Конечно, вы можете захотеть использовать другие значения по умолчанию, отличные от
\lstinline{NIL}. Вы можете указать их путём замены имени параметра на список, состоящий из
имени и выражения. Это выражение будет вычислено, только если пользователь не указал
значения для необязательного параметра. Общепринятым является простое задание конкретного
значения в качестве выражения.
\begin{myverb}
(defun foo (a &optional (b 10))
(list a b))
\end{myverb}
Эта функция требует указания одного аргумента, который будет присвоен параметру \lstinline{a}.
Второй параметр~-- \lstinline{b} получит либо значение второго аргумента, если он указан, либо
число~10.
\begin{myverb}
(foo 1 2) ==> (1 2)
(foo 1) ==> (1 10)
\end{myverb}
Однако иногда вам потребуется большая гибкость в выборе значения по умолчанию. Вы
можете захотеть вычислять значение по умолчанию, основываясь на других параметрах. И вы
можете сделать это~-- выражение для значения по умолчанию может ссылаться на параметры,
ранее перечисленные в списке параметров. Если вы пишете функцию, которая возвращает
что-то типа описания прямоугольников, и вы хотите сделать её удобной для использования с
квадратами, то можете использовать такой вот список параметров:
\begin{myverb}
(defun make-rectangle (width &optional (height width))
...)
\end{myverb}
\noindent{}что сделает параметр \lstinline{height} равным параметру \lstinline{width}, если только он не будет
явно задан.
Иногда полезно будет знать, было значение необязательного параметра задано
пользователем или использовалось значение по умолчанию. Вместо того чтобы писать код,
который проверяет, является ли переданное значение равным значению по умолчанию (это все
равно не будет работать, поскольку пользователь может явно задать значение, равное
значению по умолчанию), вы можете добавить ещё одно имя переменной к списку параметров
после выражения для значения по умолчанию. Указанная переменная будет иметь истинное
значение, если пользователь задал значение для аргумента, и \lstinline{NIL} в противном случае.
По соглашению эти переменные называются так же, как и параметры, но с добавлением
<<\lstinline{-supplied-p}>> к концу имени. Например:
\begin{myverb}
(defun foo (a b &optional (c 3 c-supplied-p))
(list a b c c-supplied-p))
\end{myverb}
Выполнение этого кода приведёт к следующим результатам:
\begin{myverb}
(foo 1 2) ==> (1 2 3 NIL)
(foo 1 2 3) ==> (1 2 3 T)
(foo 1 2 4) ==> (1 2 4 T)
\end{myverb}
\section{Остаточные (rest) параметры}
Необязательные параметры применяются только тогда, когда у вас есть отдельные параметры,
для которых пользователь может указывать или не указывать значения. Но некоторые функции
могут требовать изменяемого количества аргументов. Некоторые встроенные функции, которые
вы уже видели, работают именно так. Функция \lstinline{FORMAT} имеет два обязательных
аргумента~-- поток вывода и управляющую строку. Но, кроме этого, он требует переменное
количество аргументов, зависящее от того, сколько значений он должен вставить в
управляющую строку. Функция \lstinline{+} также получает переменное количество аргументов~--
нет никаких причин ограничиваться складыванием только двух чисел, эта функция может
вычислять сумму любого количества значений. (Она даже может работать вообще без
аргументов, возвращая значение \lstinline{0}.) Следующие примеры являются допустимыми вызовами
этих двух функций:
\begin{myverb}
(format t "hello, world")
(format t "hello, ~a" name)
(format t "x: ~d y: ~d" x y)
(+)
(+ 1)
(+ 1 2)
(+ 1 2 3)
\end{myverb}
Очевидно, что вы можете написать функцию с переменным числом аргументов, просто описывая
множество необязательных параметров. Но это будет невероятно мучительно~-- простое
написание списка параметров может быть не очень хорошим делом, и это не связывает все
параметры с их использованием в теле функции. Для того чтобы сделать это правильно, вы
должны иметь число необязательных параметров равным максимальному допустимому количеству
аргументов при вызове функций. Это число зависит от реализации, но гарантируется, что оно
будет равно минимум \lstinline{50}. В~текущих реализациях оно варьируется от \lstinline{4,096} до
\lstinline{536,870,911}\footnote{Для вашей реализации вы можете узнать это значение, используя
константу \lstinline{CALL-ARGUMENTS-LIMIT}.}\hspace{\footnotenegspace}. Хех! Этот мозгодробительный подход явно не
является хорошим стилем написания программ.
Вместо этого Lisp позволяет вам указать параметр, который примет все аргументы (этот
параметр указывается после символа \lstinline!&rest!). Если функция имеет параметр
\lstinline!&rest! (остаточный параметр), то любые аргументы, оставшиеся после связывания
обязательных и необязательных параметров, будут собраны в список, который станет значением
остаточного параметра \lstinline!&rest!. Таким образом, список параметров для функций
\lstinline{FORMAT} и \lstinline{+} будет выглядеть примерно так:
\begin{myverb}
(defun format (stream string &rest values) ...)
(defun + (&rest numbers) ...)
\end{myverb}
\section{Именованные параметры}
Необязательные и остаточные (rest) параметры дают вам достаточно гибкости, но ни один из
них не помогает вам в следующей ситуации: предположим, что вы имеете функцию, которая
получает четыре необязательных параметра. Теперь предположим, что пользователь захочет
задать значение только для одного из параметров и даже что пользователь захочет задать
значение только для некоторых, расположенных не последовательно параметров.
Пользователи, которые хотят задать значение для первого параметра, не имеют никаких
проблем~-- они просто передадут один необязательный параметр и пропустят оставшиеся. Но
что делать пользователям, которые хотят указать значения для других параметров,~-- разве
это не та проблема, которую должно решить использование необязательных параметров?
Конечно, это она. Но проблема заключается в том, что необязательные параметры все равно
являются позиционными~-- если пользователь хочет указать четвёртый необязательный
параметр, то первые три необязательных параметра превращаются для этого пользователя в
обязательные. К счастью, существует ещё один вид параметров~-- именованные (keyword)
параметры, которые позволяют указывать пользователю, какие значения будут связаны с
конкретными параметрами.
Для того чтобы задать именованные параметры, необходимо после всех требуемых,
необязательных и остаточных параметров указать символ \lstinline!&key! и затем
перечислить любое количество спецификаторов именованных параметров. Вот пример функции,
которая имеет только именованные параметры:
\begin{myverb}
(defun foo (&key a b c)
(list a b c))
\end{myverb}
Когда функция вызывается, каждый именованный параметр связывается со значением, которое
указано после ключевого слова, имеющего то же имя, что и параметр. Вернёмся к
главе~\ref{ch:04}, в которой указывалось, что ключевые слова~-- это имена, которые
начинаются с двоеточия и которые автоматически определяются как константы, вычисляемые
сами в себя (self-evaluating).
Если ключевое слово не указано в списке аргументов, то соответствующий параметр получает
значение по умолчанию, т.~е. принцип тот же, что и для необязательных параметров.
Поскольку именованные аргументы имеют метку, то они могут быть указаны в любом порядке,
если они следуют после обязательных аргументов. Например, \lstinline{foo} может быть вызвана
вот так:
\begin{myverb}
(foo) ==> (NIL NIL NIL)
(foo :a 1) ==> (1 NIL NIL)
(foo :b 1) ==> (NIL 1 NIL)
(foo :c 1) ==> (NIL NIL 1)
(foo :a 1 :c 3) ==> (1 NIL 3)
(foo :a 1 :b 2 :c 3) ==> (1 2 3)
(foo :a 1 :c 3 :b 2) ==> (1 2 3)
\end{myverb}
Так же как и для необязательных параметров, для именованных параметров можно задавать
выражение для вычисления значения по умолчанию и имя \lstinline{supplied-p}-переменной. И для
необязательных, и для именованных параметров значение по умолчанию может ссылаться на
параметры, указанные ранее в списке.
\begin{myverb}
(defun foo (&key (a 0) (b 0 b-supplied-p) (c (+ a b)))
(list a b c b-supplied-p))
\end{myverb}
\begin{myverb}
(foo :a 1) ==> (1 0 1 NIL)
(foo :b 1) ==> (0 1 1 T)
(foo :b 1 :c 4) ==> (0 1 4 T)
(foo :a 2 :b 1 :c 4) ==> (2 1 4 T)
\end{myverb}
Так же, если по некоторым причинам вы хотите, чтобы пользователь использовал имена
аргументов, отличающиеся от имён параметров, то вы можете заменить имя параметра на
список, содержащий имя, которое будет использоваться пользователем при вызове, и имя
параметра. Следующее определение \lstinline{foo}:
\begin{myverb}
(defun foo (&key ((:apple a)) ((:box b) 0) ((:charlie c) 0 c-supplied-p)
(list a b c c-supplied-p))
\end{myverb}
\noindent{}позволяет пользователю вызывать функцию вот так:
\begin{myverb}
(foo :apple 10 :box 20 :charlie 30) ==> (10 20 30 T)
\end{myverb}
Этот стиль особенно полезен, если вы хотите полностью отделить публичный интерфейс от
деталей внутренней реализации, поскольку обычно внутри вы хотите использовать короткие
имена переменных и значащие имена в программном интерфейсе. Однако обычно это
используется не особо часто.
\section{Совместное использование разных типов параметров}
Использование всех четырёх типов параметров в одной функции хотя и является вполне
возможным, но применяется редко. Когда используется более одного типа параметров, они
должны быть объявлены в порядке, который мы уже обсуждали: сначала указываются имена
требуемых параметров, затем~-- необязательных, потом~-- остаточных (\lstinline!&rest!), и в
заключение~-- именованных параметров. Но обычно в функциях, которые используют несколько
типов параметров, комбинируют требуемые параметры с одним из других видов параметров или
комбинируют необязательные и остаточные параметры. Два других сочетания~--
необязательных или остаточных параметров с именованными параметрами~-- могут привести к
очень удивительному поведению функции.
Комбинация необязательных и именованных параметров преподносит достаточно сюрпризов, так
что вы, скорее всего, должны избегать их совместного использования. Проблема заключается в
том, что если пользователь не задаёт значений для всех необязательных параметров, то эти
параметры получают имена и значения именованных параметров. Например, эта функция
использует необязательные и именованные параметры:
\begin{myverb}
(defun foo (x &optional y &key z)
(list x y z))
\end{myverb}
Если она вызывается вот так, то все нормально:
\begin{myverb}
(foo 1 2 :z 3) ==> (1 2 3)
\end{myverb}
И вот все работает нормально:
\begin{myverb}
(foo 1) ==> (1 nil nil)
\end{myverb}
Но в этом случае она выдаёт ошибку:
\begin{myverb}
(foo 1 :z 3) ==> ERROR
\end{myverb}
Это происходит потому, что имя параметра \lstinline{:z} берётся как значение для
необязательного параметра \lstinline{y}, оставляя для обработки только аргумент \lstinline{3}. При
этом Lisp ожидает, что в этом месте встретится либо пара имя/значение, либо не будет
ничего, и одиночное значение приведёт к выдаче ошибки. Будет даже хуже, если функция
будет иметь два необязательных параметра, так что использование функции как в последнем
примере приведёт к тому, что значения \lstinline{:z} и \lstinline{3} будут присвоены двум
необязательным параметрам, а именованный параметр \lstinline{z} получит значение по
умолчанию~-- \lstinline{NIL}, без всякого указания, что что-то произошло неправильно.
В~общем, если вы обнаружите, что вы пишете функцию, которая использует и необязательные, и
именованные параметры, то вам лучше просто исправить её для использования только
именованных параметров~-- этот подход более гибок, и вы всегда сможете добавить новые
параметры, не беспокоя пользователей вашей функции. Вы можете даже удалять именованные
параметры, если никто не использует их\footnote{Четыре стандартные функции принимают
необязательные и именованные аргументы~-- \lstinline{READ-FROM-STRING},
\lstinline{PARSE-NAMESTRING}, \lstinline{WRITE-LINE} и \lstinline{WRITE-STRING}. Их оставили во время
стандартизации для обеспечения обратной совместимости с более ранними диалектами Lisp.
\lstinline{READ-FROM-STRING} является лидером по количеству ошибок, сделанных начинающими
программистами на Lisp,~-- вызов этой функции как \lstinline{(read-from-string s :start 10)}
игнорирует ключевое слово \lstinline{:start}, читает с индекса \lstinline{0}, а не с \lstinline{10}.
Это происходит, поскольку \lstinline{READ-FROM-STRING} имеет два необязательных параметра,
которые съедают аргументы \lstinline{:start} и \lstinline{10}.}\hspace{\footnotenegspace}. Использование именованных
параметров помогает сделать код более лёгким для сопровождения и развития~-- если вам
нужно изменить поведение функции, и это изменение потребует ввода новых параметров, вы
можете добавить именованные параметры без изменения или даже без перекомпиляции кода,
который использует эту функцию.
Вы можете безопасно комбинировать остаточные и именованные параметры, но вначале поведение
может показаться немного удивительным. Обычно наличие либо остаточных, либо именованных
параметров приведёт к тому, что значения, оставшиеся после заполнения всех обязательных и
необязательных параметров, будут обработаны определённым образом~-- либо собраны в список
(для остаточных параметров), либо присвоены соответствующим именованным параметрам. Если в
списке параметров используются и остаточные, и именованные параметры, то выполняются оба
действия~-- все оставшиеся значения собираются в список, который присваивается параметру
\lstinline!&rest!, а также соответствующие значения присваиваются именованным параметрам.
Так что, имея следующую функцию:
\begin{myverb}
(defun foo (&rest rest &key a b c)
(list rest a b c))
\end{myverb}
\noindent{}вы получите следующие результаты:
\begin{myverb}
(foo :a 1 :b 2 :c 3) ==> ((:A 1 :B 2 :C 3) 1 2 3)
\end{myverb}
\section{Возврат значений из функции}
Все функции, которые уже были написаны, так или иначе использовали обычное поведение,
заключающееся в возврате значения последнего вычисленного выражения как собственного
возвращаемого значения. Это самый употребительный способ возврата значений из функции.
Однако иногда бывает нужно вернуть значение из середины функции, вырываясь таким образом
из вложенных управляющих конструкций. В~таком случае вы можете использовать специальный
оператор \lstinline{RETURN-FROM}, который предназначен для немедленного возвращения любого
значения из функции.
Вы увидите в главе~\ref{ch:20}, что \lstinline{RETURN-FROM} в самом деле не привязана к функциям; она
используется для возврата из блока кода, определённого с помощью оператора \lstinline{BLOCK}.
Однако \lstinline{DEFUN} автоматически помещает тело функции в блок кода с тем же именем, что и
имя функции. Так что вычисление \lstinline{RETURN-FROM} с именем функции и значением, которое вы
хотите возвратить, приведёт к немедленному выходу из функции с возвратом указанного
значения. \lstinline{RETURN-FROM} является специальным оператором, чьим первым аргументом
является имя блока, из которого необходимо выполнить возврат. Это имя не вычисляется, так
что нет нужды его экранировать.
Следующая функция использует вложенные циклы для нахождения первой пары чисел, каждое из
которых меньше, чем \lstinline{10}, и чьё произведение больше заданного аргумента, и она использует
\lstinline{RETURN-FROM} для возврата первой найденной пары чисел:
\begin{myverb}
(defun foo (n)
(dotimes (i 10)
(dotimes (j 10)
(when (> (* i j) n)
(return-from foo (list i j))))))
\end{myverb}
Надо отметить, что необходимость указания имени функции, из которой вы хотите вернуться,
является не особо удобной~-- если вы измените имя функции, то вам нужно будет также
изменить имя, использованное в операторе \lstinline{RETURN-FROM}\footnote{Другой макрос,
\lstinline{RETURN}, не требует указания имени блока. Однако вы не можете использовать его
вместо \lstinline{RETURN-FROM}, для того чтобы не указывать имя функции,~-- это лишь
синтаксическая обвязка для возврата из блока с именем \lstinline{NIL}. Описание этого
макроса вместе с описанием \lstinline{BLOCK} и \lstinline{RETURN-FROM} будет сделано в
главе~\ref{ch:20}.}\hspace{\footnotenegspace}. Но следует отметить, что явное использование
\lstinline{RETURN-FROM} в Lisp происходит значительно реже, чем использование
выражения \lstinline{return} в C-подобных языках, поскольку
все выражения Lisp, включая управляющие конструкции, такие как условные выражения и циклы,
вычисляются в значения. Так что это не представляет особой сложности на практике.
\section{Функции как данные, или функции высшего порядка}
В~то время как основной способ использования функций~-- это вызов их с указанием имени,
существуют ситуации, когда было бы полезно рассматривать функции как данные. Например, вы
можете передать одну функцию в качестве аргумента другой функции, вы можете написать общую
функцию сортировки и предоставить пользователю возможность указания функции для сравнения
двух элементов. Так что один и тот же алгоритм может использоваться с разными функциями
сравнения. Аналогично обратные вызовы (callbacks) и ловушки (hooks) зависят от
возможности хранения ссылок на исполняемый код, который можно выполнить позже. Поскольку
функции уже являются стандартным способом представления частей кода, имеет смысл разрешить
рассмотрение функций как данных\pclfootnote{Конечно, Lisp не является единственным языком,
который позволяет рассматривать функции как данные. Язык C использует указатели на
функции, Perl использует ссылки на подпрограммы, Python использует подход, аналогичный
Lisp, а C\# ввёл делегаты (типизированные указатели на функции, призванные улучшить
механизмы, используемые в Java) и также механизм анонимных классов.}.
В~Lisp функции являются просто другим типом объектов. Когда вы определяете функцию с
помощью \lstinline{DEFUN}, вы в действительности делаете две вещи: создаёте новый
объект-функцию и даёте ему имя. Кроме того, имеется возможность, как вы увидели в
главе~\ref{ch:03}, использовать \lstinline{LAMBDA} для создания функции без имени.
Действительное представление объекта-функции, независимо от того, именованный он или нет,
является неопределённым~-- в компилируемых вариантах Lisp они, вероятно, состоят в
основном из машинного кода. Единственные вещи, которые вам надо знать,~-- как получить
эти объекты и как выполнять их, если вы их получили.
Специальный оператор \lstinline{FUNCTION} обеспечивает механизм получения объекта-функции. Он
принимает единственный аргумент и возвращает функцию с этим именем. Имя не экранируется.
Так что если вы определили функцию \lstinline{foo}, например, вот так:
\begin{myverb}
CL-USER> (defun foo (x) (* 2 x))
FOO
\end{myverb}
\noindent{}вы можете получить объект-функцию следующим образом\pclfootnote{Точное печатное
представление объекта-функции может отличаться в зависимости от реализации.}:
\begin{myverb}
CL-USER> (function foo)
#<Interpreted Function FOO>
\end{myverb}
В~действительности вы уже использовали \lstinline{FUNCTION}, но это было
замаскировано. Синтаксис \lstinline!#'!, который вы использовали в
главе~\ref{ch:03}, является синтаксической обёрткой для \lstinline{FUNCTION}, точно
так же как и \lstinline{'} является обёрткой для \lstinline{QUOTE}\footnote{Лучше всего
рассматривать \lstinline{FUNCTION} как специальный вид экранирования.
Экранирование символа предотвращает его вычисление, оставляя сам символ, а не
значение переменной с именем символа. \lstinline{FUNCTION} также изменяет
нормальные правила вычисления, но вместо предотвращения вычисления символа
заставляет вычислять его как имя функции, точно так же, как если бы этот символ
использовался в качестве имени функции в выражении вызова.}\hspace{\footnotenegspace}. Так что вы можете
получить объект-функцию вот так:
\begin{myverb}
CL-USER> #'foo
#<Interpreted Function FOO>
\end{myverb}
После того как вы получили объект-функцию, есть только одна вещь, которую вы можете
сделать с ней,~-- выполнить её. Common Lisp предоставляет две функции для выполнения
функции через объект-функцию: \lstinline{FUNCALL} и \lstinline{APPLY}\footnote{В~действительности
существует и третья возможность, специальный оператор \lstinline{MULTIPLE-VALUE-CALL}, но я
отложу этот вопрос до того момента, когда мы будем обсуждать выражения, возвращающие
множественные значения, в главе~\ref{ch:20}.}\hspace{\footnotenegspace}. Они отличаются тем, как они получают
аргументы, которые будут переданы вызываемой функции.
\lstinline{FUNCALL}~-- это функция, которая используется тогда, когда во время написания кода вы
знаете количество аргументов, которые вы будете передавать функции. Первым аргументом
\lstinline{FUNCALL} является запускаемый объект-функция, а оставшиеся аргументы передаются
данной функции. Так что следующие два выражения являются эквивалентными:
\begin{myverb}
(foo 1 2 3) === (funcall #'foo 1 2 3)
\end{myverb}
Однако довольно мало смысла в использовании \lstinline{FUNCALL} для вызова функции, чьё имя вы
знаете во время написания кода. В~действительности два предыдущих выражения, скорее всего,
скомпилируются в один и тот же машинный код.
Следующая функция демонстрирует более реалистичное использование \lstinline{FUNCALL}. Она
принимает объект-функцию в качестве аргумента и рисует простую текстовую диаграмму
значений, возвращённых функцией, вызываемой для значений от \lstinline{min} до \lstinline{max} с
шагом \lstinline{step}.
\begin{myverb}
(defun plot (fn min max step)
(loop for i from min to max by step do
(loop repeat (funcall fn i) do (format t "*"))
(format t "~%")))
\end{myverb}
Выражение \lstinline{FUNCALL} вычисляет значение функции для каждого значения \lstinline{i}.
Внутрений цикл использует это значение для определения того, сколько раз напечатать знак
<<звёздочка>>.
Заметьте, что вы не используете \lstinline{FUNCTION} или \lstinline!#'! для получения значения
\lstinline{fn}; вы хотите, чтобы оно интерпретировалось как переменная, поскольку значение этой
переменной является объектом-функцией. Вы можете вызвать \lstinline{plot} с любой функцией,
которая берёт один числовой аргумент, например со встроенной функцией \lstinline{EXP}, которая
возвращает значение \lstinline{e}, возведённое в степень переданного аргумента.
\begin{myverb}
CL-USER> (plot #'exp 0 4 1/2)
*
*
**
****
*******
************
********************
*********************************
******************************************************
NIL
\end{myverb}
Однако \lstinline{FUNCALL} не особо полезен, когда список аргументов становится известен только
во время выполнения. Например, для работы с функцией \lstinline{plot} в других случаях
представьте, что вы получили список, содержащий объект-функцию, минимальное и максимальное
значения, а также шаг изменения значений. Другими словами, список содержит значения,
которые вы хотите передать как аргументы для \lstinline{plot}. Предположим, что этот список
находится в переменной \lstinline{plot-data}. Вы можете вызвать \lstinline{plot} с этими значениями
вот так:
\begin{myverb}
(plot
(first plot-data)
(second plot-data)
(third plot-data)
(fourth plot-data))
\end{myverb}
Это работает нормально, но достаточно раздражает необходимость явного доставания
аргументов лишь для того, чтобы передать их функции \lstinline{plot}.
Это как раз тот случай, когда на помощь приходит \lstinline{APPLY}. Подобно \lstinline{FUNCALL}, её
первым аргументом является объект-функция. Но после первого аргумента, вместо
перечисления отдельных аргументов, она принимает список. Затем \lstinline{APPLY} применяет
функцию к значениям в списке. Это позволяет вам переписать предыдущий код следующим
образом:
\begin{myverb}
(apply #'plot plot-data)
\end{myverb}
Кроме того, \lstinline{APPLY} может также принимать <<свободные>> аргументы, так же как и обычные
аргументы в списке. Таким образом, если \lstinline{plot-data} содержит только значения для
\lstinline{min}, \lstinline{max} и \lstinline{step}, то вы все равно можете использовать \lstinline{APPLY} для
отображения функции \lstinline{EXP}, используя следующее выражение:
\begin{myverb}
(apply #'plot #'exp plot-data)
\end{myverb}
\lstinline{APPLY} не заботится о том, использует ли функция необязательные, остаточные или
именованные объекты,~-- список аргументов создаётся путём объединения всех аргументов, и
результирующий список должен быть правильным списком аргументов для функции с достаточным
количеством аргументов для обязательных параметров и соответствующими именованными
параметрами.
\section{Анонимные функции}
После того как вы начали писать или даже просто использовать функции, которые принимают в
качестве аргументов другие функции, вы, скорее всего, открыли для себя тот момент, что
иногда раздражает необходимость определять и давать имя функции, которая будет
использоваться в одном месте, особенно если вы никогда не будете вызывать её по имени.
Когда кажется, что определение новых функций с помощью \lstinline{DEFUN} является излишним, вы
можете создать <<анонимную>> функцию, используя выражение \lstinline{LAMBDA}. Как обсуждалось в
главе~\ref{ch:03}, \lstinline{LAMBDA}-выражение выглядит примерно так:
\begin{myverb}
(lambda (parameters) body)
\end{myverb}
Можно представить себе, что \lstinline{LAMBDA}-выражения~-- это специальный вид имён функций,
где само имя напрямую описывает, что эта функция делает. Это объясняет, почему вы можете
использовать \lstinline{LAMBDA}-выражение вместо имени функции с \lstinline!#'!.
\begin{myverb}
(funcall #'(lambda (x y) (+ x y)) 2 3) ==> 5
\end{myverb}
Вы даже можете использовать \lstinline{LAMBDA}-выражение как <<имя>> функции в выражениях,
вызывающих функцию. Если вы хотите, то вы можете переписать предыдущий пример с
\lstinline{FUNCALL} в следующем виде:
\begin{myverb}
((lambda (x y) (+ x y) 2 3) ==> 5
\end{myverb}
Но обычно так никогда не пишут, это использовалось лишь для демонстрации, что
\lstinline{LAMBDA}-выражения разрешено и можно использовать везде, где могут использоваться
обычные функции\footnote{В~Common Lisp также можно использовать
\lstinline{LAMBDA}-выражение как аргумент \lstinline{FUNCALL} (или любой другой функции, которая
принимает аргумент-функцию, такой как \lstinline{SORT} или \lstinline{MAPCAR}) без указания
\lstinline!#'! перед нею, например так:
\begin{myverb}
(funcall (lambda (x y) (+ x y)) 2 3)
\end{myverb}
Это разрешено и полностью соответствует версии с использованием \lstinline!#'!. Исторически
\lstinline{LAMBDA}-выражения не были выражениями, которые можно было вычислить. \lstinline{LAMBDA}
не являлось именем функции, макросом или специальным оператором. Вместо этого список,
начинавшийся с символа \lstinline{LAMBDA}, являлся специальной синтаксической конструкцией,
которую Lisp распознавал, как что-то вроде имени функции.
Но если это было бы правдой, то выражение \lstinline{(funcall (lambda (...) ...))} должно быть
неправильным, поскольку \lstinline{FUNCALL} является функцией, и стандартные правила вычислений
для вызова функций должны требовать, чтобы \lstinline{LAMBDA}-выражение было вычислено.
Однако в ходе процесса стандартизации в ANSI, для того чтобы сделать возможным реализацию
ISLISP, другого диалекта Lisp, который стандартизировался в то же самое время, в Common
Lisp был введён макрос \lstinline{LAMBDA}, используемый для совместимости на уровне
пользователей. Этот макрос раскрывается в вызов \lstinline{FUNCTION}, окружающего
\lstinline{LAMBDA}-выражение. Другими словами, следующее \lstinline{LAMBDA}-выражение:
\begin{myverb}
(lambda () 42)
\end{myverb}
\noindent{}раскрывается в следующее, если оно возникает в контексте, где требуется его вычисление:
\begin{myverb}
(function (lambda () 42)) ; или #'(lambda () 42)
\end{myverb}
Это делает возможным использование \lstinline{LAMBDA}-выражений как значений, таких как
аргумент \lstinline{FUNCALL}. Другими словами, это просто синтаксическая обёртка. Большинство
людей либо всегда используют \lstinline!#'! перед \lstinline{LAMBDA}-выражениями, либо никогда
не используют. В~этой книге я всегда буду использовать \lstinline!#'!.}\hspace{\footnotenegspace}.
Анонимные функции могут быть очень полезными, когда вы хотите передать одну функцию в
качестве аргумента другой и она достаточно проста для записи на месте. Например,
предположим, что вы хотите нарисовать график функции \lstinline{2x}. Вы можете определить
следующую функцию:
\begin{myverb}
(defun double (x) (* 2 x))
\end{myverb}
\noindent{}которую затем передать \lstinline{plot}.
\begin{myverb}
CL-USER> (plot #'double 0 10 1)
**
****
******
********
**********
************
**************
****************
******************
********************
NIL
\end{myverb}
\vspace{1.7cm}
Но легче и более понятно написать вот так:
\begin{myverb}
CL-USER> (plot #'(lambda (x) (* 2 x)) 0 10 1)
**
****
******
********
**********
************
**************
****************
******************
********************
NIL
\end{myverb}
Другим очень важным применением \lstinline{LAMBDA}-выражений является их использование для
создания замыканий (closures)~-- функций, которые захватывают часть среды выполнения, в
которой они были созданы. Вы уже использовали замыкания в главе~\ref{ch:03}, но подробное
описание о том, как замыкания работают и как они могут использоваться, больше относится к
переменным, а не к функциям, так что я отложу это обсуждение до следующей главы.
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "pcl-ru"
%%% TeX-open-quote: "<<"
%%% TeX-close-quote: ">>"
%%% End: