文学编程简介
Table of Contents
欢迎来到文学编程的世界! 本文会简单介绍一下如何使用org-mode进行文学编程
本文的内容来源于PDX Emacs Hackers举办的研讨会, 但是考虑到很少人能够现场参加该研讨会,因此我决定把它写成一片指南. 我假设你已经有了一定的Emacs基础 并且对org-mode也比较熟悉 ,至少知道如何导出文档.
众所周知,Org的功能太多了, 它具有在一个文档中编写,执行和连接代码块的能力,这种能力十分强大, 要把这些功能都写成文档是件让人觉的恐怖的任务. 我希望这份指南能开个好头, 若你发现我遗漏了什么,请告之.
警告 :下面的例子都写得很蹩脚.
1 必要条件
本文基于org-mode v8.2.10(或更新). 由于Emacs已经内置了org, 你需要通过 C-h v <RET> org-version
来看所使用的org是否符合要求,以保障本指南例子能够正确运行(大多数的例子应该没问题,但是也不好说…).
若你的org版本太旧,请运行下面这段代码:
(when (>= emacs-major-version 24) (require 'package) (add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t) (package-initialize) (package-refresh-contents) (package-install "org"))
然后应该就装上了最新的org了. 此外你还需要安装 org-plus-contrib.
在这篇指南中,我会向你展示多种语言的接口,但这也意味着你需要在本地安装大量的解释器,你至少需要安装下面几个解释器:
- Shell (很抱歉,对于Windows用户来说,这一点恐怕比较难实现)
- Python
- Ruby
当然,你是个聪明人,把这些例子修改成你所使用的语言应该是很容易的.
2 背景知识
在我们开始学习使用org-mode 进行文学编程之前,我先简单介绍一下文学编程吧.
2.1 为什么使用文学编程?
文学编程最初是由Donald Knuth于20世纪80年代提出来的,旨在加强团队成员中的交流. 如Donald Knuth 所说过的
让我们改变针对程序结构的传统看法吧. 不要把我们的任务看成是向计算机描述怎么去作,而是向其他人解释我们想让计算机做什么.
文学编程希望能做到写得程序适于人阅读,文档描述能遵循问题逻辑的顺序,同时不受制于编程语言的缺陷. 下面是一份文学编程的例子,对人来说,这是份可阅读的文档,同时它还是源代码文件:
其背后的思想是反转注释与代码之间的关系,以前是代码中点缀着些注释,现在则是在文档中内嵌一些代码.
由于从文学编程文档中抽取出来的源代码与直接编写的源代码无异,因此这一做法并不需要现有的编程环境做出什么改变.(although it is used, to various degrees, in niche circles).
3 为什么使用Org?
Knuth最开始认为文学编程只需要使用最简单功能的编辑器就行, 因为他只是谢了noweb程序来创建文学编程文档并从中抽取源代码.
依我的观点,文学编程必须依赖编辑器的帮助,iPython’s notebook就是个例子. 但是我认为文档内容应该以可读文本的方式存储,而不是存储为JSON格式的文件. 就像 Carsten Dominik 说过的:
在第三个千禧年,使用文本文件是否还有意义? 文本文件是唯一真正可以说是可移植的文件格式. 用文本文件保存的数据永远不会丢失.
因此若你想进行文学编程,org-mode是个很好的选择,更何况我们本来就已经在用org-mode作许多事情了, 对吧?
4 使用Org有什么优势?
使用org-mode进行文学编程有如下几点优势:
- 代码更易理解
- 方便团队讨论问题
- 针对复杂状况更容易思考的清晰一点
- 学习新库和API时,可以一边实验一边记笔记
- 可以同时使用多种语言,每种语言只完成自己擅长的任务(例如可以使用通用语言来查询,维护数据库)
- org具有组织化的能力,它提供了类似Agenda和task这类的工具
5 警告
1980年的软件世界与今天的情况相比,已经发生了翻天覆地的改变. 现在的工程师们连接的更紧密了,且常常以团队的形式进行工作. 虽然如此,但各个工程师所使用的工具还是各不相同的. 即使你的团队并不使用Emacs, 你依然会觉得org-mode的方法很有用处.
首先,org-mode很适合用于设计复杂算法,当你有什么灵感的时候,可以立即写下最终的代码(你所写下的记录会成为代码中的注释), 当你毫无进展时,你可以直接把记录发给团队中的其他人敬候回应.
其次,org-mode文件可以被认为是一个repl环境,因为其中的每个代码块都能被分别执行,并且代码块的执行结果还能传递给其他的代码块… but I’m getting a head of myself.
6 导出文档
使用org-mode的主要原因是可以将org文件导出成HTML,电子邮件信息,Wiki等许多格式的文档. Org特别适合于写科技类的文章,我主要用它来程序代码相关的东西.
org-mode还支持LaTex,下面是个例子:
- Unicode References :: for instance, \alpha, \beta and \gamma. - Subscripts :: like Hydrogen atoms, H_2, and Water, H_{2}O. - Superscripts :: The mass of the sun is 1.989 x 10^30 kg. - Embedded Equations :: Surrounded with either single =$=, like $a^2=b$, or escaped parenthesis, like: \( b=\frac{1}{2} \) - Separated equations :: Either in double =$$= or escaped brackets, like this: $$ a=\frac{1}{2}\sqrt{2} $$ or this: \[ a=-\sqrt{2} \] or this: \begin{equation} x=\sqrt{b} \end{equation} #+OPTIONS: tex:t
上面那段内容可以转换成下面的HTML展示:
Unicode References for instance, α, β and γ. Subscripts like Hydrogen atoms, H[2], and Water, H[2]O. Superscripts The mass of the sun is 1.989 x 10^30 kg. Embedded Equations Surrounded with either single $, like \(a^2=b\), or escaped parenthesis, like: \( b=\frac{1}{2} \) Separated equations Either in double $$ or escaped brackets, like this: \[ a=\frac{1}{2}\sqrt{2} \] or this: \[ a=-\sqrt{2} \] or this: \begin{equation} x=\sqrt{b} \end{equation}
7 基础知识
这篇指南来源于一场研讨会, 那么让我们运行Emacs,创建一个org-mode文件,然后开始操作.
让我们输入以下代码块(大小写无关):
#+BEGIN_SRC emacs-lisp (directory-files ".") #+END_SRC
- 按下
C-c C-c
会执行该命令,并且将结果插入到文件后面 … we’ll use that to our advantage later. - 按下
C-c '(单引号)
可以以指定语言的mode来编辑这段代码. 这样你就可以利用paredit
或者类似的插件来帮你编辑代码了.
注意: 发现我的例子有错误后(注意,我并没有说"如果"), 去查一下 the org-mode manual,然后将勘误发给我.
7.1 快捷方式
若你使用比较新版本的Emacs(新于v22)或这时比较新版本的org-mode(你可能直接从ELPA中安装),那么你可以使用 Org Templates,它提供了如下功能:
- 输入
<s <TAB>
可以快速创建一个代码块 - 如果没有创建代码块,或许你可以试试 yasnippets
- 从代码块的
BEGIN
到END
部分的任意位置,你都可以通过按下C-c C-c
来执行代码 - 使用
C-c M-f
跳转到文件中下一个代码块位置,使用C-c M-b
跳转到上一个代码块位置
7.2 推荐配置
要想语法高亮代码块中的代码,可以将下面代码放到 .emacs
初始化文件中:
(setq org-confirm-babel-evaluate nil org-src-fontify-natively t org-src-tab-acts-natively t)
将 org-confirm-babel-evalute
设置为nil,可以在你用 C-c C-c
执行代码块时,不再提示“Do you want to execute”
8 支持的语言
Org-mode支持许多的编程语言, 但还是有些语言是不支持的(当然,你可以为Org-mode添加新语言的支持,这并不难).
我会展示一些比较流行的语言案例,从中你也可以看出不同语言之间的些许差别.
8.1 Ruby案例
让我们将上面lisp的例子修改为Ruby:
#+BEGIN_SRC ruby Dir.entries('.') #+END_SRC
你再按下 C-c C-c
发现没有反映, 这是因为你需要预先加载好Ruby语言的支持: M-x load-library <RET> ob-ruby
你也可以将下面配置放到 .emacs
中:
(require 'ob-ruby)
8.2 Python案例
需要注意的是,Ruby和Lisp类似,会自动将最后表达式的值作为代码块的返回值. 然而,Python语言,需要明确的 return
语句:
#+BEGIN_SRC python from os import listdir return listdir(".") #+END_SRC
8.3 shell案例
大多数的语言使用返回值作为结果,然而shell语言使用输出到标准输入的内容作为结果:
#+BEGIN_SRC sh
ls -1
#+END_SRC
若按下 C-c C-c
没反映,你需要执行: M-x load-library <RET> ob-sh
8.4 其他语言
若你像我一样,是个多语言开发者,你可以添加类似下面的配置到 .emacs
文件中:
(org-babel-do-load-languages 'org-babel-load-languages '((sh . t) (js . t) (emacs-lisp . t) (perl . t) (scala . t) (clojure . t) (python . t) (ruby . t) (dot . t) (css . t) (plantuml . t)))
9 代码块设置
通过设置不同的代码块参数(也称为"头参数")可以产生各种有趣的结果. 代码块可以有0个或多个头参数.
首先我们先讨论一下几种设置头参数的方式,然后在讨论这些参数的意义. 让我先以一个头参数为例.
9.1 例子: dir参数
下面以dir 参数为例来看如何设置一个参数, 该参数会设置代码块执行的工作目录:
#+BEGIN_SRC sh :dir /etc ls #+END_SRC
按下 C-c C-c
执行代码块,你会看到列出了许多 /etc
目录下的内容,当然前提是你有 /etc
目录…
该参数的一个有趣的用法是,可以通过Tramp实现在远程服务器上执行代码:
#+BEGIN_SRC sh hostname -f #+END_SRC #+RESULTS: : blobfish #+BEGIN_SRC sh :dir /howardabrams.com: hostname -f #+END_SRC #+RESULTS: : goblin.howardabrams.com
9.2 设置头参数的位置
不同位置设置参数所影响的作用域也不一样. 下面几个位置都可以用于设置头参数,以作用域从特殊到一般的顺序列出.
- 头参数嵌入到代码块中,或者在代码块上面
- 设置某一标题下面所有代码块的默认头参数
- 设置整个文档中所有代码块的默认头参数
- 设置所有文档中所有代码块的默认头参数
对我来说,为所有文档设置某个参数没有什么意义. 但若你需要,可以设置下列参数:
- org-babel-default-header-args
- org-babel-default-header-args:<lang>
注意:你可以在代码块被调用时设置头参数. 我们还会在下面接着提到.
9.3 太多参数了?
以将参数嵌入代码块的方式设置少量头参数还行,但是org-mode本身支持许多的参数, 若你需要设置许多的参数的话, 可以考虑将一个或多个参数放到代码块的上面去. 比如下面几个例子都是等价的:
#+BEGIN_SRC sh :dir /etc :var USER="howard" grep $USER passwd #+END_SRC #+HEADER: :dir /etc #+BEGIN_SRC sh :var USER="howard" grep $USER passwd #+END_SRC #+HEADER: :dir /etc #+HEADER: :var USER="howard" #+BEGIN_SRC sh grep $USER passwd #+END_SRC
9.4 设置某一标题下面所有代码块的默认头参数
若某一标题下面的代码块使用相同的头参数,则可以将头参数的设置放入标题下的属性drawer中. 你可以试试按照下面步骤操作:
- 在org文件中创建一个标题
- 输入
C-c C-x p
- 输入属性名称:dir
- 输入属性值: /etc
将光标定位到 :PROPERTIES:
处,然后按下 TAB
键就会显示出隐藏的内容. 整个内容看起来应该如下所示:
* A New Section :PROPERTIES: :dir: /etc :END: #+BEGIN_SRC ruby File.absolute_path(".") #+END_SRC #+RESULTS: : /etc
9.5 指定特定语言的默认头参数
你可以指定特定语言的头参数. 步骤如下:
- 在标题上按下
C-c C-x p
- 输入属性名称
header-args:sh
- 输入属性值
:dir /etc
- 输入
C-c C-x p
- 输入属性名称
header-args:ruby
- 输入属性值
:dir /
你会得到如下结果:
* Another Section :PROPERTIES: :header-args:sh: :dir /etc :header-args:ruby: :dir / :END: #+BEGIN_SRC sh ls -d $(pwd) #+END_SRC #+RESULTS: : /etc #+BEGIN_SRC ruby File.absolute_path('.') #+END_SRC #+RESULTS: : /
注意: 有些参数智能通过 header-args
的方式设置.
9.6 为文档内的所有代码块设置默认参数
通过将 #+PROPERTY:
设置项放到文档中,可以为该文档中的所有代码块设置默认参数
#+PROPERTY: dir ~/Work
注意: 这些参数并没有在前面带上冒号
要为特定语言的代码块设置,需要进行如下操作:
#+PROPERTY: header-args:sh :tangle no
注意: 你需要在设置项上按下 C-c C-c
,否则该配置项不生效.
10 头参数的类型
基础的东西已经讲完了,剩下的内容就是讲讲各参数的意义了. 我按这些参数的用处将之分为以下几类:
- 执行类参数
- 以
dir
为代表,这类参数影响代码块如何执行 - 导出类参数
- 该类参数影响了当把org文件导出成HTML(或其他格式)时,代码块以及代码块的执行结果如何展示
- 文学编程类参数
- 将代码块连接起来,可能会改变实际的源代码
- 变脸类参数
- 通过不同方式设置代码块中的变脸
- 杂类 输入/输出
- 其他参数
11 执行类参数
下列参数会影响到代码的执行情况.
11.1 results参数
当你执行代码块时,你希望得到哪个结果呢?
- 是表达式的返回值呢?
- 还是代码的输出结果?
以下面Ruby代码块为例. 默认情况下,你的到的是最后表达式的返回值:
#+BEGIN_SRC ruby puts 'Hello World' 5 * 6 #+END_SRC #+RESULTS: : 30
若将:results 头参数的值修改为 output
, 则的的结果是程序的输出:
#+BEGIN_SRC ruby :results output puts 'Hello World' 5 * 6 #+END_SRC #+RESULTS: : Hello World
注意: sh代码块的 :results
默认值为 output
.
11.2 影响输出格式的结果
代码的执行结果会插入到文档中,它可能以以下几种格式出入.
- table
- 若结果为单个数组,则插入一行,若结果为数组的数组,则插入一个表格
- list
- 按照普通org-mode列表的格式插入一个无序列表
- verbatim
- 原样输出
- file
- 将结果写入到文件中
- html
- 认为执行的结果是HTML代码,导出时原样导出
- code
- 认为执行的结果还是原语言的代码
- silent
- 只在mini-buffer中显示执行的结果
之所以有这么多的变种,是因为执行结果本身也可以被导出(可以以HTML,或Email等格式导出), 同时这些执行结果还能作为其他代码的输入变量. 这是文学编程中最有意思的部分了,我们后面还会再提到.
11.2.1 输出成列表
注意到之前的输出是以表格的格式插入的,这次我们以列表的格式来插入:
#+BEGIN_SRC ruby :results list Dir.entries('.').sort.select do |file| file[0] != '.' end #+END_SRC #+RESULTS: - for-the-host.el - instructions.org - literate-programming-tangling.png - literate-programming-tangling2.png
上面是以Ruby为例,你可以试着使用你喜欢的语言来列出目录中的文件列表.
11.2.2 原样输出
Shell命令和日志输出比较适合使用原样输出,例如:
#+BEGIN_SRC sh :results verbatim :exports both ssh -v goblin.howardabrams.com ls mossandcrow #+END_SRC #+RESULTS: OpenSSH_6.6.1, OpenSSL 1.0.1f 6 Jan 2014 debug1: Reading configuration data /etc/ssh/ssh_config debug1: /etc/ssh/ssh_config line 19: Applying options for * debug1: Connecting to goblin.howardabrams.com [162.243.135.186] port 22. debug1: Connection established. debug1: identity file /home/howard/.ssh/id_rsa type 1 debug1: identity file /home/howard/.ssh/id_rsa-cert type -1 ...
11.2.3 Session
默认情况下,每个代码块在每次运行时都会重启自己的一个解释器. 通过为:session 头参数设置一个标签值,则所有拥有同一标签的代码块在运行时都在同一个解释器session中. 为什么要这样做呢? 因为每次都重启解释器有以下几个问题:
- 有的解释器启动时间很长,例如Clojure
- 使用Tramp登录远程机器很慢
- 代码块共享函数定义与状态
注意: 由于不同代码块之间可以传递值,因此最后那个问题实际上可以绕过.
下面的例子说明了每个代码块执行时都会重启自己的解释器:
#+BEGIN_SRC python avar = 42 return avar #+END_SRC #+RESULTS: : 42 #+BEGIN_SRC python return avar / 2 #+END_SRC #+RESULTS: NameError: global name 'avar' is not defined
而基于 :session
的解释器不会重启:
#+BEGIN_SRC ruby :session foobar avar = 42 #+END_SRC #+RESULTS: : 42 #+BEGIN_SRC ruby :session foobar avar / 2 #+END_SRC #+RESULTS: : 21
:session
常常设置为标题属性. 另外,你其实可以切换到 *foobar*
这个buffer中直接与解释器进行交互,你可以在那设置变量及其他状态,然后再执行代码块
下面的例子中有什么问题呢?
* Confusing Stuff :PROPERTIES: :session: stateful :END: #+BEGIN_SRC sh :results silent NUM_USERS=$(grep 'bash' /etc/passwd | wc -l --) #+END_SRC We have access to them: #+BEGIN_SRC sh echo $NUM_USERS #+END_SRC #+RESULTS: : 2 This doesn't return... why? #+BEGIN_SRC ruby 21 * 2 #+END_SRC
警告: 为整个section设置的 :session
参数会影响到每个代码块,而不管该代码块是哪种编程语言. 这可能不是你想要的.
11.3 将结果写入到文件中
创建并执行下面这个代码块:
#+BEGIN_SRC ruby :results output :file primes.txt require 'prime' Prime.each(5000) do |prime| p prime end #+END_SRC
你会发现执行的结果是插入了一个指向文件的链接. 点击该连接会在buffer中加载该文件.
注意: :file
参数需要与 :results output
共用,因为它不知道以哪种格式输出内部值
12 文学编程
编程时,有时需要从org-mode文件中抽取出源代码来创建源代码文件,这个过程成为tangling
12.1 Tangling
:tangle
参数会将所有同类语言的代码快中内容都写入指定的源码文件中.
#+BEGIN_SRC ruby :tangle double-space.rb while s = gets print s ; puts end #+END_SRC
输入 C-c C-v t
生成 double-space.rb
拥有相同=:tangle= 值的代码块内容会按顺序写入到同一个文件中. 若 :tangle
参数值为 yes
则写入的文件其名称与原org文件名称一样(文件后缀不一样而已).
也可以使用 PROPERTY
来为整个文件的所有代码快指定一个值:
#+PROPERTY: tangle ~/.emacs.d/elisp/bling-mode.el
13 注释
若我需要与他人分享代码,我可以讲文档内容转换成注释:
Precede each line in the text from standard in (or file) with the current line number. See one liners. #+BEGIN_SRC ruby while s = gets puts "#{$<.file.lineno}: #{s}" end #+END_SRC #+PROPERTY: tangle lineno.rb #+PROPERTY: comments org
会tangle出下面这样的Ruby代码:
# Precede each line in the text from standard in (or file) with the # current line number. # See [[http://benoithamelin.tumblr.com/ruby1line][one liners]]. while s = gets puts "#{$<.file.lineno}: #{s}" end
:comments 参数指明了是否将以及如何将文档内容作为注释插入tangle出的代码. 其值为org表示将文档内容作为org code来格式化再作为注释插入. 注意: 只有代码快上面的内容才会作为注释插入.
若 :comments
的值为 link
, 则插入的注释为链接到原org文件的连接.
由于基本上我只看文学编程的原文档内容(例如我的.emacs文件)–我几乎不会去看tangle出来的源代码,因此我觉得这个功能作用不大.
默认值为 no
,意味着不插入任何注释.
13.1 Shebang
当创建脚本时,常常会指定脚本运行的解释器. 该解释器可以通过:shebang 参数来指定(可以以代码块的header或文档属性的方式来指定)
Precede each line in the text from standard in (or file) with the current line number. See one liners. #+BEGIN_SRC ruby :shebang "#!/usr/local/bin/ruby" while s = gets puts "#{$<.file.lineno}: #{s}" end #+END_SRC #+PROPERTY: shebang #!/bin/ruby #+PROPERTY: tangle lineno #+PROPERTY: comments org
导出的结果为:
#!/usr/local/bin/ruby # Precede each line in the text from standard in (or file) with the # current line number. # See [[http://benoithamelin.tumblr.com/ruby1line][one liners]]. while s = gets puts "#{$<.file.lineno}: #{s}" end
13.2 Noweb
若你为某个代码快命了名,则其他代码块就可以包含该代码快了.… 如标题所示,使用:noweb 参数.1 . 假设有如下一个Org文件:
Print the last field of each line. #+NAME: the-script #+BEGIN_SRC ruby puts $F.last #+END_SRC #+BEGIN_SRC sh :noweb yes :tangle last-col.sh ruby -ane '<<the-script>>' #+END_SRC
会创建一个名为 last-col.sh
的源代码文件,其内容为:
ruby -ane 'puts $F.last'
这个功能有什么用呢?
Donald Knuth当时所使用的老式语言,要求所有的变量和函数需要先定义后使用. 也就是说你需要从下到上的写代码. 然而有些代码适合以从上倒下的方式进行解释. 对于某些算法来说,比较适合使用web和tangle.
13.2.1 关于Noweb的警告
假设我们有一个包含多行的代码块,如下所示:
#+NAME: prime #+BEGIN_SRC ruby require "prime" Prime.prime?(ARG[0]) #+END_SRC #+BEGIN_SRC ruby :noweb yes :tangle primes.sh cat $* | xargs ruby -ne '<<prime>>' #+END_SRC
noweb引用前的文本会被当成最初的注释字符来看待(Treats the preceding text before the noweb reference like initial comment characters), 因此其产生的结果如下:
cat $* | xargs ruby -ne 'require "prime" cat $* | xargs ruby -ne 'Prime.prime?(ARG[0])'
这需要shell中的here docs或单引号. 或者是Python中的三引号(This requires either here docs or single quotes in a shell, or triple quotes in Python):
cat $* | xargs ruby -ne ' '
14 变量
Org能够以变量的形式传递一个或多个值到你的代码块中. 下面演示一个静态地设置变量的例子:
#+BEGIN_SRC python :var interest=13 return 313 * (interest / 100.0) #+END_SRC #+RESULTS: : 40.69
当然你可以再一行或多行位置上同时定义多个变量,下面是一个例子
#+HEADER: :var a=42 d=56 :var f=23 #+HEADERS: :var b=79 e=79 #+BEGIN_SRC ruby :var c=3 g=2 [ a, b, c, d, e, f, g ] #+END_SRC #+RESULTS: | 42 | 79 | 3 | 56 | 79 | 23 | 2 |
但是像这样静态地设置变量的值有什么意义呢?
14.1 将代码块的结果作为值传递给另一个代码块
创建一个命名的代码块,如下所示:
#+NAME: twelve-primes #+BEGIN_SRC ruby require 'prime' Prime.first 12 #+END_SRC #+RESULTS: twelve-primes | 2 | 3 | 5 | 7 | 11 | 13 | 17 | 19 | 23 | 29 | 31 | 37 |
注意到 RESULTS:
部分的名字与代码块的名字一样. 我们可以将该计算结果作为数组变量传递给另一个代码块:
#+BEGIN_SRC python :var primes=twelve-primes return primes[-1] #+END_SRC #+RESULTS: : 37
这也许是第一次Ruby与Python能够合作完成任务.
14.2 表格形式的变量数据
再下面的例子中,我需要一个填充了数字的表格. 我会通过一小段lisp程序来生成这个表格,但是你也可以用自己喜欢的语言来生成:
#+NAME: cool-numbers #+BEGIN_SRC emacs-lisp (mapcar (lambda (i) (list i (random 10) (expt i 2) (random 100) (expt i 3) (random 1000))) (number-sequence 1 10)) #+END_SRC #+RESULTS: cool-numbers | 1 | 1 | 1 | 14 | 1 | 74 | | 2 | 7 | 4 | 25 | 8 | 823 | | 3 | 2 | 9 | 68 | 27 | 402 | | 4 | 4 | 16 | 17 | 64 | 229 | | 5 | 6 | 25 | 4 | 125 | 208 | | 6 | 7 | 36 | 67 | 216 | 203 | | 7 | 0 | 49 | 96 | 343 | 445 | | 8 | 0 | 64 | 58 | 512 | 908 | | 9 | 2 | 81 | 15 | 729 | 465 | | 10 | 0 | 100 | 61 | 1000 | 798 |
你无需拷贝这段源代码然后运行这段代码,你只需直接把这个充满数字的表格拷贝到你的文档就行了,像这样:
#+NAME: cool-numbers | 1 | 1 | 1 | 14 | 1 | 74 | | 2 | 7 | 4 | 25 | 8 | 823 | | 3 | 2 | 9 | 68 | 27 | 402 | | 4 | 4 | 16 | 17 | 64 | 229 | | 5 | 6 | 25 | 4 | 125 | 208 | | 6 | 7 | 36 | 67 | 216 | 203 | | 7 | 0 | 49 | 96 | 343 | 445 | | 8 | 0 | 64 | 58 | 512 | 908 | | 9 | 2 | 81 | 15 | 729 | 465 | | 10 | 0 | 100 | 61 | 1000 | 798 |
这并不会改变我们使用和处理这些数字的方式. 随便说一下,我经常创建数据表格,并使用表格中的数据作为测试函数时用的参数值,我会展示给你看我是如何操作的.
这个g名为 cool-numbers 的表格再代码块中会被替换成一个数组或数组的数组, 这里我们使用Python推导式来将该值分解为一个长长的数组. 然后对其中的每个数字加一
#+BEGIN_SRC python :var nums=cool-numbers :results list return [ cell + 1 for row in nums for cell in row ] #+END_SRC #+RESULTS: - 2 - 4 - 2 - 23 - 2 - 955 - 3 - 7 - 5 - 43 - 9 ...
14.3 表格分片
我们可以只传递表格中的某一行,方法是指定一个索引编号. 你可以试试下面这短Ruby代码块:
#+BEGIN_SRC ruby :var fifth=cool-numbers[4] fifth #+END_SRC #+RESULTS: | 5 | 9 | 25 | 93 | 125 | 524 |
用类似的方法,我们也能只传递表格中的某一列数据. 下面是一个例子,其中的都号表示任意行,后面的4则限制了只取第5列的数字:
#+NAME: cubes #+BEGIN_SRC elisp :var cubes=cool-numbers[,4] cubes #+END_SRC #+RESULTS: cubes | 1 | 8 | 27 | 64 | 125 | 216 | 343 | 512 | 729 | 1000 |
14.4 Reprocessing
名为cool-numbers的表格被名为cubes的代码块所使用,然后cubes代码快的结果值又可以传递给其他代码块:
#+NAME: roots_of_list #+BEGIN_SRC python :var lst=cubes :results list import math return [ math.sqrt(n) for n in lst ] #+END_SRC #+RESULTS: roots_of_list - 1.0 - 2.8284271247461903 - 5.196152422706632 - 8.0 - 11.180339887498949 - 14.696938456699069 - 18.520259177452136 - 22.627416997969522 - 27.0 - 31.622776601683793
15 保持代码块的整洁
代码块执行时可能与其他事物有关. 若一段代码需要执行,但这段代码并不需要告诉其他人,则这段代码可以放置再代码块的外部, 下面是一些例子.
15.1 设置环境
我经常使用nova命令查询OpenStack的实例. 该命令会从环境变量中读取数字证书, 这些环境变量一般设置在resource文件中. 一个典型的工作流程可能像下面这样:
$ source openrc $ nova list
这里我想执行的代码是 nova list
, 但是在执行该代码之前还需要执行source命令. 而该source命令我又不希望被导出. 则可以将这种不可见的代码放置在prologue 中
#+HEADER: :prologue "source openrc" #+BEGIN_SRC sh nova list #+END_SRC
:prologue
中的代码不会背导出, 我的同事也只能看到 nova list
命令及其执行结果
15.2 Using RVM
15.3 对结果进行修正
有时代码块的执行结果并不就是我们想导出到文档中的样子. While we could probably change the code, perhaps our point is the code as written.
例如, shell命令 ls -l
的结果会在最开始的地方添加一个指明有多少总数的行:
下例中的 ls
命令带了一个 time-style
参数:
#+BEGIN_SRC sh ls -lhG --time-style long-iso #+END_SRC #+RESULTS: | total | 5.8M | | | | | | | -rw-rw-r-- | 1 | howard | 6.0K | 2015-09-02 | 17:36 | emacs-init.org | | -rw-rw-r-- | 1 | howard | 22K | 2015-07-05 | 11:13 | eshell-fun.org | | -rw-rw-r-- | 1 | howard | 3.0K | 2015-07-05 | 11:13 | eshell.org | | -rw-rw-r-- | 1 | howard | 4.3K | 2015-09-02 | 12:52 | getting-started2.org | | -rw-rw-r-- | 1 | howard | 5.1K | 2015-03-30 | 18:08 | getting-started.org | ...
第一行搞乱了我们的表格. 我们可以使用 tail
命令来修正我们的代码:
#+BEGIN_SRC sh ls -lhG --time-style long-iso | tail -n +2 #+END_SRC
然而,在该例中,我想讲的时 ls
命令而不是 tail
命令. =tail=命令在这很突兀.
我们可以使用:post 参数来修正代码块的执行结果, 这样我们可以不修改代码块而获得想要的结果.
在本例中,要删除掉第一行,我们要创建一个代码块处理器来返回除了第一行之外的所有行. 我设定该代码块处理器的 :exports
参数为 none
因为我不希望它被导出.
请注意data变量:
#+NAME: skip_first #+BEGIN_SRC elisp :var data="" :exports none (cdr data) #+END_SRC
现在我们的代码块中可以只包含 ls -l
命令了,但是我们还需要讲结果传递给 skip_first
代码块进行处理.
我们为data变量赋值为 *this*
(表示当前代码块的输出结果). 现在我们的结果中只包含文件了:
#+BEGIN_SRC sh :post skip_first(data=*this*) ls -lhG --time-style long-iso #+END_SRC #+RESULTS: | -rw-rw-r-- | 1 | howard | 6.0K | 2015-09-02 | 17:36 | emacs-init.org | | -rw-rw-r-- | 1 | howard | 22K | 2015-07-05 | 11:13 | eshell-fun.org | | -rw-rw-r-- | 1 | howard | 3.0K | 2015-07-05 | 11:13 | eshell.org | | -rw-rw-r-- | 1 | howard | 4.3K | 2015-09-02 | 12:52 | getting-started2.org | | -rw-rw-r-- | 1 | howard | 5.1K | 2015-03-30 | 18:08 | getting-started.org | ...
当我们在后面讨论Tower of Babel时,就会发现 :post
参数真的很有用, 因为我们可以创建一系列的输出处理器,在其他文档中使用.
16 其他特性
以下是一些不好分类的参数和特性.
16.1 调用代码块
目前为止,我们是通过 :var
参数来为代码块设置值的, 我们还可以再调用代码块是给代码块赋值.
还记得我们之前创建过的那个名为roots_of_list的代码块吗? 该代码块接受一个名为lst的变量. 下面来演示一下如果使用一个不同的值来为该变量赋值:
#+CALL: roots_of_list( lst='(16 144 81 61) ) #+Results: | 4.0 | 12.0 | 9.0 | 7.810249675906654 |
我们还可以使用其他代码块的输出结果. 下面例子中就使用的时 cool-numbers 表格中的一列作为被传递的值.
#+CALL: roots_of_list( lst=cool-numbers[,2] ) #+RESULTS: | 1.0 | 2.0 | 3.0 | 4.0 | 5.0 | 6.0 | 7.0 | 8.0 | 9.0 | 10.0 |
注意: 你可以在中括号内为代码块设置其他头参数的值. 详情请参见 Evaluating code blocks.
那么调用代码块在导出时是怎样的呢? 这个得看了. 若代码块运算结果像下例一样返回单个值:
#+NAME: cube #+BEGIN_SRC elisp :var n=0 :exports none (* n n n) #+END_SRC
那么,在设定结果为table格式的情况下调用它,其结果与平常没什么不同:
#+CALL: cube[:results table](n=3)
但过在设定结果为list格式的情况下调用它,则导出的结果是嵌入 <pre>
块中.
16.2 嵌入运算结果
如果你想得快速得到一门语言片段的计算结果,你可以在大括号内嵌入这段代码. 例如,可以试试将下面内容输入你的org文件中,然后在行首按下 C-c C-c
看看结果:
src_ruby{ 5+6 } =11=
其结果,11,会添加在代码块后面. 当导出时,只有计算结果会被导出(源代码不会被导出).
我想这项功能应该常用于生成文档内容,像下面这样:
We will be bringing src_ruby{ 5+6 } children.
注意: 计算的结果会被嵌入到HTML的 <code>
标签中.
你也可以插入shell脚本的执行结果:
Why do I have src_sh{ ls /tmp | wc -l } files?
甚至可以插入Emacs Lisp函数的返回值:
src_elisp{ org-agenda-files }
也支持插入调用代码块的值. 例如,假设我们定义了一个名为roots_of_list的代码块,则可以这样:
call_roots_of_list( lst=cool-numbers[,2] )
| 1.0 | 2.0 | 3.0 | 4.0 | 5.0 | 6.0 | 7.0 | 8.0 | 9.0 | 10.0 |
警告: 当我再演示这项功能时,发现若你在 src_XYZ
上按下了 C-c C-c
会插入代码块的计算结果. 然后再导出时会内嵌两次结果. 一次是执行代码块时生成的结果,一次是内嵌的那个结果(这个问题现在已经被修复).
16.3 Library of Babel
Library of Babel
是一系列可以在任意org-mode文件总调用的代码块. 就好像时Ruby中的Gem源一样, 你需要指定哪些包含有命名代码块的文件是可访问的.
按照以下步骤进行操作:
- 新建一个org文件,并添加至少一个命名了的代码块
- 按下
C-c C-v i
- 选择你新建的这个org文件,表示将该文件加入babel集合中.
用一个你常用的代码块时试一试:
#+NAME: take #+BEGIN_SRC elisp :var data='() only=5 (require 'cl) (flet ((take (remaining lst) (if (> remaining 0) (cons (car lst) (take (1- remaining) (cdr lst))) '("...")))) (take only data)) #+END_SRC
你保持该新建的org文件,并将其作为babel addition加载后, 就可以将该代码片段用于 :post
参数了:
#+BEGIN_SRC python :post take(data=*this*, only=3) return [x * x for x in range(1, 20)] #+END_SRC #+RESULTS: | 1 | 4 | 9 | ... |
该功能在以下几种情况下很有用:
- 用于
:post
参数中处理结果 - 通过
#+CALL
语句将运算结果嵌入到行中 - 通过
call_XYZ()
语句将运算结果嵌入到行中
要想让这些文件永久性的添加到 babel library中,需要在你的Emacs初始化文件中对每个想被添加的org文件调用 org-babel-lob-ingest
函数.
17 专用语言
我发现有些语言被org-mode所支持是为了更好的编写文档的(I’ve found a few programming languages that really add to an org-mode way of writing documents).
17.1 SQL
通过SQL语句查询数据库,然后使用其他语言处理查询结果,这种能力十分有用. and if I felt I could have used them, would have made this workshop-tutorial less trivial (but also less accessible).
假设你已经安装了Sqlite, 并且通过 M-x load-library <RET> ob-sqlite
加载了必要的库:
你可以在Sqllite命令行中使用 .backup
命令导出一个数据库,然后在 :db
参数中指定该数据库. 就像下面这样:
#+BEGIN_SRC sqlite :db dolphins.db SELECT gender,COUNT(gender) FROM oasis GROUP BY gender; #+END_SRC #+RESULTS: | f | 55 | | m | 89 |
其结果时一个简单的表格:
f | 55 |
m | 89 |
若你觉得这样很有用,请参见我的另一篇文章Literate Database essay.
17.2 Graphviz
若你安装了Graphviz , 则我们可以直接再文档中创建图标:
#+BEGIN_SRC dot :file some-illustration.png digraph { a -> b; b -> c: c -> a; } #+END_SRC
对这种应用,我一般会设置 :exports results
以便在导出时不要导出产生图片的代码.
警告: 若你希望执行代码块并生成图片,则需要设置代码块的语言类型为 dot
, 但若你想编辑该代码块,则又需要把语言类型设置为 graphviz-dot
.
17.3 PlantUML
若你安装了PlantUML ,你可以实现类似下面的功能:
#+BEGIN_SRC plantuml :file sequence.png :exports results @startuml sequence-diagram.png Alice -> Bob: synchronous call Alice ->> Bob: asynchronous call @enduml #+END_SRC
哈哈,你可以为你的源代码插入描述性插图了.
17.4 Calc
我们已经接触过了令人印象深刻的 Emacs Calculator 及其常用的数学符号.
#+BEGIN_SRC calc :var a=2 b=9 c=64 x=5 ((a+b)^3 + sqrt(c)) / (2x+1) #+END_SRC #+RESULTS: : 121.727272727
若我们没有对某些变量赋值的话,则会简化这个方程式:
#+BEGIN_SRC calc :var a=4 b=2 ((a+b)^3 + sqrt(c)) / (2x+1) #+END_SRC #+RESULTS: : (sqrt(c) + 216) / (2 x + 1)
当然,你需要通过 M-x load-library <RET> ob-calc
加载必要的库.
注意每个calc代码块的每一行都会进入Calc mode buffer的栈中(使用 C-x * *
可以切换到Calc中查看).
18 总结
下面是针对头参数 的说明清单,按你的目标或需求分类:
代码执行?
dir :: 指定代码执行的工作目录,支持Tramp session :: 在不同代码块之间共享解释器 file :: 将代码块的计算结果写入文件中 eval :: 限制只指定特定的代码块 cache :: 缓存计算结果,防止对代码块重复计算 var :: 为代码块赋值(ignore with no-expand)
导出?
文学编程?
tangle :: 源代码以何种方式写入到脚本文件中 … 这时文学编程的核心. mkdirp :: 再tangle源码文件时是否创建父目录 shebang :: 写入到源码文件中第一行的内容 noweb :: 是否扩展noweb引用 noweb-ref :: noweb引用的内容
Special Input?
prologue :: 参数决定了在执行代码块中的代码之前,作什么初始化操作 epilogue :: 参数决定了在执行代码块中的代码之后,作什么清理操作
Special Output and Formatting?
padline post :: 参数用于决定了得到代码块的result后,该result要传递到哪个代码块中作进一步的处理 wrap 其他. hlines, colnames, rownames