文学编程简介
欢迎来到文学编程的世界! 本文会简单介绍一下如何使用org-mode进行文学编程
本文的内容来源于PDX Emacs Hackers举办的研讨会, 但是考虑到很少人能够现场参加该研讨会,因此我决定把它写成一片指南. 我假设你已经有了一定的Emacs基础 并且对org-mode也比较熟悉 ,至少知道如何导出文档.
众所周知,Org的功能太多了, 它具有在一个文档中编写,执行和连接代码块的能力,这种能力十分强大, 要把这些功能都写成文档是件让人觉的恐怖的任务. 我希望这份指南能开个好头, 若你发现我遗漏了什么,请告之.
警告 :下面的例子都写得很蹩脚.
必要条件
本文基于org-mode v8.2.10(或更新). 由于Emacs已经内置了org, 你需要通过 C-h v 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
当然,你是个聪明人,把这些例子修改成你所使用的语言应该是很容易的.
背景知识
在我们开始学习使用org-mode 进行文学编程之前,我先简单介绍一下文学编程吧.
为什么使用文学编程?
文学编程最初是由Donald Knuth于20世纪80年代提出来的,旨在加强团队成员中的交流. 如Donald Knuth 所说过的
让我们改变针对程序结构的传统看法吧. 不要把我们的任务看成是向计算机描述怎么去作,而是向其他人解释我们想让计算机做什么.
文学编程希望能做到写得程序适于人阅读,文档描述能遵循问题逻辑的顺序,同时不受制于编程语言的缺陷. 下面是一份文学编程的例子,对人来说,这是份可阅读的文档,同时它还是源代码文件:
其背后的思想是反转注释与代码之间的关系,以前是代码中点缀着些注释,现在则是在文档中内嵌一些代码.
由于从文学编程文档中抽取出来的源代码与直接编写的源代码无异,因此这一做法并不需要现有的编程环境做出什么改变.(although it is used, to various degrees, in niche circles).
为什么使用Org?
Knuth最开始认为文学编程只需要使用最简单功能的编辑器就行, 因为他只是写了noweb程序来创建文学编程文档并从中抽取源代码.
依我的观点,文学编程必须依赖编辑器的帮助,iPython’s notebook就是个例子. 但是我认为文档内容应该以可读文本的方式存储,而不是存储为JSON格式的文件. 就像 Carsten Dominik 说过的:
在第三个千禧年,使用文本文件是否还有意义? 文本文件是唯一真正可以说是可移植的文件格式. 用文本文件保存的数据永远不会丢失.
因此若你想进行文学编程,org-mode是个很好的选择,更何况我们本来就已经在用org-mode作许多事情了, 对吧?
使用Org有什么优势?
使用org-mode进行文学编程有如下几点优势:
代码更易理解
方便团队讨论问题
针对复杂状况更容易思考的清晰一点
学习新库和API时,可以一边实验一边记笔记
可以同时使用多种语言,每种语言只完成自己擅长的任务(例如可以使用通用语言来查询,维护数据库)
org具有组织化的能力,它提供了类似Agenda和task这类的工具
警告
1980年的软件世界与今天的情况相比,已经发生了翻天覆地的改变. 现在的工程师们连接的更紧密了,且常常以团队的形式进行工作. 虽然如此,但各个工程师所使用的工具还是各不相同的. 即使你的团队并不使用Emacs, 你依然会觉得org-mode的方法很有用处.
首先,org-mode很适合用于设计复杂算法,当你有什么灵感的时候,可以立即写下最终的代码(你所写下的记录会成为代码中的注释), 当你毫无进展时,你可以直接把记录发给团队中的其他人敬候回应.
其次,org-mode文件可以被认为是一个repl环境,因为其中的每个代码块都能被分别执行,并且代码块的执行结果还能传递给其他的代码块… but I’m getting a head of myself.
导出文档
使用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}
基础知识
这篇指南来源于一场研讨会, 那么让我们运行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,然后将勘误发给我.
快捷方式
若你使用比较新版本的Emacs(新于v22)或这时比较新版本的org-mode(你可能直接从ELPA中安装),那么你可以使用 Org Templates,它提供了如下功能:
输入 可以快速创建一个代码块
如果没有创建代码块,或许你可以试试 yasnippets
从代码块的 BEGIN 到 END 部分的任意位置,你都可以通过按下 C-c C-c 来执行代码
使用 C-c M-f 跳转到文件中下一个代码块位置,使用 C-c M-b 跳转到上一个代码块位置
推荐配置
要想语法高亮代码块中的代码,可以将下面代码放到 .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”
支持的语言
Org-mode支持许多的编程语言, 但还是有些语言是不支持的(当然,你可以为Org-mode添加新语言的支持,这并不难).
我会展示一些比较流行的语言案例,从中你也可以看出不同语言之间的些许差别.
Ruby案例
让我们将上面lisp的例子修改为Ruby:
#+BEGIN_SRC ruby
Dir.entries('.')
#+END_SRC
你再按下 C-c C-c 发现没有反映, 这是因为你需要预先加载好Ruby语言的支持: M-x load-library ob-ruby
你也可以将下面配置放到 .emacs 中:
(require 'ob-ruby)
Python案例
需要注意的是,Ruby和Lisp类似,会自动将最后表达式的值作为代码块的返回值. 然而,Python语言,需要明确的 return 语句:
#+BEGIN_SRC python
from os import listdir
return listdir(".")
#+END_SRC
shell案例
大多数的语言使用返回值作为结果,然而shell语言使用输出到标准输入的内容作为结果:
#+BEGIN_SRC sh
ls -1
#+END_SRC
若按下 C-c C-c 没反映,你需要执行: M-x load-library ob-sh
其他语言
若你像我一样,是个多语言开发者,你可以添加类似下面的配置到 .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)))
代码块设置
通过设置不同的代码块参数(也称为”头参数”)可以产生各种有趣的结果. 代码块可以有0个或多个头参数.
首先我们先讨论一下几种设置头参数的方式,然后在讨论这些参数的意义. 让我先以一个头参数为例.
例子: 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
设置头参数的位置
不同位置设置参数所影响的作用域也不一样. 下面几个位置都可以用于设置头参数,以作用域从特殊到一般的顺序列出.
头参数嵌入到代码块中,或者在代码块上面
设置某一标题下面所有代码块的默认头参数
设置整个文档中所有代码块的默认头参数
设置所有文档中所有代码块的默认头参数
对我来说,为所有文档设置某个参数没有什么意义. 但若你需要,可以设置下列参数:
org-babel-default-header-args
org-babel-default-header-args:
注意:你可以在代码块被调用时设置头参数. 我们还会在下面接着提到.
太多参数了?
以将参数嵌入代码块的方式设置少量头参数还行,但是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
设置某一标题下面所有代码块的默认头参数
若某一标题下面的代码块使用相同的头参数,则可以将头参数的设置放入标题下的属性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
指定特定语言的默认头参数
你可以指定特定语言的头参数. 步骤如下:
在标题上按下 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 的方式设置.
为文档内的所有代码块设置默认参数
通过将 #+PROPERTY: 设置项放到文档中,可以为该文档中的所有代码块设置默认参数
#+PROPERTY: dir ~/Work
注意: 这些参数并没有在前面带上冒号
要为特定语言的代码块设置,需要进行如下操作:
#+PROPERTY: header-args:sh :tangle no
注意: 你需要在设置项上按下 C-c C-c,否则该配置项不生效.
头参数的类型
基础的东西已经讲完了,剩下的内容就是讲讲各参数的意义了. 我按这些参数的用处将之分为以下几类:
执行类参数以 dir 为代表,这类参数影响代码块如何执行
导出类参数该类参数影响了当把org文件导出成HTML(或其他格式)时,代码块以及代码块的执行结果如何展示
文学编程类参数将代码块连接起来,可能会改变实际的源代码
变脸类参数通过不同方式设置代码块中的变脸
杂类 输入/输出其他参数
执行类参数
下列参数会影响到代码的执行情况.
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.
影响输出格式的结果
代码的执行结果会插入到文档中,它可能以以下几种格式出入.
table若结果为单个数组,则插入一行,若结果为数组的数组,则插入一个表格
list按照普通org-mode列表的格式插入一个无序列表
verbatim原样输出
file将结果写入到文件中
html认为执行的结果是HTML代码,导出时原样导出
code认为执行的结果还是原语言的代码
silent只在mini-buffer中显示执行的结果
之所以有这么多的变种,是因为执行结果本身也可以被导出(可以以HTML,或Email等格式导出), 同时这些执行结果还能作为其他代码的输入变量. 这是文学编程中最有意思的部分了,我们后面还会再提到.
输出成列表
注意到之前的输出是以表格的格式插入的,这次我们以列表的格式来插入:
#+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为例,你可以试着使用你喜欢的语言来列出目录中的文件列表.
原样输出
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
...
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 参数会影响到每个代码块,而不管该代码块是哪种编程语言. 这可能不是你想要的.
将结果写入到文件中
创建并执行下面这个代码块:
#+BEGIN_SRC ruby :results output :file primes.txt
require 'prime'
Prime.each(5000) do |prime|
p prime
end
#+END_SRC
你会发现执行的结果是插入了一个指向文件的链接. 点击该连接会在buffer中加载该文件.
注意: :file 参数需要与 :results output 共用,因为它不知道以哪种格式输出内部值
导出
按下 C-c C-e h o 会导出成HTML文件,并用浏览器打开.
:exports 头参数指明了哪些内容会被导出:
code只导出代码
results只导出结果
both同时导出代码与结果
none跳过该代码块,什么都不导出
注意: :exports 一般被设置成文件属性.
若导出成HTML时希望保持语法高亮,只需要加载htmlize 库即可:
(require 'htmlize)
该功能只在最近版本的org-mode中引入. 若没有新版本的org-mode,可以从ELPA上安装.
文学编程
编程时,有时需要从org-mode文件中抽取出源代码来创建源代码文件,这个过程成为tangling
Tangling
:tangle 参数会将所有同类语言的代码快中内容都写入指定的源码文件中.
#+BEGIN_SRC ruby :tangle double-space.rb
while s = gets
print s ; puts
end
#+END_SRC
拥有相同=:tangle= 值的代码块内容会按顺序写入到同一个文件中. 若 :tangle 参数值为 yes 则写入的文件其名称与原org文件名称一样(文件后缀不一样而已).
也可以使用 PROPERTY 来为整个文件的所有代码快指定一个值:
#+PROPERTY: tangle ~/.emacs.d/elisp/bling-mode.el
注释
若我需要与他人分享代码,我可以讲文档内容转换成注释:
Precede each line in the text from standard in (or file) with the
current line number.
See [[http://benoithamelin.tumblr.com/ruby1line][one liners]].
#+BEGIN_SRC ruby
while s = gets
puts "#{$<.file.lineno>
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>
end
:comments 参数指明了是否将以及如何将文档内容作为注释插入tangle出的代码. 其值为org表示将文档内容作为org code来格式化再作为注释插入.
注意: 只有代码快上面的内容才会作为注释插入.
若 :comments 的值为 link, 则插入的注释为链接到原org文件的连接.
由于基本上我只看文学编程的原文档内容(例如我的.emacs文件)–我几乎不会去看tangle出来的源代码,因此我觉得这个功能作用不大.
默认值为 no,意味着不插入任何注释.
Shebang
当创建脚本时,常常会指定脚本运行的解释器. 该解释器可以通过:shebang 参数来指定(可以以代码块的header或文档属性的方式来指定)
Precede each line in the text from standard in (or file) with the
current line number.
See [[http://benoithamelin.tumblr.com/ruby1line][one liners]].
#+BEGIN_SRC ruby :shebang "#!/usr/local/bin/ruby"
while s = gets
puts "#{$<.file.lineno>
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>
end
Noweb
若你为某个代码快命了名,则其他代码块就可以包含该代码快了.… 如标题所示,使用:noweb 参数.[fn: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 '<>'
#+END_SRC
会创建一个名为 last-col.sh 的源代码文件,其内容为:
ruby -ane 'puts $F.last'
这个功能有什么用呢?
Donald Knuth当时所使用的老式语言,要求所有的变量和函数需要先定义后使用. 也就是说你需要从下到上的写代码.
然而有些代码适合以从上倒下的方式进行解释. 对于某些算法来说,比较适合使用web和tangle.
关于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 '<>'
#+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 '
'
变量
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 |
但是像这样静态地设置变量的值有什么意义呢?
将代码块的结果作为值传递给另一个代码块
创建一个命名的代码块,如下所示:
#+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能够合作完成任务.
表格形式的变量数据
再下面的例子中,我需要一个填充了数字的表格. 我会通过一小段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
...
表格分片
我们可以只传递表格中的某一行,方法是指定一个索引编号. 你可以试试下面这短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 |
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
保持代码块的整洁
代码块执行时可能与其他事物有关. 若一段代码需要执行,但这段代码并不需要告诉其他人,则这段代码可以放置再代码块的外部, 下面是一些例子.
设置环境
我经常使用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 命令及其执行结果
Using RVM
类似Python和Ruby这类语言,经常会需要指定解释器. 你可以再 :prologue 的命令后加上两个反斜杠来表示代码块执行时的前缀(只对shell调用有效):
#+BEGIN_SRC sh :prologue "~/.rvm/bin/rvm 1.9.3@msw exec \\"
gem list
#+END_SRC
注意: Ruby或Python代码的执行时基于rvm, pyvenv 或 ELPY 的.
对结果进行修正
有时代码块的执行结果并不就是我们想导出到文档中的样子. 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 参数真的很有用, 因为我们可以创建一系列的输出处理器,在其他文档中使用.
其他特性
以下是一些不好分类的参数和特性.
调用代码块
目前为止,我们是通过 :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格式的情况下调用它,则导出的结果是嵌入
块中.
嵌入运算结果
如果你想得快速得到一门语言片段的计算结果,你可以在大括号内嵌入这段代码. 例如,可以试试将下面内容输入你的org文件中,然后在行首按下 C-c C-c 看看结果:
src_ruby{ 5+6 } =11=
其结果,11,会添加在代码块后面. 当导出时,只有计算结果会被导出(源代码不会被导出).
我想这项功能应该常用于生成文档内容,像下面这样:
We will be bringing src_ruby{ 5+6 } children.
注意: 计算的结果会被嵌入到HTML的 标签中.
你也可以插入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 会插入代码块的计算结果. 然后再导出时会内嵌两次结果. 一次是执行代码块时生成的结果,一次是内嵌的那个结果(这个问题现在已经被修复).
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 函数.
专用语言
我发现有些语言被org-mode所支持是为了更好的编写文档的(I’ve found a few programming languages that really add to an org-mode way of writing documents).
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 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 |
其结果时一个简单的表格:
f55
m89
Graphviz
若你安装了Graphviz , 则我们可以直接再文档中创建图标:
#+BEGIN_SRC dot :file some-illustration.png
digraph {
a -> b;
b -> c:
c -> a;
}
#+END_SRC
对这种应用,我一般会设置 :exports results 以便在导出时不要导出产生图片的代码.
警告: 若你希望执行代码块并生成图片,则需要设置代码块的语言类型为 dot, 但若你想编辑该代码块,则又需要把语言类型设置为 graphviz-dot.
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
哈哈,你可以为你的源代码插入描述性插图了.
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 ob-calc 加载必要的库.
注意每个calc代码块的每一行都会进入Calc mode buffer的栈中(使用 C-x * * 可以切换到Calc中查看).
总结
下面是针对头参数 的说明清单,按你的目标或需求分类:
代码执行?
dir :: 指定代码执行的工作目录,支持Tramp
session :: 在不同代码块之间共享解释器
file :: 将代码块的计算结果写入文件中
eval :: 限制只指定特定的代码块
cache :: 缓存计算结果,防止对代码块重复计算
var :: 为代码块赋值(ignore with no-expand)
导出?
results :: 结果时要输出的内容还是返回值,格式时怎样的
exports :: 代码与结果该如何导出
文学编程?
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
Footnotes
[fn:1] 参见链接
The term noweb is from Knuth’s original program for tangling out source code. Since each code block could be
re-inserted into other blocks, he saw this as creating a tangled web of connections.