-
Notifications
You must be signed in to change notification settings - Fork 8
/
chapter-18.tex
817 lines (665 loc) · 65.2 KB
/
chapter-18.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
\chapter{Несколько рецептов для функции FORMAT}
\label{ch:18}
\thispagestyle{empty}
Функция \lstinline{FORMAT} вместе с расширенным макросом \lstinline{LOOP}~-- одна из двух
возможностей Common Lisp, которые вызывают сильную эмоциональную реакцию у многих
пользователей Common Lisp. Некоторые их любят, другие ненавидят\footnote{Конечно,
большинство людей осознают, что не стоит возбуждаться по поводу чего-либо в языке
программирования, и используют или не используют их без сильного беспокойства. С другой
стороны, интересно, что две эти возможности~-- единственные в Common Lisp, которые
реализуют, по сути, языки предметной области (domain specific languages), используя
синтаксис, не основанный на s-выражениях. Синтаксис управляющих строк \lstinline{FORMAT}
основан на символах, в то время как расширенный макрос \lstinline{LOOP} может быть понят
только в терминах грамматики ключевых слов \lstinline{LOOP}. Одна из обычных придирок к
\lstinline{FORMAT} и \lstinline{LOOP}~-- то, что они <<недостаточно лисповские>>.~--
является доказательством того, что Lisp-программистам действительно нравится синтаксис,
основанный на s-выражениях.}\hspace{\footnotenegspace}.
Поклонники \lstinline{FORMAT} любят её за мощь и краткость, в то время как противники её
ненавидят за потенциал для возникновения ошибок и непрозрачность. Сложные управляющие
строки \lstinline{FORMAT} имеют иногда подозрительное сходство с помехами на
экране\translationnote{В~оригинале~-- line noise, шум в линии, имеется в виду схожесть
строки формата с сигналом на осциллографе.}, но \lstinline{FORMAT} остаётся популярным
среди программистов на Common Lisp, которые хотят формировать небольшие куски
удобочитаемого текста без необходимости нагромождать кучи формирующего вывод кода. Хотя
управляющие строки \lstinline{FORMAT} могут быть весьма замысловаты, но во всяком случае
единственное \lstinline{FORMAT}-выражение врядли сильно замусорит ваш код. Предположим,
например, что вы хотите напечатать значения в списке, разделённые запятыми. Вы можете
написать так:
\begin{myverb}
(loop for cons on list
do (format t "~a" (car cons))
when (cdr cons) do (format t ", "))
\end{myverb}
Это не очень плохо, но любой, кто будет читать этот код, должен будет мысленно
проанализировать его, прежде чем понять, что всё, что он делает,~-- это печать содержимого
списка \lstinline{list} на стандартный вывод. С другой стороны, вы можете с одного взгляда
определить, что следующее выражение печатает список в некоторой форме на стандартный
вывод:
\begin{myverb}
(format t "~{~a~^, ~}" list)
\end{myverb}
Если вам важна форма, которую примет вывод, тогда вы можете изучить управ\-ляю\-щую строку, но
если всё, что вы хотите,~-- это приблизительно знать, что делает эта строка кода, то это
можно увидеть сразу.
Во всяком случае, вам следует по меньшей мере зрительно воспринимать \lstinline{FORMAT}, а ещё
лучше разобраться, на что она способна, прежде чем вы примкнёте к про- или
анти-\lstinline{FORMAT}'овскому лагерю. Также важно понимать по меньшей мере основы
\lstinline{FORMAT}, поскольку другие стандартные функции, такие как функции выбрасывания
условий, обсуждаемые в следующей главе, используют управляющие строки в стиле
\lstinline{FORMAT} для формирования вывода.
Чтобы сильнее запутать дело, \lstinline{FORMAT} поддерживает три совершенно разных вида
форматирования: печать таблиц с данными, структурная печать (\textit{pretty printing})
s-выражений и формирование удобочитаемых сообщений со вставленными (interpolated)
значениями. Печать таблиц с текстовыми данными на сегодня несколько устарела; это одно из
напоминаний, что Lisp стар, как FORTRAN. В~действительности некоторые директивы, которые
вы можете использовать для печати значений с плавающей точкой внутри полей с фиксированной
длиной, были основаны прямо на дескрипторах редактирования (edit descriptors) FORTRAN,
которые использовались в FORTRAN для чтения и печати столбцов с данными, расположенными
внутри полей с фиксированной длинной. Тем не менее использование Common Lisp как замены
FORTRAN выходят за рамки этой книги, так что я не буду обсуждать эти аспекты
\lstinline{FORMAT}.
Структурная печать также находится за рамками этой книги~-- не потому, что она устарела, а
потому, что это слишком большая тема. Вкратце механизм структурной печати Common Lisp~--
это настраиваемая система для печати блочно-структурированных данных, включая, но не
ограничиваясь, s-выражения, применяющаяся, когда необходимы переменные отступы и
динамически увеличивающиеся переводы строк. Это отличный инструмент при необходимости, но
не часто требуемый в повседневном программировании\pclfootnote{Читатели, интересующиеся
внутренним устройством структурной печати, могут почитать статью <<XP: A Common Lisp
Pretty Printing System>> Ричарда Уотерса (Richard Waters). Это описание системы
структурированной печати, которая в итоге была включена в Common Lisp. Вы можете
загрузить её с \url{ftp://publications.ai.mit.edu/ai-publications/pdf/AIM-1102a.pdf}.}.
Вместо этого я сосредоточусь на частях \lstinline{FORMAT}, которые вы можете использовать,
чтобы формировать удобочитаемые строки со вставленными в них значениями. Даже ограничивая
обзор таким образом, остаётся изрядное количество материала. Не чувствуйте себя так, как
будто вы обязаны помнить каждую деталь, описанную в этой главе. Вы можете достичь многого
с лишь несколькими идиомами \lstinline{FORMAT}. Сначала я опишу наиболее важные возможности
\lstinline{FORMAT}; а вам решать, насколько вы хотите стать волшебником \lstinline{FORMAT}.
\section{Функция FORMAT}
Как вы видели в предыдущих главах, функция \lstinline{FORMAT} принимает два аргумента:
получатель для своего вывода и управляющую строку, которая содержит буквенный текст и
вложенные \textit{директивы}. Любые дополнительные аргументы предоставляют значения,
используемые директивами управляющей строки, которые вставляют эти значения в печатаемый
текст. Я буду ссылаться на эти аргументы как на \textit{аргументы формата}.
Первым аргументом \lstinline{FORMAT}, получателем для печатаемого текста, может быть
\lstinline{T}, \lstinline{NIL}, поток или строка с указателем заполнения. \lstinline{Т}
обозначает поток \lstinline{*STANDARD-OUTPUT*}, в то время как \lstinline{NIL} заставляет
\lstinline{FORMAT} сформировать свой вывод в виде строки, которую функция затем
возвращает\footnote{Чтобы немного запутать дело, большинство остальных функций
ввода/вывода также принимают~\lstinline{T} и \lstinline{NIL} как указатели потоков, но с
небольшим отличием: указатель потока~\lstinline{Т} обозначает двунаправленный поток
\lstinline{*TERMINAL-IO*}, тогда как \lstinline{NIL} указывает на
\lstinline{*STANDARD-OUTPUT*} как на стандартный поток вывода и
\lstinline{*STANDARD-INPUT*} как на стандартный поток ввода.}\hspace{\footnotenegspace}. Если получатель~--
поток, то вывод пишется в поток. А если получатель~-- строка с указателем заполнения, то
форматированный вывод добавляется к концу строки и указатель заполнения соответственно
выравнивается. За исключением случая, когда получатель~-- \lstinline{NIL} и функция
возвращает строку, \lstinline{FORMAT} возвращает \lstinline{NIL}.
Второй аргумент~-- управляющая строка, является, в сущности, программой на языке
\lstinline{FORMAT}. Язык \lstinline{FORMAT} не целиком <<лисповый>>~-- его основной синтаксис основан
на символах, а не на s-выражениях, и оптимизирован для краткости, а не для лёгкого
понимания. Вот почему сложные управляющие строки \lstinline{FORMAT} могут быть приняты за
помехи.
Большинство из директив \lstinline{FORMAT} просто вставляют аргумент внутрь выводимого текста в
той или иной форме. Некоторые директивы, такие как \lstinline!~%!, которая заставляет
\lstinline{FORMAT} выполнить перевод строки, не используют никаких аргументов. Другие, как вы
увидите, могут использовать более одного аргумента. Одна из директив даже позволяет вам
прыгать по списку аргументов с целью обработки одного и того же аргумента несколько раз,
или в некоторых ситуациях пропустить определённые аргументы. Но прежде чем я буду
обсуждать конкретные директивы, давайте взглянем на общий синтаксис директив.
\section{Директивы FORMAT}
Все директивы начинаются с тильды (\lstinline!~!) и заканчиваются отдельным знаком, который
идентифицирует директиву. Вы можете писать этот символ как в верхнем, так и в нижнем
регистре. Некоторые директивы принимают \textit{префиксные параметры}, которые пишутся
непосредственно после тильды, разделяются запятыми и используются для управления такими
деталями, как сколько разрядов печатать после десятичной точки при печати числа с
плавающей точкой. Например, директива \lstinline!~$!, одна из директив, использующихся
для печати значений c плавающей точкой, по умолчанию печатает два разряда, следующие за
десятичной точкой.%$
\begin{myverb}
CL-USER> (format t "~$" pi)
3.14
NIL
\end{myverb}
Тем не менее с префиксным параметром вы можете указать, чтобы функция печатала свой
аргумент, к примеру с пятью десятичными знаками, как здесь:
\begin{myverb}
CL-USER> (format t "~5$" pi)
3.14159
NIL
\end{myverb}
Значениями префиксного параметра являются либо числа, записанные как десятичные, либо
знаки, записанные в виде одинарной кавычки, за которой следует нужный символ. Значение
префиксного параметра может быть также получено из аргумента формата двумя способами:
префиксный параметр \lstinline{v} заставляет \lstinline{FORMAT} использовать один аргумент формата и
назначить его значение префиксному параметру. Префиксный параметр \lstinline!#! будет вычислен
как количество оставшихся аргументов формата. Например:
\begin{myverb}
CL-USER> (format t "~v$" 3 pi)
3.142
NIL
CL-USER> (format t "~#$" pi)
3.1
NIL
\end{myverb}
Я дам более правдоподобные примеры использования аргумента \lstinline!#! в
разделе <<Условное форматирование>>.
Вы можете также опустить оба префиксных параметра. Впрочем, если вы хотите указать один
параметр, но не желаете указывать параметры, стоящие перед ним, то вы должны включить
запятую для каждого пропущенного параметра. Например, директива \lstinline!~F!, другая
директива для печати значений с плавающей точкой, также принимает параметр для управления
количеством десятичных разрядов при печати, но это второй по счёту параметр, а не
первый. Если вы хотите использовать \lstinline!~F! для печати числа с пятью десятичными
разрядами, вы можете написать так:
\begin{myverb}
CL-USER> (format t "~,5f" pi)
3.14159
NIL
\end{myverb}
Также вы можете изменить поведение некоторых директив при помощи \textit{модификаторов}
\lstinline!:! и знака \lstinline!@!, которые ставятся после любого префиксного параметра и
до идентифицирующего директиву знака. Эти модификаторы незначительно меняют поведение
директивы. Например, с модификатором \lstinline!:! директива \lstinline!~D!,
использующаяся для вывода целых чисел в десятичном виде, создаёт число с запятыми,
разделяющими каждые три разряда, в то время как знак \lstinline!@! заставляет
\lstinline!~D! включить знак плюс в случае положительного числа.
\begin{myverb}
CL-USER> (format t "~d" 1000000)
1000000
NIL
CL-USER> (format t "~:d" 1000000)
1,000,000
NIL
CL-USER> (format t "~@d" 1000000)
+1000000
NIL
\end{myverb}
В~случае необходимости вы можете объединить модификаторы \lstinline!:! и \lstinline!@!,
для того чтобы получить оба варианта:
\begin{myverb}
CL-USER> (format t "~:@d" 1000000)
+1,000,000
NIL
\end{myverb}
В~директивах, где оба модифицированных варианта поведения не могут быть осмысленно
объединены, использование обоих модификаторов либо не определено, либо приобретает третье
значение.
\vfill{}
\section{Основы форматирования}
Сейчас вы готовы познакомиться с конкретными директивами. Я начну с нескольких наиболее
распространённых директив, включая несколько тех, которые вы уже встречали в предыдущих
главах.
Наиболее универсальная директива~-- \lstinline!~A!, которая использует один аргумент
формата любого типа и печатает его в \textit{эстетичной} (удобочитаемой) форме. Например,
строки печатаются без кавычек и экранирующих символов (escape characters), а числа
печатаются в форме, принятой для соответствующего числового типа. Если вы хотите просто
получить значение, предназначенное для прочтения человеком, то эта директива~-- ваш выбор.
\begin{myverb}
(format nil "The value is: ~a" 10) ==> "The value is: 10"
(format nil "The value is: ~a" "foo") ==> "The value is: foo"
(format nil "The value is: ~a" (list 1 2 3)) ==> "The value is: (1 2 3)"
\end{myverb}
Родственная директива \lstinline!~S! также требует один аргумент формата любого типа и
печатает его. Однако \lstinline!~S! пытается сформировать такой вывод, который мог бы
быть прочитан обратно с помощью \lstinline{READ}. Поэтому строки должны быть заключены в
кавычки, при необходимости знаки должны быть специлизированы для пакета
(package-qualified), и~т.д. Объекты, которые не имеют подходящего для \lstinline{READ}
представления, печатаются в виде непригодном для чтения: \lstinline!#<>!. С модификатором
\textit{двоеточие} обе директивы \lstinline!~A! и \lstinline!~S! выводят
\lstinline{NIL} в виде \lstinline{()}, а не как \lstinline{NIL}. Обе директивы
\lstinline!~A! и \lstinline!~S! также принимают до четырёх префиксных параметров, которые
могут быть использованы для выравнивания пробелами (padding), добавляемыми после
(или до, при модификаторе \textit{@}) значения, впрочем эти функции действительно полезны
лишь при формировании табличных данных.
Две другие часто используемые директивы~-- это \lstinline!~%!, которая создаёт новую
строку, и \lstinline!~&!, которая выполняет \textit{перевод строки}. Разница между ними в
том, что \lstinline!~%! всегда создаёт новую строку, тогда как \lstinline!~&!.
срабатывает только если она уже не находится в начале строки. Это удобно при создании
слабо связанных функций, каждая из которых формирует кусок текста и их нужно объединить
различными способами. Например, если одна из функции выводит текст, который заканчивается
новой строкой (\lstinline!~%!), а другая функция выводит некоторый текст, который
начинается с перевода строки (\lstinline!~&!), то вам не стоит беспокоиться о получении
дополнительной пустой строки, если вы вызываете их одну за другой. Обе эти директивы могут
принимать единственный префиксный параметр, который обозначает количество выводимых новых
строк. Директива \lstinline!~%! просто выведет заданное количество знаков новой строки, в
то время как директива \lstinline!~&! создаст либо \lstinline{n - 1}, либо \lstinline{n}
новых строк, в зависимости от того, начинает ли она с начала строки.
Реже используется родственная им директива \lstinline!~~!, которая заставляет
\lstinline{FORMAT} вывести знак тильды. Подобно директивам \lstinline!~%! и \lstinline!~&!,
она может быть параметризована числом, которое задаёт количество выводимых тильд.
\section{Директивы для знаков и целых чисел}
\label{ch18:chars-numbers}
Вдобавок к директивам общего назначения \lstinline!~A! и \lstinline!~S! \lstinline{FORMAT}
поддерживает несколько директив, которые могут использоваться для получения значений
определённых типов особыми способами. Простейшей из них является директива \lstinline!~C!,
которая используется для вывода знаков. Ей не требуются префиксные аргументы, но её работу
можно корректировать с помощью модификаторов \textit{двоеточие} и \textit{@}. Без
модификаций её поведение не отличается от поведения \lstinline!~A!, за исключением того,
что она работает только со знаками. Модифицированные версии более полезны. С модификатором
\textit{двоеточие} \lstinline!~:C! выводит заданные по имени \textit{непечатаемые} знаки,
такие как пробел, символ табуляции и перевод строки. Это полезно, если вы хотите создать
сообщение к пользователю, в котором упоминается некоторый символ. Например, следующий код:
\begin{myverb}
(format t "Syntax error. Unexpected character: ~:c" char)
\end{myverb}
\noindent{}может напечатать такое сообщение:
\begin{myverb}
Syntax error. Unexpected character: a
\end{myverb}
\noindent{}а ещё вот такое:
\begin{myverb}
Syntax error. Unexpected character: Space
\end{myverb}
Вместе с модификатором \lstinline!@! \lstinline!~@С! выведет знак в синтаксисе знаков
Lisp:
\begin{myverb}
CL-USER> (format t "~@c~%" #\bslash{}a)
#\bslash{}a
NIL
\end{myverb}
Одновременно с модификаторами \lstinline!:! и \textit{@} директива \lstinline!~C! может
напечатать дополнительную информацию о том, как ввести символ с клавиатуры, если для этого
требуется специальная клавиатурная комбинация. Например, на Macintosh в некоторых
приложениях вы можете ввести нулевой символ (код символа 0 в ASCII или в любом
надмножестве ASCII, наподобие ISO-8859-1 или Unicode), удерживая клавиши Ctrl и нажав
'\lstinline!@!'. В~OpenMCL, если вы напечатаете нулевой символ c помощью директивы
\lstinline!~:C!, она сообщит вам следующее:
\begin{myverb}
(format nil "~:@c" (code-char 0)) ==> "^@ (Control @)"
\end{myverb}
Однако не все версии Lisp реализуют этот аспект директивы \lstinline!~C!. Даже если они
это делают, то реализация может и не быть аккуратной~-- например, если вы за\-пусти\-те OpenMCL
в SLIME, комбинация клавиш \lstinline!C-@! перехватывается Emacs, вызывая команду
\lstinline{set-mark-command}\footnote{Этот вариант директивы \lstinline!~C! имеет большее
значение на платформах наподобие Lisp-машин, где нажатие клавиши представляется знаками
Lisp.}\hspace{\footnotenegspace}.
Другой важной категорией являются директивы формата, предназначенные для вывода
чисел. Хотя для этого вы можете использовать директивы \lstinline!~A! и \lstinline!~S!, но
если вы хотите получить над печатью чисел больший контроль, вам следует использовать одну
из специально предназначенных для чисел директив. Ориентированные на числа директивы могут
быть подразделены на две категории: директивы для форматирования целых чисел и директивы
для форматирования чисел с плавающей точкой.
Вот пять родственных директив для форматированного вывода целых чисел: \lstinline!~D!,
\lstinline!~X!, \lstinline!~O!, \lstinline!~B! и \lstinline!~R!. Чаще всего применяется
директива \lstinline!~D!, которая выводит целые числа по основанию \lstinline{10}.
\begin{myverb}
(format nil "~d" 1000000) ==> "1000000"
\end{myverb}
Как я упоминал ранее, с модификатором \lstinline!:! она добавляет запятые.
\begin{myverb}
(format nil "~:d" 1000000) ==> "1,000,000"
\end{myverb}
А с модификатором \lstinline!@! она всегда будет печатать знак.
\begin{myverb}
(format nil "~@d" 1000000) ==> "+1000000"
\end{myverb}
И оба модификатора могут быть объединены.
\begin{myverb}
(format nil "~:@d" 1000000) ==> "+1,000,000"
\end{myverb}
С помощью первого префиксного параметра можно задать минимальный размер вывода, а с
помощью второго~-- используемый символ заполнения. По умолчанию символ заполнения~-- это
пробел, и заполнение всегда помещается до самого числа.
\begin{myverb}
(format nil "~12d" 1000000) ==> " 1000000"
(format nil "~12,'0d" 1000000) ==> "000001000000"
\end{myverb}
Эти параметры удобны для форматирования объектов наподобие календарных дат в формате с
фиксированной длиной.
\begin{myverb}
(format nil "~4,'0d-~2,'0d-~2,'0d" 2005 6 10) ==> "2005-06-10"
\end{myverb}
Третий и четвёртый параметры используются в связке с модификатором \textit{двоеточие}:
третий параметр определяет знак, используемый в качестве разделителя между группами и
разрядами, а четвёртый параметр определяет число разрядов в группе. Их значения по
умолчанию~-- запятая и число 3 соответственно. Таким образом, вы можете использовать
директиву \lstinline!~:D! без параметров для вывода больших чисел в стандартном для
Соединённых Штатов формате, но можете заменить запятую на точку и группировку с \lstinline{3}
на \lstinline{4} с помощью \lstinline!~,,'.,4D!.
\begin{myverb}
(format nil "~:d" 100000000) ==> "100,000,000"
(format nil "~,,'.,4:d" 100000000) ==> "1.0000.0000"
\end{myverb}
Заметим, что вы должны использовать запятые для замещения позиций неуказанных параметров
длины и заполняющего символа, позволяя им сохранить свои значения по умолчанию.
Директивы \lstinline!~X!, \lstinline!~O!, и \lstinline!~B! работают подобно директиве
\lstinline!~D!, за исключением того, что они выводят числа в шестнадцатеричном,
восьмеричном и двоичном форматах.
\begin{myverb}
(format nil "~x" 1000000) ==> "f4240"
(format nil "~o" 1000000) ==> "3641100"
(format nil "~b" 1000000) ==> "11110100001001000000"
\end{myverb}
Наконец, директива \lstinline!~R!~-- универсальная директива для задания \textit{системы
счисления}. Её первый параметр~-- число между 2 и 36 (включительно), которое обозначает,
какое основание системы счисления использовать. Оставшиеся параметры такие же, что и
четыре параметра, принимаемые директивами \lstinline!~D!, \lstinline!~X!, \lstinline!~O! и
\lstinline!~B!, а модификаторы \textit{двоеточие} и \lstinline!@! меняют её поведение
схожим образом. Кроме того, директива \lstinline!~R! ведёт себя особым образом при
использовании без префиксных параметров, которые я буду обсуждать в разделе
<<\nameref{ch18:eng-lang}>>.
\section{Директивы для чисел с плавающей точкой}
Вот четыре директивы для форматирования значений с плавающей точкой: \lstinline!~F!,
\lstinline!~E!, \lstinline!~G! и~\lstinline!~$!. Первые три из них~-- это директивы,
основанные на дескрипторах редактирования (edit descriptor) FORTRAN. Я пропущу большинство
деталей этих директив, поскольку они в основном имеют дело с форматированием чисел с
плавающей точкой для использования в табличной форме. Тем не менее вы можете использовать
директивы \lstinline!~F!, \lstinline!~E! и~\lstinline!~$! для вставки значений с плавающей
точкой в текст. С другой стороны, директива \lstinline!~G!, или \textit{обобщенная}
директива, сочетает аспекты директив \lstinline!~F! и \lstinline!~E! единственным
осмысленным способом для печати таблиц.
Директива \lstinline!~F! печатает свой аргумент, который должен быть
числом\footnote{Технически, если аргумент не является вещественным числом, предполагается,
что \lstinline!~F! форматирует его так же, как это сделала бы директива ~D, которая
ведёт себя как директива \lstinline!~A!, если аргумент не является числом, но не все
реализации ведут себя должным образом.}\hspace{\footnotenegspace}, в десятичном формате, по возможности
контролируя количество разрядов после десятичной точки. Директиве \lstinline!~F! тем не
менее разрешается использовать компьютеризированное экспоненциальное представление, если
число достаточно велико либо мало. Директива \lstinline!~E!, с другой стороны, всегда
выводит числа в компьютеризированной научной нотации. Обе эти директивы принимают
несколько префиксных параметров, но вам нужно беспокоиться только о втором, который
управляет количеством разрядов, печатаемых после десятичной точки.
\begin{myverb}
(format nil "~f" pi) ==> "3.141592653589793d0"
(format nil "~,4f" pi) ==> "3.1416"
(format nil "~e" pi) ==> "3.141592653589793d+0"
(format nil "~,4e" pi) ==> "3.1416d+0"
\end{myverb}
Директива \lstinline!~$! (знак доллара) похожа на \lstinline!~F!, но несколько
проще. Как подсказывает её имя, она предназначена для вывода денежных единиц. Без
параметров она эквивалентна \lstinline!~,2F!. Чтобы изменить количество разрядов,
печатаемых после десятичной точки, используйте \textit{первый} параметр, в то время как
второй параметр регулирует минимальное количество разрядов, печатающихся до десятичной
точки.%$
\begin{myverb}
(format nil "~$" pi) ==> "3.14"
(format nil "~2,4$" pi) ==> "0003.14"
\end{myverb}
С модификатором \lstinline!@! все три директивы, \lstinline!~F!, \lstinline!~E! и
\lstinline!~$!, можно заставить всегда печатать знак, плюс или минус\footnote{Итак, вот
что говорит стандарт языка. По какой-то причине, возможно коренящейся в общей
унаследованной кодовой базе, некоторые реализации Common Lisp реализуют этот аспект
директивы \lstinline!~F! некорректно.}\hspace{\footnotenegspace}. %$
\section{Директивы для английского языка}
\label{ch18:eng-lang}
Некоторые из удобнейших директив \lstinline{FORMAT} для формирования удобочитаемых
сообщений~-- те, которые выводят английский текст. Эти директивы позволяют вам выводить
числа как английский текст, выводить слова в множественном числе в зависимости от значения
аргумента формата и выполнять преобразование между строчными и прописными буквами в
секциях вывода \lstinline{FORMAT}.
Директива \lstinline!~R!, которую я обсуждал в разделе <<\nameref{ch18:chars-numbers}>>,
при использовании без указания системы счисления печатает числа как анг\-лийские слова или
римские цифры. При использовании без префиксного параметра и модификаторов она выводит
число словами как количественное числительное.
\begin{myverb}
(format nil "~r" 1234) ==> "one thousand two hundred thirty-four"
\end{myverb}
С модификатором \textit{двоеточие} она выводит число как порядковое числительное.
\begin{myverb}
(format nil "~:r" 1234) ==> "one thousand two hundred thirty-fourth"
\end{myverb}
И вместе с модификатором \lstinline!@! она выводит число в виде римских цифр; вместе с
\lstinline!@! и \textit{двоеточием} она выводит римские цифры, в которых четвёрки и
девятки записаны как \lstinline{IIII} и \lstinline{VIIII} вместо \lstinline{IV} и \lstinline{IX}.
\begin{myverb}
(format nil "~@r" 1234) ==> "MCCXXXIV"
(format nil "~:@r" 1234) ==> "MCCXXXIIII"
\end{myverb}
Для чисел, слишком больших, чтобы быть представленными в заданной форме, \lstinline!~R!
ведёт себя как \lstinline!~D!.
Чтобы помочь вам формировать сообщения со словами в нужном числе, \lstinline{FORMAT}
предоставляет директиву \lstinline!~P!, которая просто выводит 's', если соответствующий
аргумент не \lstinline{1}.
\begin{myverb}
(format nil "file~p" 1) ==> "file"
(format nil "file~p" 10) ==> "files"
(format nil "file~p" 0) ==> "files"
\end{myverb}
Тем не меннее обычно вы будете использовать \lstinline!~P! вместе с модификатором
\textit{двоеточие}, который заставляет её повторно обработать предыдущий аргумент формата.
\begin{myverb}
(format nil "~r file~:p" 1) ==> "one file"
(format nil "~r file~:p" 10) ==> "ten files"
(format nil "~r file~:p" 0) ==> "zero files"
\end{myverb}
С модификатором \lstinline!@!, который может быть объединён с модификатором
\textit{двоеточие}, \lstinline!~P! выводит \textit{y} или \textit{ies}.
\begin{myverb}
(format nil "~r famil~:@p" 1) ==> "one family"
(format nil "~r famil~:@p" 10) ==> "ten families"
(format nil "~r famil~:@p" 0) ==> "zero families"
\end{myverb}
Очевидно, что \lstinline!~P! не может разрешить все проблемы образования множественного
числа и не может помочь при формировании сообщений на других языках (отличных от
английского), она удобна в тех ситуациях, для которых предназначена. А директива
\lstinline!~[!, о которой я расскажу очень скоро, предоставит вам более гибкий способ
параметризации вывода \lstinline{FORMAT}.
Последняя директива, посвящённая выводу английского текста,~-- это \lstinline!~(!, которая
позволяет вам контролировать регистр выводимого текста. Каждая \lstinline!~(! составляет
пару с \lstinline!~)!, и весь вывод, сгенерированный частью управляющей строки между двумя
маркерами, будет преобразован в нижний регистр.
\begin{myverb}
(format nil "~(~a~)" "FOO") ==> "foo"
(format nil "~(~@r~)" 124) ==> "cxxiv
\end{myverb}
Вы можете изменить поведение \lstinline!~(! модификатором \lstinline!@!, чтобы заставить
ее начать с прописной буквы первое слово на участке текста, с \textit{двоеточием}
заставить её печатать все слова с прописной буквы, а с обоими модификаторами~--
преобразовать весь текст в верхний регистр. (Подходящие для этой директивы \textit{слова}
представляют собой буквенно-цифровые знаки, ограниченные небуквенно-цифровыми знаками или
концом текста.)
\begin{myverb}
(format nil "~(~a~)" "tHe Quick BROWN foX") ==> "the quick brown fox"
(format nil "~@(~a~)" "tHe Quick BROWN foX") ==> "The quick brown fox"
(format nil "~:(~a~)"p "tHe Quick BROWN foX") ==> "The Quick Brown Fox"
(format nil "~:@(~a~)" "tHe Quick BROWN foX") ==> "THE QUICK BROWN FOX"
\end{myverb}
\vfill{}
\section{Условное форматирование}
Вдобавок к директивам, вставляющим в выводимый текст свои аргументы и видоизменяющими
прочий вывод, \lstinline{FORMAT} предоставляет несколько директив, которые реализуют простые
управляющие структуры внутри управляющих строк. Одна из них, которую вы использовали в
главе~\ref{ch:09},-- это \textit{условная} директива \lstinline!~[!. Эта директива
замыкается соответствующей директивой \lstinline!~]!, а между ними находятся выражения,
разделённые \lstinline!~;!. Работа директивы \lstinline!~[!~-- выбрать одно из выражений,
которое затем обрабатывается в \lstinline{FORMAT}. Без модификаторов или параметров выражение
выбирается по числовому индексу; директива \lstinline!~[! использует аргумент формата,
который должен быть числом, и выбирает \textit{N}-ное (считая от нуля) выражение, где
\textit{N}~-- значение аргумента.
\begin{myverb}
(format nil "~[cero~;uno~;dos~]" 0) ==> "cero"
(format nil "~[cero~;uno~;dos~]" 1) ==> "uno"
(format nil "~[cero~;uno~;dos~]" 2) ==> "dos"
\end{myverb}
Если значение аргумента больше, чем число выражений, то ничего не печатается.
\begin{myverb}
(format nil "~[cero~;uno~;dos~]" 3) ==> ""
\end{myverb}
Однако если последний разделитель выражений~-- это \lstinline!~:;! вместо \lstinline!~;,!
тогда последнее выражение служит выражением по умолчанию.
\begin{myverb}
(format nil "~[cero~;uno~;dos~:;mucho~]" 3) ==> "mucho"
(format nil "~[cero~;uno~;dos~:;mucho~]" 100) ==> "mucho"
\end{myverb}
Также можно определить выбранное выражение, используя префиксный параметр. Хотя было бы
глупо применять константу, записанную прямо в управляющей строке, вспомним, что знак
'\lstinline!#!', используемый в качестве префиксного параметра, означает число оставшихся
для обработки аргументов. Поэтому вы можете определить строку формата следующим образом:
\begin{myverb}
(defparameter *list-etc*
"~#[NONE~;~a~;~a and ~a~:;~a, ~a~]~#[~; and ~a~:;, ~a, etc~].")
\end{myverb}
\noindent{}и использовать её так:
\begin{myverb}
(format nil *list-etc*) ==> "NONE."
(format nil *list-etc* 'a) ==> "A."
(format nil *list-etc* 'a 'b) ==> "A and B."
(format nil *list-etc* 'a 'b 'c) ==> "A, B and C."
(format nil *list-etc* 'a 'b 'c 'd) ==> "A, B, C, etc."
(format nil *list-etc* 'a 'b 'c 'd 'e) ==> "A, B, C, etc."
\end{myverb}
Заметим, что управляющая строка в действительности содержит две \lstinline!~[~]!
директивы, обе из которых применяют \lstinline!#! для выбора используемого
выражения. Первая директива использует от нуля до двух аргументов, тогда как вторая
использует ещё один, если он есть. \lstinline{FORMAT} молча проигнорирует любые аргументы
сверх использованных во время обработки управляющей строки.
С модификатором \textit{двоеточие} \lstinline!~[! может содержать только два выражения;
директива использует единственный аргумент и обрабатывает первое выражение, если аргумент
\lstinline{NIL}, и второе выражение в противном случае. Вы уже использовали этот вариант
\lstinline!~[! в главе~\ref{ch:09} для формирования сообщений типа сработало/не сработало
(pass/fail), таких как это:
\begin{myverb}
(format t "~:[FAIL~;pass~]" test-result)
\end{myverb}
Заметим, что оба выражения могут быть пустыми, но директива должна содержать~\lstinline!~;!.
Наконец, с модификатором \lstinline!@! директива \lstinline!~[! может иметь только одно
выражение. Директива использует первый аргумент и, если он отличен от \lstinline{NIL},
обрабатывает выражение, при этом список аргументов восстанавливается заново, чтобы первый
аргумент был доступен для использования заново.
\begin{myverb}
(format nil "~@[x = ~a ~]~@[y = ~a~]" 10 20) ==> "x = 10 y = 20"
(format nil "~@[x = ~a ~]~@[y = ~a~]" 10 nil) ==> "x = 10 "
(format nil "~@[x = ~a ~]~@[y = ~a~]" nil 20) ==> "y = 20"
(format nil "~@[x = ~a ~]~@[y = ~a~]" nil nil) ==> ""
\end{myverb}
\section{Итерация}
Другая директива \lstinline{FORMAT}, мимоходом виденная вами,~-- это директива итерации
\lstinline!~{!. Эта директива сообщает \lstinline{FORMAT} перебрать элементы списка или
неявного списка аргументов формата.
Без модификаторов \lstinline!~{! принимает один аргумент формата, который должен являться списком. Подобно директиве
\lstinline!~[!, которой всегда соответствует директива \lstinline!~]!, директива \lstinline!~{! всегда имеет соответствующую замыкающую
\lstinline!~}!. Текст между двумя маркерами обрабатывается как управляющая строка, которая выбирает свой аргумент из
списка, полученного директивой \lstinline!~{!. \lstinline{FORMAT} будет циклически обрабатывать эту
управляющую строку до тех пор, пока в перебираемом списке не останется элементов. В~следующем примере \lstinline!~{!
принимает один аргумент формата, список \lstinline{(1 2 3)} и затем обрабатывает управляющую строку <<\lstinline!~a, !>>, повторяя,
пока все элементы списка не будут использованы.
\begin{myverb}
(format nil "~{~a, ~}" (list 1 2 3)) ==> "1, 2, 3, "
\end{myverb}
При этом раздражает, что при печати за последним элементом списка следуют запятая и пробел. Вы можете исправить
это директивой \lstinline!~^!; внутри тела \lstinline!~{! директива \lstinline!~^! заставляет итерацию немедленно остановиться и, если в
списке не остаётся больше элементов, прервать обработку оставшейся части управляющей строки. Таким образом, для
предотвращения печати запятой и пробела после последнего элемента в списке вы можете предварить его \lstinline!~^!.
\begin{myverb}
(format nil "~{~a~^, ~}" (list 1 2 3)) ==> "1, 2, 3"
\end{myverb}
После первых двух итераций, при обработке \lstinline!~^!, в списке остаются необработанные
элементы. При этом на третий раз, после того как директива \lstinline!~a! обработает
\lstinline{3}, \lstinline!~^! заставит \lstinline{FORMAT} прервать итерацию без печати запятой и
пробела.
С модификатором \lstinline!@! \lstinline!~{! обработает оставшийся аргумент формата как список.
\begin{myverb}
(format nil "~@{~a~^, ~}" 1 2 3) ==> "1, 2, 3"
\end{myverb}
Внутри тела \lstinline!~{...~}! специальный префиксный параметр \lstinline!#! ссылается на число необработанных элементов списка, а
не на число оставшихся элементов формата. Вы можете использовать его вместе с директивой \lstinline!~[! для печати
разделённого запятыми списка с <<and>> перед последним элементом, вот так:
\begin{myverb}
(format nil "~{~a~#[~;, and ~:;, ~]~}" (list 1 2 3)) ==> "1, 2, and 3"
\end{myverb}
Тем не менее этот подход совершенно не работает, когда список имеет длину в два элемента,
поскольку тогда добавляется лишняя запятая.
\begin{myverb}
(format nil "~{~a~#[~;, and ~:;, ~]~}" (list 1 2)) ==> "1, and 2"
\end{myverb}
Вы можете поправить это кучей способов. Следующий пользуется эффектом, который даёт директива \lstinline!~@{!, когда она
заключена внутри другой директивы \lstinline!~{! или \lstinline!~@{!~-- в этом случае она перебирает все элементы, оставшиеся в
обрабатываемом внешней директивой \lstinline!~{! списке. Вы можете объединить её с директивой \lstinline!~#[!, чтобы следующая
управляющая строка форматировала список в соответствии с английской грамматикой:
\begin{myverb}
(defparameter *english-list*
"~{~#[~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~}")
(format nil *english-list* '()) ==> ""
(format nil *english-list* '(1)) ==> "1"
(format nil *english-list* '(1 2)) ==> "1 and 2"
(format nil *english-list* '(1 2 3)) ==> "1, 2, and 3"
(format nil *english-list* '(1 2 3 4)) ==> "1, 2, 3, and 4"
\end{myverb}
В~то время как эта управляющая строка приближается к такому классу кода, который трудно понять, после того как
он написан, всё-таки его можно понять, если у вас есть немного времени. Внешние
\lstinline!~{...~}! принимают список и затем перебирают его. Всё тело цикла состоит из \lstinline!~#[...~];!, печать
производится на каждом шаге цикла и таким образом зависит от количества обрабатываемых элементов, оставшихся в
списке. Разделяя директиву \lstinline!~#[...~]! разделителями выражений \lstinline!~;!, вы можете увидеть, что она состоит из
четырёх выражений, последнее из которых является выражением по умолчанию, поскольку его предваряет \lstinline!~:;!, в
отличие от простой \lstinline!~;!. Первое выражение, выполняющееся при нулевом числе элементов, пусто, оно нужно только
в том случае, если обрабатываемых элементов больше не осталось, тогда мы должны остановиться. Второе выражение с
помощью простой директивы \lstinline!~a! обрабатывает случай, когда элемент единственный. С двумя элементами справляется
<<\lstinline!~a and ~a!>>. И выражение по умолчанию, которое имеет дело с тремя и более элементами, состоит из другой
директивы итерации, в этот раз используется \lstinline!~@{! для перебора оставшихся элементов списка, обрабатываемых внешней
\lstinline!~{!. В~итоге тело цикла представляет собой управляющую строку, которая может корректно обработать список трёх
или более элементов, что в данной ситуации более чем достаточно. Поскольку цикл \lstinline!~@{! использует все
оставшиеся элементы списка, внешний цикл выполняется только один раз.
Если вы хотите напечатать что-нибудь особенное, например <<\lstinline!<empty>!>>, когда
список пуст, то у вас есть пара способов это сделать. Возможно, проще всего будет вставить
нужный вам текст в первое (нулевое) выражение внешней \lstinline!~#[! и затем добавить
модификатор \textit{двоеточие} к замыкающей \lstinline!~}! внешнего цикла~-- двоеточие
заставит цикл выполниться по меньшей мере один раз, даже если список пуст, и в этот момент
\lstinline{FORMAT} обработает нулевое выражение условной директивы.
\begin{myverb}
(defparameter *english-list*
"~{~#[<empty>~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~:}")
(format nil *english-list* '()) ==> "<empty>"
\end{myverb}
Удивительно, что директива \lstinline!~{! предоставляет даже больше вариантов с различными комбинациями префиксных
параметров и модификаторов. Я не буду обсуждать их, только скажу, что вы можете использовать целочисленный
префиксный параметр для ограничения максимального числа выполнений итерации, и что с модификатором \textit{двоеточие}
каждый элемент списка (как настоящего, так и созданного директивой \lstinline!~@{!) сам должен быть списком, чьи
элементы затем будут использованы как аргументы управляющей строки в директиве \lstinline!~:{...~}!.
\section{Тройной прыжок}
Более простой директивой является \lstinline!~*!, которая позволяет вам перемещаться по
списку аргументов формата. В~своей основной форме, без модификаторов, она прос\-то
пропускает следующий аргумент, при этом ничего не печатается. Однако чаще она используется
с модификатором \textit{двоеточие}, который заставляет её отступить назад, позволяя одному
и тому же аргументу быть обработанным дважды. Например, вы можете использовать
\lstinline!~:*! для печати числового аргумента один раз словом, а другой раз цифрами, вот
так:
\begin{myverb}
(format nil "~r ~:*(~d)" 1) ==> "one (1)"
\end{myverb}
Еще вы можете реализовать директиву, похожую на \lstinline!~:P!, для неправильной формы множественного числа (в английском
языке), объединяя \lstinline!~:*! с~\lstinline!~[!.
\begin{myverb}
(format nil "I saw ~r el~:*~[ves~;f~:;ves~]." 0) ==> "I saw zero elves."
(format nil "I saw ~r el~:*~[ves~;f~:;ves~]." 1) ==> "I saw one elf."
(format nil "I saw ~r el~:*~[ves~;f~:;ves~]." 2) ==> "I saw two elves."
\end{myverb}
В~этой управляющей строке \lstinline!~R! печатает аргумент формата в виде количественного числительного. Затем директива
\lstinline!~:*! возвращается назад, так что число также используется как аргумент директивы \lstinline!~[!, выбирая между
выражениями, когда число равно нулю, единице или чему-нибудь ещё\footnote{Если вы находите фразу <<I saw zero elves>>
(<<Я видел нуль эльфов>>) немного неуклюжей, то можете использовать слегка усовершенствованную управляющую строку,
которая использует \lstinline!~:*! иначе, вот таким образом:
\begin{myverb}
(format nil "I saw ~[no~:;~:*~r~] el~:*~[ves~;f~:;ves~]." 0) ==> "I saw no elves."
(format nil "I saw ~[no~:;~:*~r~] el~:*~[ves~;f~:;ves~]." 1) ==> "I saw one elf."
(format nil "I saw ~[no~:;~:*~r~] el~:*~[ves~;f~:;ves~]." 2) ==> "I saw two elves."
\end{myverb}
}\hspace{\footnotenegspace}.
Внутри директивы \lstinline!~{! \lstinline!~*! пропускает или перескакивает назад через элементы списка. Например, вы можете
напечатать только ключи в списке свойств (plist), таким образом:
\begin{myverb}
(format nil "~{~s~*~^ ~}" '(:a 10 :b 20)) ==> ":A :B"
\end{myverb}
Директиве \lstinline!~*! также может быть задан префиксный параметр. Без модификаторов или
с модификатором \textit{двоеточие} этот параметр определяет число аргументов, на которое
нужно передвинуться вперёд или назад, и по умолчанию равняется единице. С~модификатором
\lstinline!@! префиксный параметр определяет абсолютный, отсчитываемый от нуля индекс
аргумента, на который нужно перемеситься, по умолчанию это нуль. Вариант \lstinline!~*! с
\lstinline!@! может быть полезен, если вы хотите использовать различные управляющие строки
для формирования различных сообщений для одних и тех же аргументов и если этим сообщениям
нужно использовать свои аргументы в различном порядке\footnote{Эта проблема может
возникнуть при попытке локализовать приложение и перевести удо\-бо\-чи\-тае\-мые сообщения на
различные языки. \lstinline{FORMAT} может помочь с некоторыми из этих проблем, но это далеко
на полноценная система локализации.}\hspace{\footnotenegspace}.
\section{И многое другое...}
Осталось ещё многое~-- я не упоминал о директиве \lstinline!~?!, которая может брать
фрагменты управляющих строк из аргументов формата, или директиве \lstinline!~/!, которая
позволяет вызвать произвольную функцию для обработки следующего аргумента формата. И ещё
остались все директивы для формирования табличной и удобочитаемой печати. Но уже
затронутых в этой главе директив пока будет вполне достаточно.
В~следующей главе вы перейдёте к системе условий и перезапусков Common Lisp, аналогичной системам
исключений и обработки ошибок, присутствующих в других языках.
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "pcl-ru"
%%% TeX-open-quote: "<<"
%%% TeX-close-quote: ">>"
%%% End: