莫看,莫看,莫看。重要的事说三遍,浪费时间我不管。
文化编程(Literate Programming)由计算机学界名宿 Donald Knuth 所创。其名,大陆译为「文学编程」,甚缪。将程序视为蛮夷,以文教化,Literate Programming 之意大抵如是。
文化,其目的在于实现人与人的沟通交流。倘若这世上只有一人,文化是多余的。同理,倘若世上只有一个编程者,文化编程也是多余的。程序的属性分为两部分,一是供机器执行,二是供人类阅读。程序能正确被机器执行,这是第一性,但程序要继续维护下去,其代码更易于为他人理解,这是第二性,二者缺一不可。所以,程序要有文化。
Knuth 创立文化编程,江湖传言,是报 E. W. Dijkstra 的一箭之仇。 Dijkstra 也是计算机学界名宿。当年,他提出了结构化编程,指出应当像设计机器那样编写程序,而 Knuth 写的程序不够结构化,或许为此而不快。创立文化编程之后,Knuth 就可以说 Dijkstra 没文化,当然他不会这样说。
江湖恩仇,儿女情长,这些事,说起来没完没了,到此为止。这篇文章,真正的主角是我。
Knuth 虽然提出了文化编程,可惜的是,他创造的文化编程工具 CWEB 有历史局限性,现在不是很好用。这可以理解,毕竟这个工具最初是他自用,并且在写这个工具之时,也只有美国人有更多的机会使用计算机。
好在这种工具实现起来也不太难,去年过年的那段时间,我写了一个类似的工具,名曰 orez,意思是 zero 翻转。为了防止百年之后有人再乱翻译,我先将它翻译为逆零 :)
有文化的代码
orez 基本上遵循 CWEB 的思路——程序的代码与文档,彼此独立,但又混而为一。若以泡茶为喻,则代码之于茶叶,文档之于水,而 orez 之于茶壶。代码与文档的混合体,可称之为有文化的代码。
代码与文档的混合需要借助一组简单的标记方能实现。下面,以一组五言诗句(姑且当其是)为代码,以诗句解析作为文档,演示这些标记的用法。这组诗句很直白,是一位无名氏,于一次磨刀之后随手而作,记录了磨刀的过程,赞美了磨过之后刀如何锋利,最后感慨真正锋利的刀无人赏识而隐没于陋鞘。
代码与文档皆以段落的形式出现,并且总是以 @
符号领起。例如:
@ 一位侠士用水浸泡砥石,准备亲手去磨已钝了的未名刀:
@ 准备 #
沧浪三瓢水
砥石浴盆中
侠士自磨刀
霜刃名未名
二者的区别是,代码段落有名字,即 @
与 #
之间的文字,例如「准备
」。代码段落的名字可根据实际情况而取,此处仅仅是追求足够简单。
倘若有文化的代码是以文档开始,那么首个文档段落的领起符号可省略。例如:
侠士用水浸泡砥石,准备亲手去磨已钝了的未名刀:
@ 准备 #
沧浪三瓢水
砥石浴盆中
侠士自磨刀
霜刃名未名
若假设代码段落之后总存在一个文档段落,哪怕这个段落没有任何内容,那么文档段落的领起符便可以在形式上作为代码段落的终止符。虽然不必非如此不可,但是在以文档段落开始的情况下,我更喜欢这种形式。例如:
侠士用水浸泡砥石,准备亲手去磨已钝了的未名刀:
@ 准备 #
沧浪三瓢水
砥石浴盆中
侠士自磨刀
霜刃名未名
@
这样一来,若代码段落后面是文档段落,那么该文档段落领起符已存在。若代码段落后面依然是代码段落,那么二者之间会夹杂一个内容为空的文档段落,无碍。
接下来再继续增加文档段落与代码段落:
磨刀,主要是靠腰部力量,手起到固定刀片倾斜角度的作用:
@ 姿势 #
力从足下起
腰旋若扭松
双手互犄角
刃行掠风轻
@
刀片在砥石上的前进方向总是与锋线垂直,并且需要时不时用手试
一下刀锋是否已磨到卷边的程度:
@ 技巧 #
锋线有曲直
法向自无知
进退两茫然
探手试微疵
@
假设所有的文档段落与代码段落皆已写出,那么最后可通过代码段落的引用标记,将所有已给出的代码段落汇总到一起。例如:
@ 磨刀歌 #
# 准备 @
# 姿势 @
# 技巧 @
... ...
@
形如 # ... @
这样的结构,便是已给出的代码段落的引用,亦即所引用的代码段落的内容最终会嵌入到被引用处。同一个代码段落,可于多处被引用。
貌离神合
倘若不想为每个代码段落都取个名字,毕竟取名字很烦人,可以考虑使用同名代码段,运算符 +
用于标记同名代码段落内容连接起来,表示一个完整的代码段落。
例如:
侠士用水浸泡砥石,准备亲手去磨已钝了的未名刀:
@ 磨刀歌 #
沧浪三瓢水
砥石浴盆中
侠士自磨刀
霜刃名未名
@
磨刀,主要是靠腰部力量,手起到固定刀片倾斜角度的作用:
@ 磨刀歌 # +
力从足下起
腰旋若扭松
双手互犄角
刃行掠风轻
@
刀片在砥石上的前进方向总是与锋线垂直,并且需要时不时用手试
一下刀锋是否已磨到卷边的程度:
@ 磨刀歌 # +
锋线有曲直
法向自无知
进退两茫然
探手试微疵
@
上述三个代码段落,虽然分散各处,但它们实际上是由两个 +
运算符连接而成的一个整体:
@ 磨刀歌 #
沧浪三瓢水
砥石浴盆中
侠士自磨刀
霜刃名未名
力从足下起
腰旋若扭松
双手互犄角
刃行掠风轻
锋线有曲直
法向自无知
进退两茫然
探手试微疵
@
+
运算符表示自上而下顺次连接同名的代码段落,其逆运算符 ^+
则表示自下而上连接同名的代码段落。例如:
@ 磨刀歌 # ^+
磨刀歌
无名氏
@
这时,完整的代码段落如下:
@ 磨刀歌 #
磨刀歌
无名氏
沧浪三瓢水
砥石浴盆中
侠士自磨刀
霜刃名未名
力从足下起
腰旋若扭松
双手互犄角
刃行掠风轻
锋线有曲直
法向自无知
进退两茫然
探手试微疵
@
标签
有时,需要将一个代码段落连接到某个特定的同名代码段落之前或之后,这时需要借助标签。
首先需要在目标段落上放一个标签。例如:
@ 磨刀歌 #
<第一节>
沧浪三瓢水
砥石浴盆中
侠士自磨刀
霜刃名未名
@
<第一节>
即标签。标签必须置于代码段落标题行的下一行,且独占一行。
在源段落中,将目标段落标签与 +
或 ^+
运算符组合起来,便可将源段落与目标段落连接起来。例如:
@ 磨刀歌 # <第一节> ^+
磨刀歌
无名氏
@
结果依然是:
@ 磨刀歌 #
磨刀歌
无名氏
沧浪三瓢水
砥石浴盆中
... ...
@
输出
假设程序代码与文档内容混合体——有文化的代码,存于文本文件 foo.orz。现在,若从中导出给人看的文档——可喻为倒茶喝,只需使用以下命令:
$ orez -w foo.orz -o foo.yml
生成的 foo.yml 是一份带有排版信息的中间文件,它最终能转化为什么文件,这取决于程序代码与文档内容是基于何种排版语言混而为一。倘若是基于 Markdown 语言,便可使用 orez-md 工具,将 foo.yml 转换为 foo.md 文件(Markdown 文件),然后,再用其他工具将 foo.md 文件转化为网页或其他形式的文档形式。
在 Linux 系统(或类 Unix 系统)中,从 foo.orz 到 foo.md 的整个过程,可浓缩为一条命令:
$ orez -w foo.orz | orez-md > foo.md
foo.md 就是从 orez 这个茶壶嘴里流出的茶水。
一壶茶喝到了没滋味的时候,又该怎样倒出茶叶呢?打开壶盖,倒出来。不过,为了更贴近 orez 的逻辑,需要将茶叶视为袋泡茶,揪住袋上棉线,将茶叶从茶壶中拎出来。例如:
$ orez -t -e "磨刀歌" foo.orz -o mdg.txt
最后所得 mdg.txt 文件便是以 磨刀歌
为棉线而拎出的袋泡茶。
尾巴
更多细节见 《orez 的故事》。之所以又重写一篇简略介绍,是因为以后要用 orez 的一些标记。