-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcl.tex
More file actions
2280 lines (1889 loc) · 169 KB
/
cl.tex
File metadata and controls
2280 lines (1889 loc) · 169 KB
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
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\chapter{Введение в Common Lisp}
\label{ch:cl}
На вопрос о том, а надо ли оно, ответ приходит после медитации над следующими положениями:
\begin{itemize}
\item программируемый язык программирования;
\item код есть данные;
\item предельно динамичный;
\item мультипарадигменный;
\item идеален для инкрементальной интерактивной разработки;
\item макросы;
\item условия и перезапуски (ошибки можно отлаживать интерактивно!);
\item мощная объектная система, \emph{CLOS};
\item метаобъектный протокол.
\end{itemize}
\section{Функции}
В простейшем случае функции определяются с помощью макроса \lstinline{DEFUN}. Типичное определение функции:
\begin{cllst}{}{}
(defun function-name (parameter*)
"Optional documentation string"
body*)
\end{cllst}
Как правило, имена функций содержат только буквы, цифры и знак ``минус''. Иногда имя может содержать даже пробельный символ.
Список параметров функции определяет переменные, которые будут использоваться для хранения аргументов, переданных при вызове функции. Различают обязательные, необязательные, множественные, и именованные (keyword) параметры.
За списком параметров может находиться строка, которая описывает назначение функции. После того, как функция определена, эта строка (строка документации) будет ассоциирована с именем функции и может быть позже получена с помощью функции \lstinline{DOCUMENTATION}.
Тело \lstinline{DEFUN} состоит из любого числа выражений CL. При вызове функции они вычисляются по порядку, и результат вычисления последнего выражения возвращается, как значение функции. Для возврата из любой точки функции может использоваться специальный оператор \lstinline{RETURN-FROM}.
\begin{cllst}{}{}
(defun verbose-sum (x y)
"Sum any two numbers after printing a message."
(format t "Summing ~d and ~d.~%" x y)
(+ x y))(defun foo ()
(documentation 'verbose-sum 'function) => "Sum any two numbers after printing a message."
\end{cllst}
\subsection{Параметры функций}
Есть несколько типов параметров функций.
\subsubsection{Обязательные параметры}
Если список параметров является простым списком символов (имён переменных), то параметры называются обязательными. Когда функция вызывается, её должно быть передано ровно по одному аргументу для каждого из обязательных параметров. Каждый параметр связывается с соответствующим аргументом. Если функция вызывается с меньшим или большим количеством аргументов, чем требуется, то CL сообщит об ошибке.
\subsubsection{Необязательные параметры}
Параметры могут иметь значения по умолчанию. При вызове функции можно не использовать такие параметры.
\begin{cllst}{}{}
(defun foo (a b &optional c d)
(list a b c d))
(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{cllst}
Если значение по умолчанию не указано при определении функции, то значением по умолчанию является \lstinline{NIL}. Значение по умолчанию задаётся заменой имени параметра на список, состоящий из имени и выражения. Выражение будет вычислено при вызове функции, если вызывающий не указал значения для данного параметра.
\begin{cllst}{}{}
(defun foo (a &optional (b 10))
(list a b))
(foo 1 2) => (1 2)
(foo 1) => (1 10)
\end{cllst}
В выражениях, используемых для определения необязательных параметров, можно ссылаться на параметры, ранее встречавшиеся в списке параметров.
\begin{cllst}{}{}
(defun make-rectangle (width &optional (height width))
...)
\end{cllst}
Иногда необходимо знать, задано ли значение необязательного параметра вызывающей стороной или используется значение по умолчанию. Для этого надо добавить в список, определяющий параметр, третьим элементом имя переменной. Новая переменная будет равна \lstinline{T}, если вызывающая сторона задала значение по умолчанию при вызове, и будет равна \lstinline{NIL} в ином случае.
\begin{cllst}{}{}
(defun foo (a b &optional (c 3 c-supplied-p))
(list a b c c-supplied-p))
(foo 1 2) => (1 2 3 NIL)
(foo 1 2 3) => (1 2 3 T)
(foo 1 2 4) => (1 2 4 T)
\end{cllst}
\subsubsection{Остаточный (rest) параметр}
Если требуется определить функцию, которой можно передавать переменное количество аргументов, можно указать остаточный параметр. Этот параметр будет равен списку всех аргументов, которые остались после связывания обязательных и необязательных параметров.
\begin{cllst}{}{}
(defun + (&rest numbers) ...)
\end{cllst}
\subsubsection{Именованные параметры}
Для того, чтобы задать именованные параметры, необходимо после всех требуемых, необязательных и остаточных параметров, указать символ \lstinline{&key} и затем перечислить любое количество спецификаторов именованных параметров.
\begin{cllst}{}{}
(defun foo (&key a b c)
(list a b c))
(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{cllst}
Также как и для необязательных параметров, для именованных параметров можно задавать выражение для вычисления значения по умолчанию и имя supplied-p-переменной. Также в варажении, которое используется для вычисления значения по умолчанию, можно ссылаться на параметры, указанные ранее.
\begin{cllst}{}{}
(defun foo (&key (a 0) (b 0 b-supplied-p) (c (+ a b)))
(list a b c b-supplied-p))
(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{cllst}
Также если необходимо, чтобы вызывающий использовал имена аргументов, отличающиеся от имен параметров, то вы можете заменить имя параметра на список, содержащий имя, которое будет использоваться при вызове, и имя параметра.
\begin{cllst}{}{}
(defun foo (&key ((:apple a)) ((:box b) 0) ((:charlie c) 0 c-supplied-p))
(list a b c c-supplied-p))
(foo :apple 10 :box 20 :charlie 30) => (10 20 30 T)
\end{cllst}
\subsubsection{Совместное использование различных типов параметров}
Использование всех четырех типов параметров в одной функции хоть и возможно, но встречается редко. Когда используется более одного типа параметров, они должны быть объявлены в порядке, в котором они обсуждались здесь: сначала идут обязательные параметры, затем — необязательные, потом — остаточный, и в заключение — именованные. Но обычно в функциях, которые используют несколько типов параметров, комбинируют обязательные параметры с одним из других типов параметров. Или используют вместе необязательные и остаточные параметры. Два других сочетания – необязательных или остаточных параметров с именованными параметрами — могут привести к очень удивительному поведению функции.
\begin{cllst}{}{}
(defun foo (x &optional y &key z)
(list x y z))
(foo 1 2 :z 3) => (1 2 3)
(foo 1) => (1 nil nil)
(foo 1 :z 3) => ERROR
(defun foo (&rest rest &key a b c)
(list rest a b c))
(foo :a 1 :b 2 :c 3) => ((:A 1 :B 2 :C 3) 1 2 3)
\end{cllst}
\subsection{Возврат значений из функций}
Как правило, результатом вычисления функции является результат последнего вычисленного выражения в теле функции. Иногда необходимы исключения из этого правила. Для этого можно использовать специальный оператор \lstinline{RETURN-FROM}, который предназначен для немедленного возвращения любого значения из функции.
\begin{cllst}{}{}
(defun foo (n)
(dotimes (i 10)
(dotimes (j 10)
(when (> (* i j) n)
(return-from foo (list i j))))))
\end{cllst}
\subsection{Функции высшего порядка}
В CL функции являются первоклассными объектами. При определение функции с помощью \lstinline{DEFUN} происходит две вещи:
\begin{enumerate}
\item Создаётся новый объект-функция.
\item Создаётся новое имя, с которым ассоциирован объект-функция.
\end{enumerate}
Чтобы получить объект-функцию по имени нужно использовать \lstinline{FUNCTION} или же \lstinline{#'}.
\begin{cllst}{}{}
(defun foo (x) (* 2 x))
(function foo) => #<FUNCTION FOO>
#'foo => #<FUNCTION FOO>
\end{cllst}
Полученный объект-функцию можно выполнить, используя \lstinline{FUNCALL} или \lstinline{APPLY}. Они отличаются тем, как они получают аргументы, которые будут переданы вызываемой функции. \lstinline{FUNCALL} — это функция, используемая, когда известно количество аргументов, которые будут переданы функции. Первым аргументом \lstinline{FUNCALL} является запускаемый объект-функция, а оставшиеся аргументы передаются данному объекту-функции при вызове.
Следующая функция демонстрирует использование \lstinline{FUNCALL}. Она принимает объект-функцию в качестве аргумента и рисует простую текстовую диаграмму значений, возвращенных переданной функцией, вызываемой для значений от \lstinline{min} до \lstinline{max} с шагом \lstinline{step}.
\begin{cllst}{}{}
(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 "~%")))
CL-USER> (plot #'exp 0 4 1/2)
*
*
**
****
*******
************
********************
*********************************
******************************************************
NIL
\end{cllst}
Первым аргументом \lstinline{APPLY} является также объект-функция, а вторым аргументом она принимает список. Затем \lstinline{APPLY} применяет объект-функцию к значениям в списке.
\begin{cllst}{}{}
(apply #'plot '(#'exp 0 4 1/2))
(apply #'plot #'exp '(0 4 1/2))
\end{cllst}
\subsection{Анонимные функции}
Анонимные функции определяются с помощью \lstinline{LAMBDA}.
\begin{cllst}{}{}
(lambda (parameters) body)
(funcall #'(lambda (x y) (+ x y)) 2 3) => 5
((lambda (x y) (+ x y)) 2 3) => 5
\end{cllst}
\section{Переменные}
CL поддерживает два вида переменных: \emph{лексические} и \emph{динамические} (\emph{специальные}).
В CL переменные не типизированы. Переменная может содержать значения любого типа, а сами значения содержат информацию о типе, которая может быть использована для проверки типов во время выполнения.
Одной из форм, позволяющих вводить новые переменные, является \lstinline{LET}. Эта форма имеет следующий вид:
\begin{cllst}{}{}
(let (variable*)
body-form*)
\end{cllst}
где каждая \lstinline{variable} является либо списком, состоящим из имени переменной и начального значения, либо просто именем переменной (в таком случае значение переменной равно \lstinline{NIL}).
\begin{cllst}{}{}
(let ((x 10) (y 20) z)
(list x y z)) => (10 20 NIL)
\end{cllst}
Сначала вычисляются все выражения, результаты которых будут использоваться в качестве начальных значений. Затем создаются и инициализируются в начальные значения переменные. После чего выполняются формы из \lstinline{body-form*}.
\begin{cllst}{}{}
(defun foo (x)
(format t "Parameter: ~a~%" x)
(let ((x 2))
(format t "External LET: ~a~%" x)
(let ((x 3))
(format t "Internal LET: ~a~%" x))
(format t "External LET: ~a~%" x))
(format t "Parameter: ~a~%" x))
CL-USER> (foo 1)
Parameter: 1
External LET: 2
Internal LET: 3
External LET: 2
Parameter: 1
NIL
\end{cllst}
Ещё одной связывающей формой является вариант \lstinline{LET}: \lstinline{LET*}. Различие состоит в том, что в \lstinline{LET} имена переменных могут быть использованы только в теле \lstinline{LET}, а в \lstinline{LET*} формы начальных значений могут ссылаться на переменные, введенные ранее.
\subsection{Замыкания и лексические переменные}
\begin{cllst}{}{}
(let ((count 0))
(list
#'(lambda () (incf count))
#'(lambda () (decf count))
#'(lambda () count)))
(funcall (first *count-fns*)) => 1
(funcall (first *count-fns*)) => 2
(funcall (second *count-fns*)) => 1
(funcall (third *count-fns*)) => 1
\end{cllst}
Анонимная функция называется \emph{замыканием (closure)}, потому что она замыкается вокруг привязки, созданной \lstinline{LET}. В замыканиях захватывается не значение переменной, а привязка. Поэтому замыкание может не только иметь доступ ко значению переменной, вокруг которой оно замкнуто, но и присваивать ей новые значения.
\subsection{Динамические (специальные) переменные}
CL предоставляет два способа создания динамических переменных: \lstinline{DEFVAR} и \lstinline{DEFPARAMETER}. Единственное отличие в том, что \lstinline{DEFVAR} не изменит значение уже определенной переменной, а \lstinline{DEFPARAMETER} — изменит. Обе формы принимают имя переменной, начальное значение и необязательную строку документации.
Принято, что имена динамических переменных начинаются и заканчиваются ``звёздочкой''.
\begin{cllst}{}{}
(defvar *count* 0 "Number of created widgets so far.")
*count* => 0
(defvar *count* 1)
*count* => 0
(defparameter *count* 2)
*count* => 2
\end{cllst}
Форма \lstinline{DEFVAR} может использоваться без начального значения для определения переменной без значения. Такая переменная называется \emph{несвязанной (unbound)}.
Присваивание нового значения динамической переменной в какой-либо связывающей форме приводит к тому, что весь код, который находится в теле этой связывающей форме, и код, который вызывается из кода в теле, будет видеть новое значение динамической переменной (отсюда и название).
\begin{cllst}{}{}
(defun hello-world ()
(format *standard-output* "Hello, world!~%"))
CL-USER> (hello-world)
Hello, world!
NIL
(with-open-file (stream #p"/tmp/scream.txt" :direction :output)
(let ((*standard-output* stream))
(hello-world)))
CL-USER> (hello-world)
Hello, world!
NIL
\end{cllst}
В итоге \texttt{/tmp/scream.txt} содержит:
\begin{plainlst}{}{}
% cat scream.txt
Hello, world!
\end{plainlst}
Также возможно локально объявить имя динамическим (специальным). Если в связывающей форме объявить имя специальным, то привязка, созданная для этой переменной, будет динамической, а не лексической. Другой код может локально определить имя специальным, чтобы обращаться к динамической привязке.
\subsection{Константы}
Все константы являются глобальными и определяются с помощью \lstinline{DEFCONSTANT}. Базовая форма \lstinline{DEFCONSTANT} подобна \lstinline{DEFPARAMETER}.
\begin{cllst}{}{}
(defconstant name initial-value-form [ documentation-string ])
\end{cllst}
К константе можно только обращаться. При попытке изменить константу зависит от реализации.
\subsection{Присваивание}
Чтобы присвоить новое значение переменной, следует использовать макрос \lstinline{SETF}, являющийся в CL оператором присваивания:
\begin{cllst}{}{}
(setf place value)
\end{cllst}
\lstinline{SETF} раскрывается (expand) в зависимости от того, чем является \lstinline{place}. Когда \lstinline{place} является переменной, то он раскрывается в вызов специальной формы \lstinline{SETQ}, который имеет доступ как лексическим, так и к динамическим переменным. \lstinline{SETF} возвращает последнее присвоенное значение.
\begin{cllst}{}{}
(defvar *x*)
(defvar *y*)
(setf *x* 10) => 10
(setf *x* 1 y 2) => 2
(setf *x* (setf *y* (random 10))) => 6
\end{cllst}
\subsubsection{Обобщённое присваивание}
Местом может быть не только привязки переменных. \lstinline{SETF} может присвоить значение любому месту (place). В CL можно присваивать, например, элементу списка.
\begin{cllst}{}{}
(let ((x 1))
(setf x 10)
x) => 10
(let ((x '(2 2 3)))
(setf (first x) 1)
x) => (1 2 3)
(let ((x #(2 2 3)))
(setf (aref x 0) 1)
x) => #(1 2 3)
(let ((x (make-hash-table)))
(setf (gethash 'key x) 10)
(gethash 'key x)) => 10
\end{cllst}
Новые места можно определять с помощью \lstinline{DEFSETF} и \lstinline{DEFINE-SETF-EXPANDER}.
\section{Макросы}
Макрос — определение того, как переданные S-выражения будут превращены в формы CL. С помощью макросов можно создавать новые синтаксические конструкции или даже встроенные проблемно-ориентированные языки (embedded domain-specific languages).
Когда выражение Lisp содержит вызов макроса, компилятор Lisp, вместо вычисления аргументов и передачи их в функцию, передает аргументы, не вычисляя их, в код макроса, который, в свою очередь, возвращает новое выражение на Lisp, которое затем вычисляется в месте исходного вызова макроса.
Поэтому следуем различать \emph{время раскрытия макросов} и \emph{время выполнения}, когда уже все макросы раскрыты и выполняется обычный код. Код, работающий во время раскрытия макросов, запускается в окружении, сильно отличающимся от окружения кода, работающего во время выполнения. А именно: во время раскрытия макросов не существует способа получить доступ к данным, которые будут существовать во время выполнения. Код, работающий во время раскрытия макросов, может работать только с данными, являющимися неотъемлемой частью исходного кода.
Предположим, что макрос \lstinline{WHEN} определен следующим образом:
\begin{cllst}{}{}
(defmacro when (condition &rest body)
`(if ,condition (progn ,@body)))
\end{cllst}
И допустим, что имеется следующая функция \lstinline{FOO}:
\begin{cllst}{}{}
(defun foo (x)
(when (> x 10) (print 'big)))
\end{cllst}
В этом случае, параметр \lstinline{condition} будет связан с формой \lstinline{(> x 10)}, а форма \lstinline{(print 'big)} будет собрана в список, который станет значением параметра \lstinline{body}. После раскрытия получится следующий код:
\begin{cllst}{}{}
(if (> x 10) (progn (print 'big)))
\end{cllst}
Синтаксис \lstinline{DEFMACRO} выглядит так:
\begin{cllst}{}{}
(defmacro name (parameter*)
"Optional documentation string."
body-form*)
\end{cllst}
\subsection{Макропараметры}
Список параметров макроса является списком \emph{деструктурируемых} параметров. Внутри списка деструктурируемых параметров имя параметра может быть заменено вложенным списком параметров. Параметры в таком списке будут получать свои значения из элементов переданного аргумента-списка. Список деструктурируемых параметров также предоставляет проверку ошибок.
Другой особенностью списка параметров макросов является то, что вы можете использовать \lstinline{&body} как синоним \lstinline{&rest}. Семантически \lstinline{&body} и \lstinline{&rest} эквивалентны.
\begin{cllst}{Пример макроса}{}
(defun primep (number)
(when (> number 1)
(loop for fac from 2 to (isqrt number) never (zerop (mod number fac)))))
(defun next-prime (number)
(loop for n from number when (primep n) return n))
(defmacro do-primes ((var start end) &body body)
`(do ((,var (next-prime ,start) (next-prime (1+ ,var))))
((> ,var ,end))
,@body))
\end{cllst}
\subsection{Раскрытие}
Выражения квазицитирования подобны выражениям цитирования за исключением того, что можно ``раскавычить'' (вычислять) определенные подвыражения, предваряя их запятой, за которой возможно следует знак ``at'' (\lstinline{@}). Без этого знака запятая вычисляет и влючает как есть значение следующего за ней подвыражения. Со знаком ``at'' значение, которое должно быть списком, ``вклеивается'' в обрамляющий список.
По сути квазицитирование — это удобный синтаксис для написания кода, который генерирует списки. Когда процедура чтения считывает выражение квазицитирования, она преобразует его в код, который генерирует соответствующую списковую структуру.
\begin{table}[h!]
\caption{Примеры квазицитирования}
\begin{center}
\begin{tabular}{lll}
\toprule
Квазицитирования & Эквивалентный код & Результат \\
\midrule
\texttt{`(a (+ 1 2) c)} & \texttt{(list 'a '(+ 1 2) 'c)} & \texttt{(a (+ 1 2) c)} \\
\texttt{`(a ,(+ 1 2) c)} & \texttt{(list 'a (+ 1 2) 'c)} & \texttt{(a 3 c)} \\
\texttt{`(a (list 1 2) c)} & \texttt{(list 'a '(list 1 2) 'c)} & \texttt{(a (list 1 2) c)} \\
\texttt{`(a ,(list 1 2) c)} & \texttt{(list 'a (list 1 2) 'c)} & \texttt{(a (1 2) c)} \\
\texttt{`(a ,@(list 1 2))} & \texttt{(append (list 'a) (list 1 2))} & \texttt{(a 1 2 c)} \\
\bottomrule
\end{tabular}
\end{center}
\end{table}
Для проверки раскрытия макроса можно использовать две функции: \lstinline{MACROEXPAND-1} и \lstinline{MACROEXPAND}. Функция \lstinline{MACROEXPAND-1} получает любое выражение в качестве аргумента и возвращает результат одного шага раскрытия макроса. Функция \lstinline{MACROEXPAND} продолжает раскрытие результата пока первый элемент получаемого раскрытия является именем макроса.
\subsection{Устранение протечек}
Внутренние детали реализации макроса могут ``протекать'' тремя путями.
Предположим, что макрос \lstinline{DO-PRIMES} вызван с выражением \lstinline{(random 100)}:
\begin{cllst}{}{}
(do-primes (p 0 (random 100))
(format t "~d " p))
\end{cllst}
Если воспользоваться функцией \lstinline{MACROEXPAND-1}, то можно заметить, что выражение \lstinline{(random 100)} будет вычисляться при каждой проверке условия в макросе \lstinline{DO}:
\begin{cllst}{}{}
CL-USER> (macroexpand-1 '(do-primes (p 0 (random 100)) (format t "~d " p)))
(DO ((P (NEXT-PRIME 0) (NEXT-PRIME (1+ P))))
((> P (RANDOM 100)))
(FORMAT T "~d " P))
T
\end{cllst}
Исправить это поведение можно вычислением \lstinline{end} единожды и сохранением результата вычисления.
\begin{cllst}{}{}
(defmacro do-primes ((var start end) &body body)
`(do ((ending-value ,end)
(,var (next-prime ,start) (next-prime (1+ ,var))))
((> ,var ending-value))
,@body))
\end{cllst}
Но данное исправление одной проблемы вводит две других.
Так как переменные цикла \lstinline{DO} инициализируются в том порядке, в каком они определены в тексте, то при раскрытии макроса выражение, переданное как \lstinline{end}, будет вычислено перед выражением, переданным как \lstinline{start}. Это устраняется изменением порядка определения двух переменных.
\begin{cllst}{}{}
(defmacro do-primes ((var start end) &body body)
`(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
(ending-value ,end))
((> ,var ending-value))
,@body))
\end{cllst}
Последняя проблема связана с именем переменной \lstinline{ending-value}. Проблема в том, что это имя может быть использовано во внешнем пользовательском кодом. Следующий вызов do-primes не работает корректно из-за данной ``протечки'':
\begin{cllst}{}{}
(do-primes (ending-value 0 10)
(print ending-value))
\end{cllst}
С помощью \lstinline{MACROEXPAND-1} легко отыскивается причина неверного поведения:
\begin{cllst}{}{}
(do ((ending-value (next-prime 0) (next-prime (1+ ending-value)))
(ending-value 10))
((> ending-value ending-value))
(print ending-value))
\end{cllst}
Для устранения этой проблемы нужна возможность создавать уникальный символ. Функция \lstinline{GENSYM} возвращает уникальный символ при каждом вызове. Возвращённый символ не интернируется в какой-либо пакет. Код, использующий \lstinline{GENSYM}, не должен быть частью раскрытия.
\begin{cllst}{}{}
(defmacro do-primes ((var start end) &body body)
(let ((ending-value-name (gensym)))
`(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
(,ending-value-name ,end))
((> ,var ,ending-value-name))
,@body)))
\end{cllst}
Теперь вышеприведённое использование макроса будет раскрыто в подобное:
\begin{cllst}{}{}
(do ((ending-value (next-prime 0) (next-prime (1+ ending-value)))
(#:g2141 10))
((> ending-value #:g2141))
(print ending-value))
\end{cllst}
\subsection{Примеры макросов}
Было бы неплохо писать код вроде следующего вместо использования \lstinline{GENSYM}:
\begin{cllst}{}{}
(defmacro do-primes ((var start end) &body body)
(with-gensyms (ending-value-name)
`(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
(,ending-value-name ,end))
((> ,var ,ending-value-name))
,@body)))
\end{cllst}
и получить \lstinline{DO-PRIMES}, эквивалентный предыдущей версии.
\begin{cllst}{}{}
(defmacro with-gensyms ((&rest names) &body body)
`(let ,(loop for n in names collect `(,n (gensym)))
,@body))
\end{cllst}
Можно посмотреть, как раскрывается \lstinline{loop}-часть:
\begin{cllst}{}{}
CL-USER> (loop for n in '(a b c) collect `(,n (gensym)))
((A (GENSYM)) (B (GENSYM)) (C (GENSYM)))
\end{cllst}
\section{Примитивные типы данных}
\subsection{Числа}
CL поддерживает несколько числовых типов:
\begin{itemize}
\item целые числа, максимальная величина которых аппаратно-зависима;
\item целые числа произвольной величины;
\item числа с плавающей точкой;
\item дроби;
\item комплексные числа.
\end{itemize}
\subsubsection{Запись}
Примеры записи и печати рациональных чисел:
\begin{cllst}{}{}
CL-USER> (list +1 -1 2/1 3/2 6/4 #xA #b01001 #b1010/1011 #o777 #36rABFG 1.)
(1 -1 2 3/2 3/2 10 9 10/11 511 481372 1)
\end{cllst}{}
Во время чтения дроби сокращаются, если это возможно. Как видно из примера, возможна запись чисел с основанием отличным от $10$. Если числу предшествует \lstinline{#B} или \lstinline{#b}, то число считывается как двоичное. Строки \lstinline{#O} или \lstinline{#o} используются для восьмеричных чисел, а \lstinline{#X} или \lstinline{#x} используются для шестнадцатеричных. Возможно записывать числа в системах с другими основаниями (от 2 до 36) с указанием префикса \lstinline{#nR}, где \lstinline{n} — основание (всегда записывается в десятичном виде). В качестве дополнительных ``цифр'' используются буквы A-Z или a-z.
Примеры записи и печати чисел с плавающей точкой:
\begin{cllst}{}{}
CL-USER> (list 1.0 1e0 1d0 0.1 .1 1e-3)
(1.0 1.0 1.0d0 0.1 0.1 0.001)
\end{cllst}
В CL есть четыре подтипа чисел с плавающей точкой: \lstinline{short}, \lstinline{single}, \lstinline{double} и \lstinline{long}. Количество бит, отведённых для представления значений каждого типа, зависит от реализации и аппаратной платформы. Маркеры экспоненты: \lstinline{s}, \lstinline{f}, \lstinline{d,} \lstinline{l} обозначают использование \lstinline{short}, \lstinline{single}, \lstinline{double} и \lstinline{long}. Буква \lstinline{e} указывает, что должно использоваться представление по умолчанию.
Примеры записи и печати комплексных чисел:
\begin{cllst}{}{}
CL-USER> (list #c(2 1) #c(2/3 3/4) #c(2 1.0) #c(2.0 1.0d0)
#c(1/2 1.0) #c(3 0) #c(3.0 0.0) #c(1/2 0) #c(-6/3 0))
s
(#C(2 1) #C(2/3 3/4) #C(2.0 1.0) #C(2.0d0 1.0d0)
#C(0.5 1.0) 3 #C(3.0 0.0) 1/2 -2)
\end{cllst}
Синтаксис: префикс \lstinline{#C} или \lstinline{#c}, за которым следует список из двух чисел, представляющих реальную и мнимую часть комплексного числа.
\subsubsection{Математические операции}
\begin{cllst}{}{}
(+) => 0 (+ 1 2) => 3
(+ 1 2 3) => 6 (- 5 4) => 1
(- 2) => -2 (- 10 3 5) => 2
(*) => 1 (* 2 3 4) => 24
(/ 10 5 2) => 1 (/ 2 3) => 2/3
(+ #c(1 2) 3) => #c(4 2)
\end{cllst}
В CL есть четыре вида функций для усечения и округления для вещественных чисел (рациональных или с плавающей точкой) в целые числа:
\begin{enumerate}
\item \lstinline{FLOOR} усекает число в сторону отрицательной бесконечности, возвращая наибольшее целое, меньшее или равное аргументу.
\item \lstinline{CEILING} усекает число в сторону положительной бесконечности, возвращая наименьшее целое, большее или равное аргументу.
\item \lstinline{TRUNCATE} усекает число в сторону нуля, ведя себя как \lstinline{FLOOR} для положительных аргументов, и как \lstinline{CEILING} для отрицательных.
\item \lstinline{ROUND} округляет число до ближайшего целого. Если аргумент находится ровно между двумя целыми числами, то округление происходит в сторону ближайшего четного числа.
\end{enumerate}
Функция \lstinline{MOD} и \lstinline{REM} возвращают модуль и остаток деления с усечением чисел. Эти две функции соотносятся с \lstinline{FLOOR} и \lstinline{TRUNCATE} следующим образом:
\begin{cllst}{}{}
(+ (* (floor (/ x y)) y) (mod x y)) == x
(+ (* (truncate (/ x y)) y) (rem x y)) == x
\end{cllst}
\subsubsection{Сравнение чисел}
Функция \lstinline{=} является предикатом для проверки равенства чисел, который игнорирует разницу в типах, то есть сравнивает математически. Функции \lstinline{<}, \lstinline{>}, \lstinline{<=} и \lstinline{>=} сравнивают рациональные числа и числа с плавающей точкой.
\begin{cllst}{}{}
(= 1 1.0 #c(1.0 0.0) #c(1 0)) => T
(/= 1 1) => NIL (/= 1 2) => T
(/= 1 2 3) => T (/= 1 2 3 1.0) => NIL
(<= 2 3 3 4) => T (<= 2 3 4 3) => NIL
\end{cllst}
Другими часто используемыми функциями и предикатами являются: \lstinline{ZEROP}, \lstinline{MINUSP}, \lstinline{PLUSP}, \lstinline{EVENP}, \lstinline{ODDP}, \lstinline{MIN} и \lstinline{MAX}.
\subsection{Знаки}
В CL знаки являются отдельным типом объектов, а не числами. Синтаксис чтения: префикс \lstinline{#\}, за которым следует нужный знак. После \lstinline{#\} может использоваться любой знак. Пробел и другие аналогичные знаки можно записывать другим способом: вместа знака за \lstinline{#\} следует название знака. Список поддерживаемых имен зависит от набора знаков и реализации Lisp, но все реализации поддерживают имена \lstinline{Space} и \lstinline{Newline}. Другими распространёнными, но не стандартными именами являются \lstinline{Tab}, \lstinline{Page}, \lstinline{Rubout}, \lstinline{Linefeed}, \lstinline{Return} и \lstinline{Backspace}.
Для сравнения знаков, не учитывая их регистр, следует использовать функции: \lstinline{CHAR=}, \lstinline{CHAR=/}, \lstinline{CHAR<}, \lstinline{CHAR>}, \lstinline{CHAR<=} и \lstinline{CHAR>=}. Для сравнения знаков, учитывая их регистр, следует использовать функции: \lstinline{CHAR-EQUAL}, \lstinline{CHAR-NOT-EQUAL}, \lstinline{CHAR-LESSP}, \lstinline{CHAR-GREATERP}, \lstinline{CHAR-NOT-GREATERP} и \lstinline{CHAR-NOT-LESSP}.
\subsection{Строки}
Строка является одномерным массивом знаков. Строки записываются, заключенными в двойные кавычки. Можно включить в строку любой знак, поддерживаемый набором знаков, за исключением двойной кавычки (\lstinline{"}) и обратного слеша (\lstinline{\}). Эти два знака необходимо экранировать.
Для сравнения строк, не учитывая их регистр, следует использовать функции: \lstinline{STRING=}, \lstinline{STRING=/}, \lstinline{STRING<}, \lstinline{STRING>}, \lstinline{STRING<=} и \lstinline{STRING>=}. Для сравнения строк, учитывая их регистр, следует использовать функции: \lstinline{STRING-EQUAL}, \lstinline{STRING-NOT-EQUAL}, \lstinline{STRING-LESSP}, \lstinline{STRING-GREATERP}, \lstinline{STRING-NOT-GREATERP} и \lstinline{STRING-NOT-LESSP}. Эти функции могут сравнивать только две строки, в отличии от аналогов для чисел.
Каждая из этих функций имеет четыре именованных параметра: \lstinline{:start1}, \lstinline{:end1}, \lstinline{:start2} и \lstinline{:end2}.
\begin{cllst}{}{}
(string= "foobarbaz" "quuxbarfoo" :start1 3 :end1 6 :start2 4 :end2 7) => T
\end{cllst}
\section{Коллекции}
В CL есть два основных вида коллекций: последовательности и хеш-таблицы. Последовательности в свою очередь подразделяются на: векторы, списки и строки. В этой главе будут рассматриваться в основном векторы, но все рассматриваемые здесь функции для работы с последовательностями, естественно, применимы к спискам и строкам.
\subsection{Векторы и массивы}
Векторы являются последовательностью с доступом по целочисленному индексу. Векторы могут иметь фиксированный размер либо же изменяемый. Создать вектор можно функцией \lstinline{VECTOR} или же специальным синтаксисом \lstinline{#(...)}. Однако, что произойдёт в результате изменения вектора, созданного вторым способом, стандартом не определено.
\begin{cllst}{}{}
(vector) => #()
(vector 1) => #(1)
(vector 1 2) => #(1 2)
\end{cllst}
Функцией \lstinline{MAKE-ARRAY} можно создавать массивы любой размерности. Обязательным аргументом является список размерностей либо число, если массив одномерен, то есть является вектором. Без других аргументов создатся массив с неициализированными элементами. Поведение при доступе к неициализированным элементам неопределено. Используя параметр \lstinline{:initial-element} можно создать массив, проинициализированный определённым значением.
\begin{cllst}{}{}
(make-array 5 :initial-element nil) => #(NIL NIL NIL NIL NIL)
\end{cllst}
Размер вектора хранится в \emph{указателе заполнения (fill pointer)}. Указать его начальное значение можно параметром \lstinline{:fill-pointer}.
\begin{cllst}{}{}
(make-array 5 :fill-pointer 0) => #()
(make-array 5 :fill-pointer 3) => #(0 0 0)
(make-array 5 :fill-pointer 3 :initial-element nil) => #(NIL NIL NIL)
\end{cllst}
Создать массив изменяемым можно параметром \lstinline{:adjustable}. Следующий массив может не имеет элементов, памяти выделено изначально под 5 элементов, но добавлять в массив можно больше пяти элементов.
\begin{cllst}{}{}
(make-array 5 :fill-pointer 0 :adjustable t) ==> #()
\end{cllst}
Примеры работы с векторами различных свойств:
\begin{cllst}{}{}
(defparameter *array* (make-array 2 :fill-pointer 0))
(vector-push 0 *array*) => 0
(vector-push 1 *array*) => 1
(vector-push 2 *array*) => NIL
*array* => #(0 1)
(defparameter *array*
(make-array 2 :fill-pointer 1 :initial-element 0 :adjustable t))
(vector-push 1 *array*) => 1
(vector-push 2 *array*) => NIL
*array* => #(0 1)
(vector-push-extend 2 *array*) => 2
*array* => #(0 1 2)
\end{cllst}
Все созданные массивы и векторы могут хранить элементы любого типа. Но можно создавать массивы, которые хранят только элементы определённого типа, параметром \lstinline{:element-type}.
\begin{cllst}{}{}
(make-array 5 :fill-pointer 0 :adjustable t :element-type 'character) => ""
(make-array 3 :initial-contents '(0 1 0) :element-type 'bit) => #*010
\end{cllst}
Последний вектор является битовым.
\subsection{Функции для последовательностей}
\begin{table}[h!]
\caption{Описание функций}
\begin{center}
\begin{tabular}{lp{5cm}p{6.5cm}}
\toprule
Название & Обязательные аргументы & Результат \\
\midrule
\texttt{COUNT} & Объект и последовательность & Число вхождений в последовательности \\
\texttt{FIND} & Объект и последовательность & Объект или \texttt{NIL} \\
\texttt{POSITION} & Объект и последовательность & Индекс ячейки в последовательности или \texttt{NIL} \\
\texttt{REMOVE} & Удаляемый объект и последовательность & Последовательность, из которой удалены указанные объекты \\
\texttt{SUBSTITUTE} & Новый объект, заменяемый объект и последовательность & Последовательность, в которой указанные объекты заменены на новые \\
\bottomrule
\end{tabular}
\end{center}
\end{table}
\begin{table}[h!]
\caption{Описание некоторых именованных параметров}
\begin{center}
\begin{tabular}{lp{8.5cm}p{2cm}}
\toprule
Параметр & Описание & Значение по умолчанию \\
\midrule
\texttt{:test} & Функция двух аргументов, используемая для сравнения элементов (или значений, извлеченных функцией \texttt{:key}) с указанным объектом. & \texttt{EQL} \\
\texttt{:key} & Функция одного аргумента, используемая для извлечения значения из элемента последовательности. \texttt{NIL} указывает на использование самого элемента. & \texttt{NIL} \\
\texttt{:start} & Начальный индекс (включительно) обрабатываемой последовательности. & $0$ \\
\texttt{:end} & Конечный индекс $(n - 1)$ обрабатываемой последовательности. \texttt{NIL} указывает на конец последовательности. & \texttt{NIL} \\
\texttt{:from-end} & Если имеет истинное значение, то последовательность будет обрабатываться в обратном порядке. & \texttt{NIL} \\
\texttt{:count} & Число, указывающее количество удаляемых или заменяемых элементов, или \texttt{NIL} для всех элементов (только для \texttt{REMOVE} и \texttt{SUBSTITUTE}). & \texttt{NIL} \\
\bottomrule
\end{tabular}
\end{center}
\end{table}
\begin{cllst}{Примеры использования}{}
(length #(1 2 3)) => 3
(elt 1 #(1 2 3)) => 2
(count 1 #(1 2 1 2 3 1 2 3 4)) => 3
(remove 1 #(1 2 1 2 3 1 2 3 4)) => #(2 2 3 2 3 4)
(remove 1 '(1 2 1 2 3 1 2 3 4)) => (2 2 3 2 3 4)
(remove #\a "foobarbaz") => "foobrbz"
(substitute 10 1 #(1 2 1 2 3 1 2 3 4)) => #(10 2 10 2 3 10 2 3 4)
(substitute 10 1 '(1 2 1 2 3 1 2 3 4)) => (10 2 10 2 3 10 2 3 4)
(substitute #\x #\b "foobarbaz") => "fooxarxaz"
(find 1 #(1 2 1 2 3 1 2 3 4)) => 1
(find 10 #(1 2 1 2 3 1 2 3 4)) => NIL
(position 2 #(1 2 1 2 3 1 2 3 4)) => 1
(position 9 #(1 2 1 2 3 1 2 3 4)) => NIL
(count "foo" #("foo" "bar" "baz") :test #'string=) => 1
(find 'c #((a 10) (b 20) (c 30) (d 40)) :key #'first) => (C 30)
(find 'a #((a 10) (b 20) (a 30) (b 40)) :key #'first) => (A 10)
(find 'a #((a 10) (b 20) (a 30) (b 40)) :key #'first :from-end t) => (A 30)
(remove #\a "foobarbaz" :count 1) => "foobrbaz"
(remove #\a "foobarbaz" :count 1 :from-end t) => "foobarbz"
\end{cllst}
\subsubsection{ФВП для последовательностей}
CL также предоставляет два набора функций высшего порядка, которые вместо аргумента, используемого для сравнения, получают функцию, которая вызывается для каждого элемента последовательности. Первый набор функций имеет те же имена, что и функции из базового набора, но с добавлением суффикса \lstinline{-IF}. Эти функции подсчитывают, ищут, удаляют и заменяют элементы, для которых аргумент-функция возвращает истинное значение. Другой набор функций отличается тем, что использует суффикс \lstinline{-IF-NOT}, и выполняет те же операции над элементами, для которых функция не возвращает истинного значения.
\begin{cllst}{Примеры}{}
(count-if #'evenp #(1 2 3 4 5)) => 2
(count-if-not #'evenp #(1 2 3 4 5)) => 3
(position-if #'digit-char-p "abcd0001") => 4
(remove-if-not #'(lambda (x) (char= (elt x 0) #\f))
#("foo" "bar" "baz" "foom")) => #("foo" "foom")
(count-if #'evenp #((1 a) (2 b) (3 c) (4 d) (5 e)) :key #'first) => 2
(count-if-not #'evenp #((1 a) (2 b) (3 c) (4 d) (5 e)) :key #'first) => 3
(remove-if-not #'alpha-char-p
#("foo" "bar" "1baz") :key #'(lambda (x) (elt x 0))) => #("foo" "bar")
\end{cllst}
\subsection{Хеш-таблицы}
Хеш-таблицы создаются функцией с говорящим именем \lstinline{MAKE-HASH-TABLE}. Имеет среди прочих один аргумент: \lstinline{:test}. С помощью него можно указать одну из четырёх функций: \lstinline{EQ}, \lstinline{EQL}, \lstinline{EQUAL}, \lstinline{EQUALP}, используемую для сравнения ключей.
Функция \lstinline{GETHASH} обеспечивает доступ к элементам хеш-таблицы.
\begin{cllst}{}{}
(defparameter *h* (make-hash-table))
(gethash 'foo *h*) => NIL
(setf (gethash 'foo *h*) 'quux)
(gethash 'foo *h*) => QUUX
\end{cllst}
\lstinline{GETHASH} возвращает на самом деле два значения. Получить их можно используя макрос \lstinline{MULTIPLE-VALUE-BIND}.
\begin{cllst}{}{}
(defun show-value (key hash-table)
(multiple-value-bind (value present) (gethash key hash-table)
(if present
(format nil "~a is present" value)
(format nil "~a is not present" value))))
\end{cllst}
\begin{cllst}{Функции для хеш-таблиц}{}
(defparameter *h* (make-hash-table))
(setf (gethash 'key1 *h*) 'value1)
(setf (gethash 'key2 *h*) 'value2)
CL-USER> (maphash #'(lambda (k v) (format t "~a => ~a~%" k v)) *h*)
KEY1 => VALUE1
KEY2 => VALUE2
NIL
(loop for k being the hash-keys in *h* using (hash-value v)
do (format t "~a => ~a~%" k v))
\end{cllst}
\section{Списки}
Использование списков в CL очень часто, так как эта структура данных используется для представления, преобразования и порождения кода в макросах. Но не только — об этом следом.
\subsection{Cons-ячейки}
Списки построены на основе \emph{cons-ячеек}. Cons-ячейка (от функции \lstinline{CONS}, используемой для их создания) — это всего лишь пара объектов. Таким образом список состоит из пар, первым элементом которых являются элементы (значения) списка, а второй элемент указывает на последующую ячейку, если таковая присутствует.
\begin{cllst}{}{}
(cons 1 nil) => (1)
(cons 1 (cons 2 nil)) => (1 2)
(cons 1 (cons 2 (cons 3 nil))) => (1 2 3)
\end{cllst}
Для доступа к первому и второму элементам пары используются функции \lstinline{CAR} и \lstinline{CDR}, соответственно.
\begin{cllst}{}{}
(defparameter *cons* (cons 1 2))
(car *cons*) => 1
(cdr *cons*) => 2
(setf (car *cons*) 10) => 10
*cons* => (10 . 2)
(setf (cdr *cons*) 20) => 20
*cons* => (10 . 20)
\end{cllst}
У \lstinline{CAR} и \lstinline{CDR} есть синонимы: \lstinline{FIRST} и \lstinline{REST}.
\subsection{Функции для списков}
Для создания списков есть функция \lstinline{LIST}. Функция \lstinline{APPEND} склеивает списки, при этом все списки копируются кроме последнего.
\begin{cllst}{}{}
(defparameter *list-1* (list 1 2))
(defparameter *list-2* (list 3 4))
(defparameter *list-3* (append *list-1* *list-2*))
(setf (first *list-2*) 0)
*list-2* => (0 4)
*list-3* => (1 2 0 4)
\end{cllst}
Функция \lstinline{NCONC} склеивает списки, не копируя их, а изменяя последние cons-ячейки всех списков, кроме последнего.
В CL есть функции от \lstinline{SECOND} до \lstinline{TENTH}, извлекающие соответствующие элементы списка. Более общая функция \lstinline{NTH} принимает два аргумента: индекс и список, и возвращает $n$-ый (начиная с нуля) элемент списка. Также существует функция \lstinline{NTHCDR}, принимающая индекс и список и возвращающая результат $n$-кратного применения \lstinline{REST} к списку.
28 составных \lstinline{CAR}/\lstinline{CDR} функций составляют ещё одно семейство функций. Имя каждой функции получается подстановкой до 4 букв \lstinline{A} или \lstinline{D} между \lstinline{C} и \lstinline{R}. Каждая \lstinline{A} представляет вызов \lstinline{CAR}, а каждая \lstinline{D} — \lstinline{CDR}.
\begin{table}[h!]
\caption{Другие функции для работы со списками}
\begin{center}
\begin{tabular}{lp{11.5cm}}
\toprule
Имя & Описание \\
\midrule
\texttt{LAST} & Возвращает последнюю cons-ячейку в списке. Если вызывается с целочисленным аргументом $n$, возвращает n ячеек. \\
\texttt{BUTLAST} & Возвращает копию списка без последней cons-ячейки. Если вызывается с целочисленным аргументом $n$, исключает последние $n$ ячеек. \\
\texttt{NBUTLAST} & Похожа на \texttt{BUTLAST}, но изменяет переданный список, не имеет строго заданных побочных эффектов. \\
\texttt{LDIFF} & Возвращает копию списка до заданной cons-ячейки. \\
\texttt{TAILP} & Возвращает \texttt{T}, если переданный объект является cons-ячейкой, которая является частью списка. \\
\texttt{LIST*} & Строит список, содержащий все переданные аргументы кроме последнего, после этого присваивает \texttt{CDR} последней cons-ячейки списка последний аргумент, то есть смесь \texttt{LIST} и \texttt{APPEND}. \\
\texttt{MAKE-LIST} & Строит список из $n$ элементов. Начальные элементы списка — \texttt{NIL} или значение заданное параметром \texttt{:initial-element}. \\
\texttt{REVAPPEND} & Комбинация \texttt{REVERSE} и \texttt{APPEND}: инвертирует первый аргумент, как \texttt{REVERSE} и добавляет второй аргумент. Не имеет строго заданных побочных эффектов. \\
\texttt{NRECONC} & Аналог предыдущей функции; инвертирует первый аргумент, как \texttt{NREVERSE} и добавляет второй аргумент. Не имеет строгих побочных эффектов. \\
\texttt{CONSP} & Проверяет, является ли объект cons-ячейкой. \\
\texttt{ATOM} & Проверяет, является ли объект не cons-ячейкой. \\
\texttt{LISTP} & Проверяет, является ли объект cons-ячейкой или \texttt{NIL} \\
\texttt{NULL} & Проверяет, является ли объект \texttt{NIL}. \\
\bottomrule
\end{tabular}
\end{center}
\end{table}
Просто перечисление функций с говорящими за себя именами: \lstinline{REVERSE}, \lstinline{NREVERSE}, \lstinline{PUSH}, \lstinline{POP}, \lstinline{PUSHNEW}, \lstinline{DELETE}, \lstinline{DELETE-IF}, \lstinline{DELETE-IF-NOT}, \lstinline{DELETE-DUPLICATED}, \lstinline{NSUBSTITUTE}, \lstinline{NSUBSTITUTE-IF} и \lstinline{NSUBSTITUTE-IF-NOT}.
\subsection{Отображение}
Для отображения можно использовать функцию \lstinline{MAP}, так как она работает с любой последовательностью. Специфичными для списков являются функции \lstinline{MAPCAR}, \lstinline{MAPLIST}, \lstinline{MAPCAN}, \lstinline{MAPCON}, \lstinline{MAPC} и \lstinline{MAPL}.
\begin{cllst}{}{}
(mapcar #'(lambda (x) (* 2 x)) (list 1 2 3)) => (2 4 6)
(mapcar #'+ (list 1 2 3) (list 10 20 30)) => (11 22 33)
(maplist #'(lambda (x) x) (list 1 2 3)) => ((1 2 3) (2 3) (3))
(mapcan #'(lambda (x) (and (numberp x) (list x)))
'(a 1 b c 3 4 d 5))
=> (1 3 4 5)
(mapcon #'list '(1 2 3 4)) => ((1 2 3 4) (2 3 4) (3 4) (4))
\end{cllst}
\subsection{Списки как деревья}
Так как списки могут содержать любые значения, а ,следовательно, и списки, то с их помощью можно представлять любого вида деревья. Например, \lstinline{'((nil 1 nil) 2 (nil 3 nil))} представляет собой бинарное дерево поиска, в которое были вставлены последовательно числа $2$, $1$ и $3$.
Для работы с такими деревьями в CL есть несколько специальных функций. Одна из них — \lstinline{COPY-TREE}, которая, в отличие от \lstinline{COPY-LIST}, копирует списки не только вдоль, но и вглубь. Функция \lstinline{TREE-EQUAL} проверяет, равны ли два дерева, что подразумевает одинаковость их форм и равенство содержащихся в них значений.
Функция \lstinline{SUBST} заменяет один элемент дерева другим. Подобное делают функции \lstinline{SUBST-IF}, \lstinline{NSUBST} и \lstinline{NSUBST-IF} с очевидными особенностями.
\begin{cllst}{}{}
(subst 10 1 '(1 2 (3 2 1) ((1 1) (2 2)))) => (10 2 (3 2 10) ((10 10) (2 2)))
\end{cllst}
\begin{cllst}{Обход дерева}{}
(defun preorder (node f)
(when node
(funcall f (first node))
(preorder (second node) f)
(preorder (third node) f)))
(defun inorder (node f)
(when node
(inorder (second node) f)
(funcall f (first node))
(inorder (third node) f)))
(defun postorder (node f)
(when node
(postorder (second node) f)
(postorder (third node) f)
(funcall f (first node))))
(defun level-order (node f)
(loop with level = (list node)
while level
do
(setf level (loop for node in level
when node
do (funcall f (first node))
and collect (second node)
and collect (third node)))))
(defparameter *tree* '(1 (2 (4 (7))
(5))
(3 (6 (8)
(9)))))
(defun show (traversal-function)
(format t "~&~(~A~):~12,0T" traversal-function)
(funcall traversal-function *tree* (lambda (value) (format t " ~A" value))))
(map nil #'show '(preorder inorder postorder level-order))
\end{cllst}
\begin{plainlst}{}{}
preorder: 1 2 4 7 5 3 6 8 9
inorder: 7 4 2 5 1 8 6 9 3
postorder: 7 4 2 5 1 8 6 9 3
level-order: 1 2 3 4 5 6 7 8 9
\end{plainlst}
\subsection{Списки как множества}
Реализация множеств в CL — это набор функцией, которые производят теоретико-множественные операции над списками. Для добавления элемента в множество есть функции \lstinline{ADJOIN} и \lstinline{PUSHNEW}.
\begin{cllst}{}{}
(defparameter *set* ())
(adjoin 1 *set*)
*set* => NIL
(setf *set* (adjoin 1 *set*))
(pushnew 2 *set*)
*set* => (2 1)
(pushnew 2 *set*)
*set* => (2 1)
\end{cllst}
Функции \lstinline{MEMBER}, \lstinline{MEMBER-IF} и \lstinline{MEMBER-IF-NOT} проверяют, принадлежит ли элемент множеству. Вместо того, чтобы вернуть элемент, когда он присутствует в множестве, они возвращают cons-ячейку, содержащую элемент. Если искомый элемент отсутствует в списке, все три функции возвращают \lstinline{NIL}.
Функции \lstinline{INTERSECTION}, \lstinline{UNION}, \lstinline{SET-DIFFERENCE} и \lstinline{SET-DIFFERENCE-OR-EXCLUSIVE} делают то, что следует из их названий. Все эти функции имеют свои парные функции, начинающиеся с \lstinline{N}, которые изменяют переданные множества.
Функция \lstinline{SUBSETP} проверяет, является ли одно множество подмножеством другого.
\subsection{Ассоциативные списки}
Ассоциативный список — это структура данных, которая отображает ключи на значения, и наоборот. Ассоциативные списки также поддерживают добавление пар ключ/значение, которые скрывают существующие таким образом, что вновь добавленные пары могут быть позже удалены, и первоначальные пары снова станут видимы.
Технически, ассоциативный список является списком, элементами которого являются cons-ячейки.
\begin{cllst}{}{}
((a . 1) (b . 2) (c . 3))
\end{cllst}
Поиск в ассоциативном списке производиться функцией \lstinline{ASSOC} (а также — \lstinline{ASSOC-IF} и \lstinline{ASSOC-IF-NOT}).
\begin{cllst}{}{}
(assoc 'c '((a . 1) (b . 2) (c . 3))) => 3
(assoc 'd '((a . 1) (b . 2) (c . 3))) => NIL
(cdr (assoc 'a '((a . 1) (b . 2) (c . 3)))) => 1
(assoc "a" '(("a" . 1) ("b" . 2) ("c" . 3)) :test #'string=) => ("a" . 1)
(assoc 'a '((a . 10) (a . 1) (b . 2) (c . 3))) => (A . 10)
\end{cllst}
Функции \lstinline{RASSOC}, \lstinline{RASSOC-IF} и \lstinline{RASSOC-IF-NOT} выполняют поиск с конца списка.
Новый элемент добавить можно функцией \lstinline{ACONS}.
\begin{cllst}{}{}
(acons 'new-key 'new-value alist)
(setf alist (acons 'new-key 'new-value alist))
(push (cons 'new-key 'new-value) alist)
\end{cllst}
Для копирования ассоциативного списка следует использовать функцию \lstinline{COPY-ALIST}.
Ассоциативный список также можно создать функцией \lstinline{PAIRLIS}, которая принимает два списка и возвращает список пар.
\subsection{Destructuring-bind}
С помощью макроса \lstinline{DESTRUCTURING-BIND} можно деструктурировать списки и связывать их части с некоторыми переменными.
\begin{cllst}{}{}
(destructuring-bind (parameter*) list
body-form*)
\end{cllst}
\begin{cllst}{Примеры}{}
(destructuring-bind (x y z) (list 1 2 3)
(list :x x :y y :z z)) => (:X 1 :Y 2 :Z 3)
(destructuring-bind (x y z) (list 1 (list 2 20) 3)
(list :x x :y y :z z)) => (:X 1 :Y (2 20) :Z 3)
(destructuring-bind (x (y1 y2) z) (list 1 (list 2 20) 3)
(list :x x :y1 y1 :y2 y2 :z z)) => (:X 1 :Y1 2 :Y2 20 :Z 3)
(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2 20) 3)
(list :x x :y1 y1 :y2 y2 :z z)) => (:X 1 :Y1 2 :Y2 20 :Z 3)
(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2) 3)
(list :x x :y1 y1 :y2 y2 :z z)) => (:X 1 :Y1 2 :Y2 NIL :Z 3)
(destructuring-bind (&key x y z) (list :x 1 :y 2 :z 3)
(list :x x :y y :z z)) => (:X 1 :Y 2 :Z 3)
(destructuring-bind (&key x y z) (list :z 1 :y 2 :x 3)
(list :x x :y y :z z)) => (:X 3 :Y 2 :Z 1)
(destructuring-bind (&whole whole &key x y z) (list :z 1 :y 2 :x 3)
(list :x x :y y :z z :whole whole))
=> (:X 3 :Y 2 :Z 1 :WHOLE (:Z 1 :Y 2 :X 3))
\end{cllst}
\section{Loop}
\lstinline{LOOP} представляет собой встроенный проблемно-ориентированный язык (\emph{embedded domain-specific language, eDSL}) для описания итераций. Что он может:
\begin{itemize}
\item описывать циклы со счётчиком и по различным структурам данных;
\item собирать, подсчитывать, суммировать, вычислять минимальное и максимальное значения по коллекциям;
\item выполнять выражения Lisp;
\item описывать условия останова;
\item выполнять определенные действия при заданных условиях;
\item создавать локальные переменные;
\item выполнять некоторые выражения до выполнения цикла и после него.