-
Notifications
You must be signed in to change notification settings - Fork 8
/
chapter-32.tex
909 lines (785 loc) · 84.7 KB
/
chapter-32.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
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
\chapter{Заключение: что дальше?}
\label{ch:32}
\thispagestyle{empty}
Я надеюсь, теперь вы убеждены, что в названии этой книги нет противоречия. Однако вполне
вероятно, что есть какая-то область программирования, которая очень практически важна для
вас и которую я совсем не обсудил. Например, я ничего не сказал о том, как разрабатывать
графический пользовательский интерфейс (GUI), как связываться с реляционными базами
данных, как разбирать XML или как писать программы, которые являются клиентами различных
сетевых протоколов. Также я не обсудил две темы, которые станут важными, когда вы начнёте
писать реальные приложения на языке Common Lisp: оптимизацию кода и сборку приложений для
поставки.
Должно быть очевидно, что я не собираюсь глубоко охватить эти темы в этой финальной
главе. Вместо этого я дам вам несколько направлений, в которых вы можете двигаться,
занимаясь теми аспектами программирования на Lisp, которые интересуют вас больше.
\section{Поиск библиотек Lisp}
В~то время как стандартная библиотека функций, типов данных и макросов, поставляемая с
языком Common Lisp, достаточно велика, она служит лишь целям общего
программирования. Специализированные задачи, такие как создание графического
пользовательского интерфейса (GUI), общение с базами данных и разбор XML, требуют
библиотек, не обеспечиваемых стандартом этого языка.
Простейшим путём найти библиотеку, делающую что-то нужное вам, может быть просто проверка
ее наличия в составе вашей реализации Lisp. Большинство реализаций предоставляет по,
крайней мере, некоторые возможности, не указанные в стандарте языка. Коммерческие
поставщики, как правило, особенно усиленно работают над предоставлением дополнительных
библиотек для своих реализаций с целью оправдать их стоимость. Например, Franz's Allegro
Common Lisp Enterprise Edition поставляется среди прочего с библиотеками для разбора XML,
общения по протоколу SOAP, генерации HTML, взаимодействия с реляционными базами данных
и построения графического интерфейса пользователя различными путями. Другая выдающаяся
коммерческая реализация, LispWorks, также предоставляет несколько подобных библиотек,
включая пользующуюся уважением переносимую библиотеку CAPI, которая может использоваться
для разработки GUI-приложений, работающих на любой операционной системе, где есть
LispWorks.
Свободные реализации и реализации с открытым кодом обычно не включают такую большую связку
библиотек, вместо этого полагаясь на переносимые свободные и открытые библиотеки. Но даже
эти реализации обычно заполняют такие из наиболее важных областей, не охваченных
стандартом языка, как работа с сетями и многопоточность.
Единственный недостаток использования специфичных для реализации библиотек~-- это то, что
они привязывают вас к той реализации, которая их предоставляет. Если вы поставляете
приложения конечным пользователям или развёртываете ваши приложения на серверах, которыми
вы сами управляете, это может быть не очень важно. Однако если вы хотите писать код для
повторного использования другими программистами на Lisp или просто не хотите быть
привязанными к конкретной реализации, это, может быть, будет вас раздражать.
Переносимые библиотеки (переносимость означает либо что они написаны целиком на
стандартном Common Lisp, либо что они содержат необходимые макросы, чтобы работать с
несколькими реализациями)\footnote{Сочетание средств выборочного чтения исходного кода
(\lstinline!#+!, \lstinline!#-!) и макросов даёт возможность разрабатывать для
нестандартных средств слои переносимости, которые ничего не делают, кроме предоставления
общего API, развёрнутого поверх специфичного API конкретной реализации. Переносимая
библиотека файловых путей из главы~\ref{ch:15}~-- пример библиотеки такого рода, хотя
она, скорее, служит не для устранения различий в API разных реализаций, а для сглаживания
различий в интерпретации стандарта языка авторами разных реализаций.}\hspace{\footnotenegspace} лучше всего искать
в Интернете.
Вот три лучших места, откуда можно начать поиски (с обычными предосторожностями,
связанными с тем, что все URL устаревают сразу же, как только они напечатаны на бумаге):
\begin{itemize}
\item \pclURL{http://www.common-lisp.net/}{Common-Lisp.net}~-- сайт, размещающий свободные
и открытые проекты на Common Lisp, предоставляющий средства контроля версий исходного кода,
списки рассылки и размещение веб-документов проектов. В~первые полтора года работы сайта
было зарегистрировано около сотни проектов.
\item \pclURL{http://clocc.sourceforge.net/}{Коллекция открытого кода Common Lisp (The
Common Lisp Open Code Collection, CLOCC)}~-- немного более старая коллекция библиотек
свободного ПО, которые, как подразумевается, должны быть переносимы между разными
реализациями языка Common Lisp и самодостаточными, то есть не зависящими от каких-то
других библиотек, не включённых в эту коллекцию.
\item \pclURL{http://www.cliki.net/}{Cliki (Common Lisp Wiki)}~-- Wiki-сайт, посвящённый
свободному программному обеспечению на языке Common Lisp. Так же как и любой другой
сайт, основанный на Wiki, он может изменяться в любое время, обычно на нем есть довольно
много ссылок на библиотеки и реализации Common Lisp с открытым кодом. Система
редактирования документов, на которой работает сайт и давшая ему имя, также написана на
языке Common Lisp.
\end{itemize}
Пользователи Linux, работающие на системах Debian или Gentoo, также могут легко
устанавливать постоянно растущее число библиотек для языка Lisp, которые распространяются
с помощью средств распространения и установки этих систем: apt-get на Debian и emerge на
Gentoo.
Я не буду сейчас рекомендовать какие-то конкретные библиотеки, поскольку си\-туа\-ция с ними
меняется каждый день~-- после многих лет зависти к библиотекам языков Perl, Python и
Java программисты на Common Lisp в последние пару лет приняли вызов к созданию такого
набора библиотек, которого заслуживает Common Lisp, и коммерческих, и с открытым кодом.
Одна из областей, в которой в последнее время было много активности,~-- это фронт
разработки графического интерфейса приложений. В~отличие от Java и C\#, но как и в языках
Perl, Python и C, в языке Common Lisp не существует единственного пути для разработки
графического интерфейса. Способ разработки зависит от реализации Common Lisp, с которой вы
работаете, а также от операционной системы, которую вы хотите поддерживать.
Коммерческие реализации Common Lisp обычно обеспечивают какой-то способ разработки
графического интерфейса для платформ, на которых они работают. В~дополнение к этому
LispWork предоставляет библиотеку CAPI, уже упоминавшуюся, для разработки переносимых
графических приложений.
Из программного обеспечения с открытым кодом у вас есть несколько возможных вариантов. На
системах Unix вы можете писать низкоуровневые приложения для системы X Windows, используя
библиотеку CLX, реализацию X Windows протокола на чистом языке Common Lisp, примерно такую
же, как xlib для языка C. Или вы можете использовать различные обёртки для высокоуровневых
API и библиотек, таких как GTK или Tk, так же, как вы можете делать это в языках Perl или
Python.
Или если вы ищете что-то совсем необычное, то можете посмотреть на библиотеку Common
Lisp Interface Manager (CLIM). Будучи наследником графической библиотеки символических
LISP-машин, CLIM является мощным, но сложным средством. Хотя многие коммерческие
реализации языка Lisp поддерживают ее, не похоже, что она очень сильно использовалася. Но в
последние несколько лет новая реализация CLIM с открытым кодом, McCLIM, набирает обороты
(она располагается на сайте Common-Lisp.net), так что, возможно, мы на грани нового расцвета
этой библиотеки.
\section{Взаимодействие с другими языками программирования}
В~то время как много полезных библиотек может быть написано на чистом Common Lisp,
используя только возможности, указанные в стандарте языка, и гораздо больше может быть
написано с использованием нестандартных средств, предоставляемых какой-то из реализаций,
иногда проще использовать существующую библиотеку, написанную на другом языке, таком
как~C.
Стандарт языка не даёт механизма для вызова из кода на языке Lisp кода, написанного на
другом языке программирования, и не требует, чтобы реализация языка предоставляла такие
возможности. Но в наше время почти все реализации поддерживают то, что называется Foreign
Function Interface, или кратко~-- FFI\pclfootnote{Foreign Function Interface эквивалентен в
основном JNI в языке Java, XS в языке Perl или модулю расширения языка Python.}.
Основная задача FFI~-- позволить вам дать языку Lisp достаточно информации для того, чтобы
прилинковать чужой код, написанный не на Lisp. Таким образом, если вы собираетесь вызывать
функции из какой-то библиотеки для языка C, вам нужно рассказать языку Lisp о том, как
транслировать объекты Lisp, передаваемые этой функции, в типы данных языка C, а значение,
возвращаемое функцией,~-- обратно в объекты языка Lisp. Однако каждая реализация
предоставляет свой собственный механизм FFI, со своими отличными от других возможностями и
синтаксисом. Некоторые реализации FFI позволяют делать функции обратного вызова (callback
functions), другие~-- нет. Библиотека Universal Foreign Function Interface (UFFI)
предоставляет слой для переносимости приложений, написанных с использованием FFI,
доступный на более полудюжины реализаций Common Lisp. Библиотека определяет макросы,
которые раскрываются в вызовы соответствующих функций интерфейса FFI данной
реализации. Библиотека UFFI применяет подход <<наименьшего общего знаменателя>>, то есть
она не может использовать преимущества всех возможностей разных реализаций FFI, но все же
обеспечивает хороший способ построения обёрток API для языка С\pclfootnote{Два главных
недостатка библиотеки UFFI~-- это отсутствие поддержки вызова Lisp-функций из кода на
языке C, которую предоставляют многие, но не все FFI-реализации, и отсутствие поддержки
CLISP, одной из популярных реализаций Common Lisp, библиотека FFI которой хотя и
достаточно хороша, но сильно отличается от FFI других реализаций и поэтому не
вписывается легко в рамки модели UFFI.}.
\section{Сделать, чтобы работало; правильно, быстро}
Как уже было сказано много раз, по разным сведениям, Дональдом Кнутом, Чарльзом Хоаром и
Эдсгером Дейкстрой, <<преждевременная оптимизация является первопричиной всех
зол>>\pclfootnote{Кнут использовал эту фразу несколько раз в своих публикациях, включая его
работу 1974 года <<Программирование как искусство>> (<<Computer Programming as an
Art>>), удостоенную премии Тьюринга, и в работе <<Структурное программирование с
использованием оператора goto>> (<<Structured Programs with goto Statements.>>). В
работе <<Ошибки в TeX>> (<<The Errors of TeX>>) он приписывал эту фразу Чарльзу Хоару.
А Хоар в своём электронном письме от 2004 года к Гансу Генвицу из компании phobia.com
(Hans Genwitz of phobia.com) признался, что не помнит точно происхождения этой фразы, но
он бы приписал её авторство Дейкстре.}. Common Lisp~-- замечательный язык в этом
отношении, если вы хотите следовать этой практике и при этом вам нужна высокая
производительность. Эта новость может быть для вас сюрпризом, если вы уже слышали
традиционное мнение о том, что Lisp~-- медленный язык. В~ранние годы языка Lisp, когда
компьютеры ещё программировали с помощью перфокарт, этот язык с его высокоуровневыми
чертами был обречён быть медленнее, чем его конкуренты, а именно ассемблер и Фортран. Но
это было давно. В~то же время Lisp использовался для всего, от создания сложных систем
искусственного интеллекта до написания операционных систем, и было проделано много
работы, чтобы выяснить, как компилировать Lisp в эффективный код. В~этом разделе мы
поговорим о некоторых из причин, почему Common Lisp является прекрасным языком для
написания высокопроизводительных программ, и о том, как это достигается.
Первой причиной того, что Lisp является прекрасным языком для написания
высокопроизводительного кода, является, как ни парадоксально, динамический характер
программирования на этом языке~-- та самая вещь, которая сначала мешала довести
производительность программ на языке Lisp до уровней, достигнутых компиляторами языка
Фортран. Причина того, что динамичность Lisp упрощает создание высокопроизводительного
кода, заключается в том, что первый шаг к эффективному коду~-- это всегда поиск
правильных алгоритмов и структур данных.
Динамические свойства языка Lisp сохраняют код гибким, что упрощает опробование разных
подходов. Имея ограниченное время на создание программы, вы в конечном итоге более
вероятно придёте к более высокопроизводительной версии, если не будете проводить много
времени, забираясь в тупики и выбираясь из них обратно. В~языке Common Lisp вы можете
опробовать идею, увидеть, что она ведёт в никуда, и двигаться дальше, не тратя много
времени, убеждая компилятор, что ваш код все же достоин того, чтобы он наконец запустился,
и ожидая, когда же он наконец закончит его компилировать. Вы можете написать простую, но
эффективную версию функции~-- набросок кода,~-- чтобы определить, работает ли ваш
основной код правильно, а затем заменить эту функцию на более сложную и более эффективную
её реализацию, если все хорошо. Но если общий подход к решению оказался с изъяном, то вы
не потратите кучу времени на отладку функции, которая в конечном итоге не нужна, что
означает, что у вас остаётся больше времени для поиска лучшего подхода к решению задачи.
Следующая причина того, что Common Lisp является хорошим языком для разработки
высокопроизводительного программного обеспечения, заключается в том, что большинство
реализаций Common Lisp обладает зрелыми компиляторами, которые генерируют достаточно
эффективный машинный код. Мы остановимся сейчас на том, как помочь компиляторам
сгенерировать код, который был бы сравним с кодом, генерируемым компиляторами языка C, но
эти реализации и так уже немного быстрее, чем те языки программирования, реализации
которых менее зрелы и которые используют более простые компиляторы или
интерпретаторы. Также, поскольку компилятор Lisp доступен во время выполнения программы,
программист на Lisp имеет некие возможности, которые было бы трудно эмулировать в других
языках: ваша программа может генерировать код на Lisp во время выполнения, который затем
может быть скомпилирован в машинный код и запущен. Если сгенерированный код должен
работать много раз, то это большой выигрыш. Или, даже без использования компилятора во
время выполнения, замыкания дают вам другой способ объединять код и данные времени
выполнения. Например, библиотека регулярных выражений CL-PPCRE, работающая под CMUCL,
быстрее, чем библиотека регулярных выражений языка Perl, на некоторых тестах, несмотря
даже на то, что библиотека языка Perl написана на высокооптимизированном языке C. Это,
вероятно, происходит от того, что в Perl регулярные выражения транслируются в байт-код,
который затем интерпретируется средствами поддержки регулярных выражений Perl, в то время
как библиотека CL-PPCRE транслирует регулярные выражения в дерево скомпилированных
функций, использующих замыкания (closures), которые вызывают друг друга средствами
нормальных вызовов функций.
Однако даже с правильным алгоритмом и высококачественным компилятором вы можете не
достичь нужной вам скорости. Значит, пришло время подумать об оптимизации и
профайлере. Основной подход здесь в языке Lisp, как и в других языках,~-- сначала
применить профайлер, чтобы найти места, где ваша программа реально тратит много времени, а
уже потом озаботиться ускорением этих частей.
Есть несколько подходов к профилированию. Стандарт языка предоставляет несколько
простейших средств измерения времени выполнения каких-то форм. В~частности, макроcом
\lstinline{TIME} можно обернуть любую форму, и он вернёт возвращаемое формой значение,
напечатав сообщение в поток \lstinline!*TRACE_OUTPUT*! о том, как долго выполнялась форма
и сколько памяти она использовала. Конкретный вид сообщения определяется конкретной
реализацией.
Вы также можете использовать \lstinline{TIME} для довольно быстрого и грубого
профилирования, чтобы сузить ваши поиски узкого места. Например, предположим, у вас есть
долго работающая функция, которая вызывает две другие функции, примерно так:
\begin{myverb}
(defun foo ()
(bar)
(baz))
\end{myverb}
Если вы хотите увидеть, какая из функций работает дольше, то можете изменить определение
функции таким образом:
\begin{myverb}
(defun foo ()
(time (bar))
(time (baz)))
\end{myverb}
Теперь вы можете вызвать \lstinline{foo}, и Lisp напечатает два отчёта, один для \lstinline{bar},
другой для \lstinline{baz}. Формат отчёта зависит от реализации, вот как это выглядит в Allegro
Common Lisp:
\begin{myverb}
CL-USER> (foo)
; cpu time (non-gc) 60 msec user, 0 msec system
; cpu time (gc) 0 msec user, 0 msec system
; cpu time (total) 60 msec user, 0 msec system
; real time 105 msec
; space allocation:
; 24,172 cons cells, 1,696 other bytes, 0 static bytes
; cpu time (non-gc) 540 msec user, 10 msec system
; cpu time (gc) 170 msec user, 0 msec system
; cpu time (total) 710 msec user, 10 msec system
; real time 1,046 msec
; space allocation:
; 270,172 cons cells, 1,696 other bytes, 0 static bytes
\end{myverb}
Конечно, было бы немного проще это читать, если бы вывод включал какие-то пометки. Если вы
часто пользуетесь этим приёмом, было бы полезно определить ваш собственный макрос вот так:
\begin{myverb}
(defmacro labeled-time (form)
`(progn
(format *trace-output* "~2&~a" ',form)
(time ,form)))
\end{myverb}
Если вы замените \lstinline{TIME} на \lstinline{labeled-time} в \lstinline{foo}, вы увидите следующий
вывод:
\begin{myverb}
CL-USER> (foo)
(BAR)
; cpu time (non-gc) 60 msec user, 0 msec system
; cpu time (gc) 0 msec user, 0 msec system
; cpu time (total) 60 msec user, 0 msec system
; real time 131 msec
; space allocation:
; 24,172 cons cells, 1,696 other bytes, 0 static bytes
(BAZ)
; cpu time (non-gc) 490 msec user, 0 msec system
; cpu time (gc) 190 msec user, 10 msec system
; cpu time (total) 680 msec user, 10 msec system
; real time 1,088 msec
; space allocation:
; 270,172 cons cells, 1,696 other bytes, 0 static bytes
\end{myverb}
С этой формой отчёта сразу ясно, что большее время выполнения \lstinline{foo} тратится в
\lstinline{baz}.
Конечно, вывод от \lstinline{TIME} становится немного неудобным, если форма, которую вы хотите
профилировать, вызывается последовательно много раз. Вы можете создать свои средства
измерения, используя функции \lstinline{GET-INTERNAL-REAL-TIME} и \lstinline{GET-INTERNAL-RUN-TIME},
которые возвращают число, увеличиваемое на величину константы
\lstinline{INTERNAL-TIME-UNITS-PER-SECOND} каждую секунду.
\lstinline{GET-INTERNAL-REAL-TIME} измеряет абсолютное время, реальное время, прошедшее между
событиями, в то время как \lstinline{GET-INTERNAL-RUN-TIME} даёт некое специфичное для
конкретной реализации значение, равное времени, которое Lisp-программа тратит реально на
выполнение именно пользовательского кода, исключая внутренние затраты Lisp-машины на
поддержку программы, такие как выделение памяти и сборка мусора.
Вот достаточно простой, но полезный вспомогательный инструмент профилирования, построенный
с помощью нескольких макросов и функции \lstinline{GET-INTERNAL-RUN-TIME}:
\begin{myverb}
(defparameter *timing-data* ())
(defmacro with-timing (label &body body)
(with-gensyms (start)
`(let ((,start (get-internal-run-time)))
(unwind-protect (progn ,@body)
(push (list ',label ,start (get-internal-run-time)) *timing-data*)))))
(defun clear-timing-data ()
(setf *timing-data* ()))
(defun show-timing-data ()
(loop for (label time count time-per %-of-total) in (compile-timing-data) do
(format t "~3d% ~a: ~d ticks over ~d calls for ~d per.~%"
%-of-total label time count time-per)))
(defun compile-timing-data ()
(loop with timing-table = (make-hash-table)
with count-table = (make-hash-table)
for (label start end) in *timing-data*
for time = (- end start)
summing time into total
do
(incf (gethash label timing-table 0) time)
(incf (gethash label count-table 0))
finally
(return
(sort
(loop for label being the hash-keys in timing-table collect
(let ((time (gethash label timing-table))
(count (gethash label count-table)))
(list label time count (round (/ time count)) (round (* 100 (/ time total))))))
#'> :key #'fifth))))
\end{myverb}
Этот профайлер позволяет вам обернуть вызов любой формы в макрос
\lstinline{with-timing}. Каждый раз, когда форма будет выполняться, время начала и конца
выполнения будет записываться в список, связываясь с меткой, которую вы
указываете. Функция \lstinline{show-timing-data} выводит таблицу, в которой показано, как много
времени было потрачено в различно помеченных секциях кода, следующим образом:
\begin{myverb}
CL-USER> (show-timing-data)
84% BAR: 650 ticks over 2 calls for 325 per.
16% FOO: 120 ticks over 5 calls for 24 per.
NIL
\end{myverb}
Очевидно, вы могли бы сделать этот профайлер более сложным во многих отношениях. С другой
стороны, каждая реализация Lisp часто предоставляет свои средства профилирования, которые
могут выдавать информацию, часто недоступную пользовательскому коду, поскольку они имеют
доступ к внутренним механизмам реализации.
Как только вы нашли узкое место в вашем коде, вы начинаете оптимизацию. Первое, что вам
следует попробовать,~-- это, конечно, попытаться найти более эффективный базовый алгоритм,
что всегда приносит большую выгоду. Но если предположить, что вы уже используете
эффективный алгоритм, то тогда как раз время начинать рихтовать код, то есть
оптимизировать код, чтобы он не делал абсолютно ничего, кроме необходимой работы.
Главное средство при этом~-- дополнительные объявления Lisp. Основная идея объявлений в
языке Lisp~-- в том, что они дают компилятору информацию, которую он может использовать
различным образом, чтобы генерировать более хороший код.
Например, рассмотрим простую функцию:
\begin{myverb}
(defun add (x y) (+ x y))
\end{myverb}
Как я упоминал в главе~\ref{ch:10}, если вы сравните производительность этой Lisp-функции
с вроде бы эквивалентной функцией на языке C:
\begin{lstlisting}[language=C]
int add (int x, int y) { return x + y; }
\end{lstlisting}
\noindent{}вы, возможно, обнаружите, что Lisp-функция заметно медленнее, даже если ваша
реализация Common Lisp обладает высококачественным компилятором в машинный код.
Это происходит потому, что версия функции на Common Lisp выполняет гораздо больше
всего~-- компилятор Common Lisp даже не знает, что значения \lstinline{x} и \lstinline{y} являются
числами, и таким образом вынужден сгенерирвать код для проверки типов во время выполнения.
И как только он определил, что это~-- числа, он должен также определить, какого типа эти
числа~-- целые, рациональные, с плавающей точкой или комплексные, и перенаправить вызов
соответствующей вспомогательной процедуре, обрабатывающей соответствующие типы. И даже
если \lstinline{x} и \lstinline{y} являются целыми числами, то процедура сложения должна
позаботиться о том, что результат сложения может быть слишком большим для представления в
FIXNUM-числе, числе, которое может быть представлено в одном машинном слове, и, таким
образом, должна выделить число типа BIGNUM.
В~языке C, с другой стороны, поскольку типы всех переменных объявлены, компилятор знает
точно, какой тип значений будет храниться в \lstinline{x} и \lstinline{y}. И, поскольку арифметика
языка C просто вызывает переполнение, когда результат сложения слишком большой, чтобы быть
представленным в возвращаемом типе, в функции нет ни проверки на переполнение, ни
выделения длинного целого значения, если результат не вмещается в машинное слово.
Таким образом, поскольку поведение кода на Common Lisp гораздо более близко к
математической корректности, версия на языке C, возможно, может быть скомпилирована в одну
или две машинные инструкции. Но если вы пожелаете дать компилятору Common Lisp ту же
информацию, которую имеет компилятор языка C о типах аргументов и возвращаемом значении, и
принять некоторые компромисы, подобные используемым в языке C, относительно общности кода
и проверки ошибок, функция на Common Lisp также может компилироваться в пару машинных
инструкций.
Именно для этого и служат объявления. Главная польза объявлений~-- в том, чтобы сказать
компилятору о типах переменных и других выражений. Например, вы можете указать
компилятору, что складываемые аргументы являются FIXNUM-значениями, написав функцию
следующим образом:
\begin{myverb}
(defun add (x y)
(declare (fixnum x y))
(+ x y))
\end{myverb}
Выражение \lstinline{DECLARE} не является формой языка Common Lisp, это~-- часть
синтаксиса макроса \lstinline{DEFUN}. Если это выражение используется, то оно должно быть
указано до кода из которого состоит тело функции\footnote{Объявления могут появляться в
большинстве форм, которые вводят новые переменные, как, например \lstinline{LET},
\lstinline{LET*} и семейство описывающих циклы макросов \lstinline{DO}. \lstinline{LOOP}
имеет свой механизм объявления типов переменных цикла. Специальный оператор
\lstinline{LOCALLY}, упоминаемый в главе~\ref{ch:20}, делает не что иное, как создаёт
место для объявлений.}\hspace{\footnotenegspace}. Данное объявление объявляет, что
аргументы функции \lstinline{x} и \lstinline{y} будут всегда FIXNUM-значениями. Другими
словами, это обещание компилятору, и компилятору разрешено генерировать код в
предположении, что все, что вы пообещали ему, будет истинным.
Чтобы объявить тип возвращаемого значения, вы можете обернуть основное выражение функции
\lstinline{(+ x y)} в специальный оператор \lstinline{THE}. Этот оператор принимает спецификатор
типа, такой как \lstinline{FIXNUM}, и какую-то форму, и говорит компилятору, что эта форма
будет возвращать значение указанного типа. Таким образом, чтобы дать компилятору Common
Lisp всю ту же информацию, которую имеет компилятор языка C, вы можете написать следующее:
\begin{myverb}
(defun add (x y)
(declare (fixnum x y))
(the fixnum (+ x y)))
\end{myverb}
Однако даже эта версия нуждается в ещё одном объявлении, чтоб дать компилятору Common Lisp
те же разрешения, что есть и у компилятора языка C, генерировать быстрый, но опасный
код. Объявление \lstinline{OPTIMIZE} используется для того, чтобы указать компилятору, как
распределить своё внимание между пятью целями:
\begin{itemize}
\item скоростью генерируемого кода;
\item количеством проверок времени выполнения;
\item использованием памяти как в терминах размера кода, так и в терминах размера памяти
данных;
\item количеством отладочной информации, хранимой вместе с кодом;
\item скоростью процесса компиляции.
\end{itemize}
Объявление \lstinline{OPTIMIZE} содержит один или более списков, каждый из которых содержит
один из символов: \lstinline{SPEED}, \lstinline{SAFETY}, \lstinline{SPACE}, \lstinline{DEBUG} или
\lstinline{COMPILATION-SPEED} и число от нуля до трёх включительно. Число указывает
относительный вес, который компилятор должен дать соответствующему параметру, причём 3
означает наиболее важное направление, а 0~-- что это не имеет значения вообще. Таким
образом, чтобы заставить компилятор Common Lisp компилировать функцию \lstinline{add}
более-менее так же, как это бы делал компилятор языка C, вы можете переписать её так:
\begin{myverb}
(defun add (x y)
(declare (optimize (speed 3) (safety 0)))
(declare (fixnum x y))
(the fixnum (+ x y)))
\end{myverb}
Конечно, теперь Lisp-версия функции страдает многими из слабостей C-версии: если
переданные аргументы не FIXNUM-значения или если сложение вызывает переполнение,
результаты будут математически некорректны или даже хуже. Также если кто-то вызовет
функцию \lstinline{add} с неправильным количеством параметров, будет мало хорошего. Таким
образом, вам следует использовать объявления этого вида только после того, как ваша
программа стала работать правильно, и вы должны добавлять их только в местах, где
профилирование показывает необходимость этого. Если вы имеете достаточную
производительность без этих объявлений, пропускайте их. Но если профайлер показывает вам
какое-то проблемное место в вашем коде и вам нужно оптимизировать его~-- вперёд. Поскольку
вы можете использовать объявления таким образом, редко ког\-да нужно переписывать код на
языке C только из соображений производительности. Для доступа к существующему коду на C
используется FFI, но когда нужна производительность, подобная языку C, используют
объявления. И конечно, то, как близко вы захотите приблизить производительность данной
части кода на языке Common Lisp к коду на C или C++, зависит главным образом от вашего
желания.
Другое средство оптимизации, встроенное в язык Lisp,~-- это функция
\lstinline{DISASSEMBLE}. Точное поведение данной функции зависит от реализации, потому что оно
зависит от того, как реализация компилирует код: в машинный код, байт-код или какую-то
другую форму. Но основная идея в том, что она показывает вам код, сгенерированный
компилятором, когда он компилировал данную функцию.
Таким образом, вы можете использовать \lstinline{DISASSEMBLE}, чтобы увидеть, возымели ли ваши
объявления какой-то эффект на генерируемый код. Если ваша реализация языка Lisp использует
компиляцию в машинный код и если вы знаете язык ассемблера вашей платформы, вы сможете
достаточно хорошо представить себе, что реально происходит, когда вы вызываете одну из
ваших функций. Например, вы можете использовать \lstinline{DISASSEMBLE}, чтобы понять, в чем
различия между нашей первой версией \lstinline{add} и окончательной версией. Сначала определите
и скомпилируйте исходную версию
\begin{myverb}
(defun add (x y) (+ x y))
\end{myverb}
Затем в сессии REPL вызовите \lstinline{DISASSEMBLE} с именем этой функции. В~Allegro это
выведет следующий ассемблероподобный листинг сгенерированного компилятором кода:
\begin{myverb}
CL-USER> (disassemble 'add)
;; disassembly of #<Function ADD>
;; formals: X Y
;; code start: #x737496f4:
0: 55 pushl ebp
1: 8b ec movl ebp,esp
3: 56 pushl esi
4: 83 ec 24 subl esp,$36
7: 83 f9 02 cmpl ecx,$2
10: 74 02 jz 14
12: cd 61 int $97 ; SYS::TRAP-ARGERR
14: 80 7f cb 00 cmpb [edi-53],$0 ; SYS::C_INTERRUPT-PENDING
18: 74 02 jz 22
20: cd 64 int $100 ; SYS::TRAP-SIGNAL-HIT
22: 8b d8 movl ebx,eax
24: 0b da orl ebx,edx
26: f6 c3 03 testb bl,$3
29: 75 0e jnz 45
31: 8b d8 movl ebx,eax
33: 03 da addl ebx,edx
35: 70 08 jo 45
37: 8b c3 movl eax,ebx
39: f8 clc
40: c9 leave
41: 8b 75 fc movl esi,[ebp-4]
44: c3 ret
45: 8b 5f 8f movl ebx,[edi-113] ; EXCL::+_2OP
48: ff 57 27 call *[edi+39] ; SYS::TRAMP-TWO
51: eb f3 jmp 40
53: 90 nop
; No value
\end{myverb}
Очевидно, что здесь полно всякой всячины. Если вы знакомы с ассемблером процессоров
архитектуры x86, вы, может быть, это поймёте. Теперь скомпилируйте версию функции \lstinline{add}
со всеми объявлениями:
\begin{myverb}
(defun add (x y)
(declare (optimize (speed 3) (safety 0)))
(declare (fixnum x y))
(the fixnum (+ x y)))
\end{myverb}
И дизассемблируйте функцию \lstinline{add} снова, и посмотрите, был ли какой-то толк от
этих объявлений.
\begin{myverb}
CL-USER> (disassemble 'add)
;; disassembly of #<Function ADD>
;; formals: X Y
;; code start: #x7374dc34:
0: 03 c2 addl eax,edx
2: f8 clc
3: 8b 75 fc movl esi,[ebp-4]
6: c3 ret
7: 90 nop
; No value
\end{myverb}
Похоже, что был.
\section{Поставка приложений}
Другая практически важная тема, о которой я не говорил нигде в этой книге,~-- это как
поставлять приложения, написанные на языке Lisp. Главная причина, по которой я пренебрегал
этой темой, заключается в том, что есть много разных способов это делать, и который из них
лучше вам подходит, зависит от того, какой вид программного обеспечения вам нужно
распространять, кому его надо распространять и какой реализацией Common Lisp этот кто-то
пользуется. В~этом разделе я дам обзор некоторых из возможностей.
Если вы написали код, которым хотите поделиться со своими коллегами, которые тоже пишут на
языке Lisp, самый простой путь распространения~-- это распространять исходный
код\footnote{Файлы FASL, получающиеся после компиляции с помощью \lstinline{COMPILE-FILE},
имеют формат, зависящий от реализации, и могут даже быть несовместимы с разными версиями
одной и той же реализации. Таким образом, они~-- не очень хороший способ распространения
кода на языке Lisp. В~одном случае они могут быть удобны~-- как способ обеспечения
исправлений вашего приложения, которое работает на какой-то определённой версии
конкретной реализации. Тогда, чтобы исправить ваше приложение, достаточно загрузить
FASL-файл с помощью \lstinline{LOAD}, и, поскольку FASL-файл может содержать любой код, он
также может быть использован как для определения новой версии кода, так и для изменения
существующих данных, чтобы они соответствовали новой версии.}\hspace{\footnotenegspace}. Вы можете поставлять
простую библиотеку как один исходный файл, который программисты могут загрузить в их образ
Lisp-машины командой \lstinline{LOAD}, возможно, после компиляции с помощью
\lstinline{COMPILE-FILE}.
Более сложные библиотеки или приложения, разбитые на несколько исходных файлов, ставят
дополнительный вопрос: для того чтобы загрузить сложный код, файлы исходного кода должны
быть загружены и откомпилированы в правильном порядке. Например, файл, содержащий
определения макросов, должен быть загружен до файлов, которые используют эти макросы, а
файл, содержащий инструкцию определения пакета \lstinline{DEFPACKAGE}, должен быть загружен до
того, как все файлы, использующие этот пакет, будут просто читаться
\lstinline{READ}'ом. Программисты на языке Lisp называют это проб\-ле\-мой определения систем
(system definition problem) и обычно разрешают её с по\-мощью средств, называемых средствами
определения систем (system definition facilities) или утилитами определения систем (system
definition utilities), которые являются чем-то вроде аналогов билд-системам, таким как
утилиты \lstinline{make} и \lstinline{ant}. Как и эти средства, средства определения систем
позволяют указать зависимости между разными файлами и берут на себя заботу о загрузке и
компиляции этих файлов в правильном порядке, стараясь при этом выполнять только ту работу,
которая необходима, например перекомпилировать только те файлы, которые изменились.
В~наши дни наиболее широко используется система определения систем, называемая
\lstinline{ASDF}, что означает Another System Definition Facility (еще одно средство
определения систем)\footnote{\lstinline{ASDF} был написан Дэниэлем Барлоу (Daniel Barlow),
одним из разработчиков \lstinline{SBCL}, и был включён в \lstinline{SBCL} как часть, а
также поставлялся как самостоятельная библиотека. В~последнее время он был заимствован и
включён в другие реализации Lisp, такие как \lstinline{OpenMCL} и
\lstinline{Allegro}.}\hspace{\footnotenegspace}. Основная идея \lstinline{ASDF} в том, что вы описываете вашу
систему в специальном файле~-- ASD, а \lstinline{ASDF} предоставляет возможности по
выполнению некоторого набора операций с описанными таким образом системами, такие как
загрузка и компиляция систем. Система может быть объявлена зависимой от других систем,
которые в таком случае будут корректно загружены при необходимости. Например, вот как
выглядит содержимое файла <<html.asd>>, включающего описание системы для \lstinline{ASDF}
для библиотеки \lstinline{FOO} из глав~\ref{ch:31} и~\ref{ch:32}:
\begin{myverb}
(defpackage :com.gigamonkeys.html-system (:use :asdf :cl))
(in-package :com.gigamonkeys.html-system)
(defsystem html
:name "html"
:author "Peter Seibel <peter@gigamonkeys.com>"
:version "0.1"
:maintainer "Peter Seibel <peter@gigamonkeys.com>"
:license "BSD"
:description "HTML and CSS generation from sexps."
:long-description ""
:components
((:file "packages")
(:file "html" :depends-on ("packages"))
(:file "css" :depends-on ("packages" "html")))
:depends-on (:macro-utilities))
\end{myverb}
Если вы добавите символьную ссылку на этот файл в каталог, указанный в переменной
\lstinline{asdf:*central-registry*}\footnote{В~системе Windows, где нет символьных ссылок, это
надо делать немного по-другому, но принцип тот же. (\emph{Примечание переводчика}: на самом
деле в современном Win32 API поддерживаются символьные ссылки в файловой системе NTFS,
правда, операции с ними недоступны в большинстве стандартного ПО для работы с файлами, но
предоставляются такой известной программой, как \lstinline{Far}. Во времена создания этой
книги данная возможность уже существовала, но, видимо, автор не был с нею знаком ввиду
малораспространённости средств, поддерживающих её. Кроме того, символьные ссылки в
Unix/Lunux-образных операционных системах имеют некоторые особенности, в результате
которых пути к файлам относительно символьной ссылки вычисляются с использованием того
места, куда эта ссылка ссылается. В~Win32 такого не происходит, поэтому этот способ для
аналогичных целей просто неприменим в Win32.)}\hspace{\footnotenegspace}, то затем вы можете набрать следующую
комманду:
\begin{myverb}
(asdf:operate 'asdf:load-op :html)
\end{myverb}
\noindent{}чтобы скомпилировать и загрузить файлы <<packages.lisp>>, <<html.lisp>> и
<<html-macros.lisp>> в правильном порядке после того, как система \lstinline{:macro-utilities}
будет скомпилирована и загружена. Примеры других файлов определения систем \lstinline{ASDF} вы
можете найти в прилагаемом к книге коде~-- код из каждой практической главы определён как
система с соответствующими межсистемными зависимостями, определёнными в формате ASDF.
Большинство свободного ПО и ПО с открытым кодом на языке Common Lisp, которое вы найдёте,
будет поставляться с ASD-файлом. Некоторые библиотеки могут поставляться с другими
системами определения, такими как немного более старая \lstinline{MK:DEFSYSTEM}, или даже
системами, изобретёнными авторами этой библиотеки, но общая тенденция, кажется, все же в
использовании \lstinline{ASDF}\footnote{Другое средство, \lstinline{ASDF-INSTALL},
построенное поверх систем \lstinline{ASDF} и \lstinline{MK:DEFSYSTEM}, предоставляет
простой способ автоматически скачать библиотеки из Интернета и загрузить их. Лучший
способ начать знакомство с \lstinline{ASDF-INSTALL}~-- инструкция, написанная Эди
Вайтзом (Edi Weitz) <<A tutorial for ASDF-INSTALL>> (\url{http://
www.weitz.de/asdf-install/}).}\hspace{\footnotenegspace}.
Конечно, \lstinline{ASDF} позволяет программистам легко устанавливать библиотеки, но это никак
не помогает поставлять приложения конечным пользователям, которые не имеют представления о
языке Lisp. Если вы поставляете приложения для конечного пользователя, по-видимому, вы бы
хотели предоставить пользователям нечто, что он бы мог загрузить, установить и запустить,
не зная ничего о языке Lisp. Вы не можете ожидать, что пользователи будут отдельно
устанавливать реализацию Lisp'а, и вы бы, наверное, хотели, чтобы приложения, написанные на
языке Lisp, запускались так же, как и все прочие приложения в вашей операционной
системе,~-- двойным нажатием кнопки мыши на иконке приложения или набором имени приложения
в командной строке интерпретатора команд.
Однако, в отличие от программ, написанных на языке C, которые обычно могут расчитывать на
присутствие неких совместно используемых библиотек, которые составляют так называемый
<<С-рантайм>> и присутствуют как часть операционной системы, программы на языке Lisp
должны включать ядро языка Lisp, то есть ту же часть Lisp-системы, которая используется
при разработке, хотя и, возможно, без каких-то модулей, не требуемых во время выполнения
программы.
И даже все ещё более сложно~-- понятие <<программа>> не очень хорошо определено в языке
Lisp. Как вы видели во время чтения этой книги, процесс разработки программ на языке
Lisp~-- это инкрементальный процес, подразумевающий постоянные изменения определений и
данных, находящихся внутри исполняемого образа Lisp-машины. Поэтому <<программа>>~-- это
всего лишь определённое состояние этого образа, которое достигнуто в результате загрузки
файлов \lstinline{.lisp} или \lstinline{.fasl}, которые содержат код, который создаёт
соответствующие определения и данные. Вы могли бы соответственно распространять приложение
на языке Lisp как рантайм Lisp-машины, плюс набор файлов \lstinline{.fasl} и исполняемый
модуль, который запускал бы рантайм Lisp-машины, загружал бы все файлы \lstinline{.fasl} и
как-то вызывал бы соответствующую начальную функцию вашего приложения. Однако, поскольку в
реальности загрузка файлов \lstinline{.fasl} может занимать значительное время, особенно если
они должны выполнить какие-то вычисления для установки начального состояния данных,
большинство реализаций Common Lisp предоставляет способ выгрузить исполняемый образ
Lisp-машины в файл, называемый \emph{image file} (\emph{файл образа}) или иногда
\emph{core file}, чтобы сохранить все состояние Lisp-машины. Когда рантайм Lisp-машины
запускается, первым делом он загружает файл образа, что у него получается гораздо быстрее,
чем если бы то же состояние восстанавливалось с помощью загрузки файлов \lstinline{.fasl}.
Обычно файл образа~-- это базовый образ Lisp-машины, содержащий только стандартные пакеты,
определённые стандартом языка, и какие-то дополнительные пакеты, предоставляемые данной
реализацией. Но в большинстве реализаций вы можете указать другой, свой файл образа. Таким
образом, вместо поставки приложения в виде рантайма Lisp-машины и набора файлов
\lstinline{.fasl} вы можете поставлять рантайм Lisp-машины и только один файл образа,
содержащий все определения кода и данных, которые составляют ваше приложение. В~таком
случае вам будет надо только запустить рантайм с этим файлом образа и вызвать функцию,
служащую точкой входа в ваше приложение.
Здесь всё становится зависящим от реализации и операционной системы. Некоторые реализации
Common Lisp, в особенности коммерческие, как, например, Allegro или LispWorks, предоставляют
средства для создания таких файлов образа. Например, Allegro Enterprise Edition
предоставляет функцию \lstinline{excl:generate-application}, которая создаёт каталог,
содержащий рантайм языка Lisp как разделяемую библиотеку, файл образа и выполняемый файл,
который запускает рантайм с данным образом. Похожим образом механизм LispWorks
Professional Edition, называемый <<поставка>> (delivery), позволяет вам строить
однофайловый исполняемый файл из ваших программ. В~различных реализациях Unix вы можете
делать фактически то же самое, хотя, возможно, там проще использовать командный файл для
запуска всего, чего угодно.
В~Mac OS X всё ещё лучше: поскольку все приложения в этой ОС упакованы в \texttt{.app}-пакеты,
которые, по сути, являются каталогами с определённой структурой, очень легко упаковать все
части приложения на Lisp в \texttt{.app}-пакет, который можно запустить двойным нажатием клавиши
мыши. Утилита Bosco Микеля Эвинса (Mikel Evins) делает создание \texttt{.app}-пакетов простым для
приложений, работающих на OpenMCL.
Конечно, в наши дни другой популярный способ поставки приложений~-- это серверные
приложения. Это та ниша, где Common Lisp может превосходно использоваться: вы можете
выбрать сочетание операционной системы и реализации Common Lisp, которая вас устраивает, и
вам не нужно заботиться о поставке приложения в том виде, в котором его мог бы установить
пользователь. А возможность языка Common Lisp интерактивно разрабатывать и отлаживать
программы позволяет вам отлаживаться и обновлять версии вашей программы без остановки
сервера, что либо вообще было бы невозможно в менее динамичных языках программирования,
либо требовало бы построения довольно сложной инфраструктуры поддержки.
\section{Что дальше?}
Ну вот и все. Добро пожаловать в чудесный мир языка Lisp. Лучшее, что вы можете сейчас
сделать (если вы уже это не сделали),~-- это начать писать свои собственные программы на
языке Lisp. Выберите проект, который вам интересен, и сделайте его в Common Lisp. Потом
сделайте ещё один. Как говорится, намылить, прополоскать, повторить...
Однако, если вам нужна дальнейшая помощь, этот раздел даст вам несколько мест, где её
можно найти. Для начинающих полезно посмотреть сайт книги <<Practical Common Lisp Web
site>> по ссылке \url{http://www.gigamonkeys.com/book}, где вы найдёте исходный код из
практических глав книги, список опечаток и ссылки на другие ресурсы в сети Интернет.
Вдобавок к сайтам, которые я упомянул в разделе <<Поиск библиотек Lisp>>, вы, возможно,
заходите изучить Common Lisp HyperSpec (известную также как HyperSpec или CLHS), которая
является HTML-версией ANSI-стандарта языка, приготовленной Кентом Питманом (Kent Pitman) и
сделанной общедоступной компанией LispWorks по ссылке
\url{http://www.lispworks.com/documentation/HyperSpec/index.html}. HyperSpec ни в коем
случае не является учебником, но это самое надёжное руководство по языку, которое вы
только можете достать, не покупая печатную версию стандарта языка у комитета ANSI, и при
том гораздо более удобное в повседневном использовании\pclfootnote{SLIME включает в свой
состав библиотеку на языке Elisp (Emacs lisp), которая позволяет вам автоматически
попадать по ключевому слову, определённому в стандарте, на статью в HyperSpec. Вы также
можете загрузить полную копию HyperSpec из Интернета и использовать её локально.}.
Если вы хотите связаться с другими лисперами, Usenet-группа (группа новостей)
<<comp.lang.lisp>> и IRC-канал <<\#lisp>> в \pclURL{http://www.freenode.net}{чат-сети
Freenode}~-- вот два основных сборища лисперов в сети\translationnote{Здесь сложно было
бы удержаться от упоминания русскоязычного канала lisp@conference.jabber.ru, благодаря
существованию которого данная книга и смогла появиться на свет на русском
языке.}. Существует также некоторое количество блогов, связанных с языком Lisp, большее
количество которых собрано на сайте <<Planet Lisp>> по адресу
\mbox{\url{http://planet.lisp.org}}.
И не пропускайте также во всех этих форумах анонсы собраний местных групп пользователей
языка Lisp~-- в последние несколько лет собрания лисперов возникают спонтанно во всех
городах мира, от Нью-Йорка до Окленда, от Кёльна до Мюнхена, от Женевы до Хельсинки.
Если же вы хотите продолжить изучение книг, вот вам несколько советов. В~качестве хорошего
толстого справочника можете держать на вашем столе книгу <<The ANSI Common Lisp Reference
Book>> под редакцией Дэвида Марголиса (David Margolies)(издательство Apress,
2005~г.)\pclfootnote{Другой классический справочник~-- книга Гая Стила (Guy Steele) <<Common
Lisp: The Language>> (издательство Digital Press, 1984 и 1990~г.). Первое издание,
известное также как CLtL1, было фактически стандартом языка несколько лет. Ожидая, пока
официальный стандарт ANSI будет закончен, Гай Стил, который также был в комитете ANSI по
языку Lisp, решил выпустить второе издание, чтобы восполнить разрыв между CLtL1 и
окончательным стандартом. Второе издание, теперь известное также как CLtL2~-- по сути,
слепок работы комитета по стандартизации, снятый в определённый момент времени, близкий
к концу, но всё-таки не в самом конце процесса стандартизации. Следовательно, CLtL2
отличается от стандарта в некоторых аспектах, что делает эту книгу не очень хорошим
справочником. Однако она всё же является полезным историческим документом, особенно
потому, что включает документацию по некоторым возможностям, которые были исключены из
окончательного стандарта, а также не входящие в стандарт комментарии о том, почему
что-то сделано именно так.}.
Для детального изучения объектной системы языка Lisp вы можете для начала прочитать книгу
Сони Кин (Sonya E. Keene) <<Object-Oriented Programming in Common Lisp: A Programmer's
Guide to CLOS>> (<<Объектно-ориентированное программирование в Common Lisp: Руководство
программиста по CLOS>>) (издательство Addison-Wesley, 1989~г.). Затем, если вы хотите
стать настоящим мастером, или просто чтобы расширить кругозор, прочитайте книгу авторов
Грегора Кикзалеса, Джима де Ривьереса и Даниэля Боброва (Gregor Kiczales, Jim des
Rivieres, Daniel G. Bobrow) <<The Art of the Metaobject Protocol>> (издательство MIT
Press, 1991~г.). Эта книга, также известная как AMOP, является как объяснением, что такое
метаобъектный протокол и зачем он нужен, так и фактически стандартом метаобъектного
протокола, поддерживаемого многими реализациями языка Common Lisp.
Две книги, которые охватывают общие приёмы программирования на Common Lisp,~-- это
<<Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp>> Питера
Норвига (Peter Norvig) (издательство Morgan Kaufmann, 1992~г.) и <<On Lisp: Advanced
Techniques for Common Lisp>> Пола Грема (Paul Graham) (издательство Prentice Hall,
1994~г.). Первая даёт твёрдую основу приёмов искусственного интеллекта и в то же время
немного учит, как писать хороший код на Common Lisp. Вторая особенно хороша в рассмотрении
макросов.
Если вы~-- человек, который любит знать, как всё работает до последнего винтика, книга
Кристиана Квене (Christian Queinnec) <<Lisp in Small Pieces>> (издательство Cambridge
University Press, 1996~г.) сможет дать вам удачное сочетание теории языков программирования
и практических методик реализации языка Lisp. Несмотря на то что она изначально
сконцентрирована на реализации языка Scheme, а не Common Lisp, принципы, изложенные в
книге, применимы и для него.
Для людей, которые хотят более теоретического взгляда на вещи, или для тех, кто просто
хочет попробовать, каково это~-- быть новичком-студентом компьютерных наук в
Массачусетском технологическом институте, следующая книга авторов Харольда Абельсона,
Геральда Джея Сусмана и Джули Сусман (Harold Abelson, Gerald Jay Sussman, and Julie
Sussman)~-- <<Structure and Interpretation of Computer Programs, Second Edition>>
(<<Структура и интерпретация компьютерных программ>>) (издательство M.I.T. Press, 1996~г.),
классическая работа по компьютерным наукам, которая использует язык Scheme для обучения
важным концепциям программирования. Любой программист может много узнать из этой книги, не
забывайте только, что у языков Scheme и Common Lisp есть важные отличия.
Как только вы охватите своим умом язык Lisp, вам, возможно, захочется добавить чего-то
объектного. Поскольку никто не может заявлять, что он действительно понимает объекты без
знания хотя бы чего-то о языке Smalltalk, вы, возможно, захотите начать знакомство с этим
языком с книги Адели Голдберг и Дэвида Робсона (Adele Goldberg, David Robson)
<<Smalltalk-80: The Language>> (издательство Addison Wesley, 1989~г.), которая является
стандартным введением в язык Smalltalk. После этого~-- книга Кента Бека (Kent Beck)
<<Smalltalk Best Practice Patterns>> (издательство Prentice Hall, 1997~г.), которая полна
хороших советов для программистов на этом языке, большинство из которых также применимо и
к любому другому объектно-ориентированному языку.
На другом конце спектра~-- книга Бертрана Мейера (Bertrand Meyer), изобретателя часто не
замечаемого наследника Симулы и Алгола~-- языка Eiffel, <<Object-Oriented Software
Construction>> (Prentice Hall, 1997~г.), прекрасная демонстрация склада ума людей, мыслящих в
стиле статических языков программирования. Эта книга содержит много пищи для размышлений
даже для программистов, работающих с динамическими языками, каковым является язык Common
Lisp. В~частности, идеи Мейера о контрактном программировании могут помочь понять, как
нужно использовать систему условий языка Common Lisp.
Хотя и не о компьютерах как таковых, книга Джеймса Суровьеки <<Мудрость масс: почему те,
кого много, умнее тех, кого мало, и как коллективная мудрость формирует бизнес, экономику,
общество и нации>> (<<The Wisdom of Crowds: Why the Many Are Smarter Than the Few and How
Collective Wisdom Shapes Business, Economies, Societies, and Nations>>, James Surowiecki,
Doubleday, 2004~г.) содержит великолепный ответ на вопрос: <<почему если Lisp такой
замечательный, его все не используют?>> Смотрите раздел <<Лихорадка досчатой дороги>>,
начиная со страницы~53.
И в заключение для удовольствия и чтобы понять, какое влияние Lisp и лисперы оказали на
развитие культуры хакеров, просмотрите или прочитайте от корки до корки <<Новый словарь
хакеров>> Эрика Реймонда (<<The New Hacker's Dictionary, Third Edition>>, compiled by Eric
S. Raymond, MIT Press, 1996~г.), который базируется на исходном словаре хакеров, редактируемом
Гаем Стилом (Harper \& Row, 1983~г.).
Но не давайте всем этим книгам мешать вашему программированию, потому что единственный
путь изучить язык программирования~-- это использовать его. Если вы дошли до этого места,
вы безусловно в состоянии заняться этим.
\vspace{0.5cm}
Happy hacking!
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "pcl-ru"
%%% TeX-open-quote: "<<"
%%% TeX-close-quote: ">>"
%%% End: