文学编程简介

source: https://github.com/lujun9972/emacs-document/blob/master/org-mode/%E6%96%87%E5%AD%A6%E7%BC%96%E7%A8%8B%E7%AE%80%E4%BB%8B.org


文学编程简介

欢迎来到文学编程的世界! 本文会简单介绍一下如何使用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 所说过的

让我们改变针对程序结构的传统看法吧. 不要把我们的任务看成是向计算机描述怎么去作,而是向其他人解释我们想让计算机做什么.

文学编程希望能做到写得程序适于人阅读,文档描述能遵循问题逻辑的顺序,同时不受制于编程语言的缺陷. 下面是一份文学编程的例子,对人来说,这是份可阅读的文档,同时它还是源代码文件:

文学编程简介_第1张图片

其背后的思想是反转注释与代码之间的关系,以前是代码中点缀着些注释,现在则是在文档中内嵌一些代码.

由于从文学编程文档中抽取出来的源代码与直接编写的源代码无异,因此这一做法并不需要现有的编程环境做出什么改变.(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中. 你可以试试按照下面步骤操作:

  1. 在org文件中创建一个标题
  2. 输入 C-c C-x p
  3. 输入属性名称:dir
  4. 输入属性值: /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

输入 C-c C-v t 生成 double-space.rb

拥有相同=: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}: #{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,意味着不插入任何注释.

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}: #{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

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 |

其结果时一个简单的表格:

f 55
m 89

若你觉得这样很有用,请参见我的另一篇文章Literate Database essay.

Graphviz

若你安装了Graphviz , 则我们可以直接再文档中创建图标:

#+BEGIN_SRC dot :file some-illustration.png
  digraph {
    a -> b;
    b -> c:
    c -> a;
  }
#+END_SRC

文学编程简介_第2张图片

对这种应用,我一般会设置 :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

文学编程简介_第3张图片

哈哈,你可以为你的源代码插入描述性插图了.

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.


你可能感兴趣的:(转载)