Common Lisp 中有一个叫作打印器的概念,它包含所有用于写出 Lisp 数据的文本表示的函数。其中一个函数是format,它让程序员可以按自己的需要格式化数据。
format以及 Lisp 打印器整体上的能力是一个很大的话题;这里将介绍使用format的基础知识。
函数format至少接受两个实参:一个是要打印到的流,另一个是定义打印什么以及如何打印的控制字符串。
流本身是一个单独的概念,会在别处介绍。不过现在只需要知道:打印是输出到流上的,而这个实参有两个特殊值:t和nil。t表示打印到标准输出(实际上是变量*standard-output*所绑定的那个流),nil表示创建一个字符串并返回它。
注意,除非在后一种情况下把nil指定为流,否则format求值的结果总是nil。这是进行简单的字符串插值或构造的常见方式。
(format nil "hello world") ; => "hello world"
(format t "hello world") ; => NIL (but "hello world" is printed to standard output)
控制字符串可以包含“格式指示符”,它们告诉format该打印什么,以及如何解释传给format的其他实参。某种意义上,控制字符串就是一个由format解释并运行的程序。所有指示符都以~开头,后面跟一个字符(例如~A、~D、~&)。字符的大小写无关紧要。
多数格式指示符只是把一个传给format的实参插入到输出中。这叫作“消耗”该实参。有些指示符不消耗实参,有些则消耗不止一个。甚至有一个指示符允许你在实参列表中来回跳转,或跳过某些实参。
一个非常通用的格式指示符是~A。它会打印数据的“人类可读”版本。
(format nil "Value = ~a" 10) ; => "10"
(format nil "hello ~a" "world") ; => "hello world"
(format nil "The list is: ~A" (list 1 2 3)) ; => "The list is: (1 2 3)"
另一个有用的格式指示符是~S。它会打印数据的“机器可读”版本。如果你想能把生成的输出当作 Lisp 数据来解读,这会非常有用。对许多东西来说,它看起来和~A一样,但要注意,比如对字符串而言,输出中会包含引号(当被嵌入另一个字符串时会被转义),这样这个值就能作为字符串实参读回来。
(format nil "~s" 10) ; => "10"
(format nil "~s" "hello world") ; => "\"hello world\""
(format nil "~s" (list 1 2 3)) ; => "(1 2 3)"
另外两个非常有用的指示符是~%和~&。前者输出一个换行符,后者输出一个“新行”。区别在于,~%总是输出一个换行符,而~&只在当前不在行首时才输出一个。这两者都不消耗实参。
(format nil "~%new~%lines") ; => "
new
lines"
(format nil "~&new~&lines" ; => "new
lines"
最后,~~是一个简单的指示符,会输出一个字面量~。
和几乎所有有工作的人一样,Lisp 外星人 Layla 有时也需要做报告,让别人了解一些信息。他们想请你帮忙,把她为这些报告准备的数据格式化好。
Layla 首先需要一个函数:format-quarter-value,它接收两个形参:日历季度和数值,并把它们格式化后用于报告。它应该求值为一个字符串。
(format-quarter-value "last" 3.14) ; => "The value last quarter: 3.14"
(format-quarter-value "this" 0) ; => "The value this quarter: 0"
Layla 感谢你写了 format-quarter-value函数,并说它在下一个需要的函数中会非常有用。
下一份报告需要把两个季度的季度和数值分别打印在两行上。
另外,输出需要写入作为第一个实参传入的流。
例如:(format-two-quarters t "last" 3.14 "this" 0)会在标准输出上产生以下内容:
The value last quarter: 3.14
The value this quarter: 0
注意它前后的空行。
Layla 忘了告诉你,Lisp 外星人喜欢以可读作 Lisp 数据的方式来查看值。所以他们需要一个新函数,它的输出可以当作 Lisp 数据读取(同样写入指定的流):
(format-two-quarters-for-reading nil "last" 3.14 "this" 0) ; =>
"(\"The value last quarter: 3.14\" \"The value this quarter: 0\")"