如何在 Emacs 进行文学编程

如何在 Emacs 进行文学编程

根据官方文档,org mode 原来是通过 org-babel 这一插件来实现文学编程的。自 7.0 版本以来, org mode 就已经内嵌了 Babel,

Emacs 27 内置的 org mode 已经是 8.0 以上了,所以对于这篇文档的读者(Emacs 新手)来说,相信你们的 org mode 都是 7.0 以上了。

这里我们不讨论 7.0 以下的情况。

在 org mode 中运行代码非常简单,你只需要有类似下面的代码块(source block),想要运行代码块时,将光标移动到代码块中,同时按下 C-c C-c 即可。

#+name: a block of code

#+caption: a block of code

#+begin_src python

print("python")

#+end_src

在本文中,我主要使用 python 代码,当然 org mode 支持的编程语言种类非常多,读者可以通过查看文档来看你的语言是否在支持列表上。

有一定水平的读者也可以自己添加语言支持,但这不在本文讨论范围之内。

虽然 org mode 支持的编程语言种类非常多,但是在默认设置下,我们需要的语言不一定被加载了。

因此,读者可以通过运行类似下面这段代码来加载你所需要的语言,或者你可以将这段代码放在 init.el 之中,这样它可以自动加载。

在这里,我使用 C-c C-c 来运行下面这段代码。

(setq org-babel-load-languages

'((js . t)

(java . t)

(python . t)

(sqlite . t)

(emacs-lisp . t)

(shell . t)

(ditaa . t)))((js . t) (java . t) (python . t) (sqlite . t) (emacs-lisp . t) (shell . t) (ditaa . t))

我们可以注意到上面出现了 ((js . t) (java . t) (python . t) (sqlite . t) (emacs-lisp . t) (shell . t) (ditaa . t)) 这一串字符,

有一定 elisp 经验的读者可以知道,这正是前面 (setq org-babel-load-languages ...) 这段代码的返回值。

在实际使用中,运行上面的代码所得到的结果会出现在如下的 RESULTS block 中,我们可以通过设置代码块的 header 来控制结果的输出样式。

#+RESULTS:

((js . t) (java . t) (python . t) (sqlite . t) (emacs-lisp . t) (shell . t) (ditaa . t))

这里我们一定要注意

在默认情况下,RESULTS 显示的是代码块的返回值,而非打印值,这一点如果不注意的话会浪费很多时间。

在实战例子中我们还会提到这个重点。

下面我们用五个例子来介绍 org mode 文学编程中最基本的操作。

实战一计算 1+1 并显示结果。

return 1+12

注意,如果你在阅读这篇教程的 HTML 版本,上面的代码块和结果在 org mode 中是这样的。

#+caption: 1+1

#+name: 1+1

#+begin_src python

return 1+1

#+end_src

#+RESULTS: 1+1

: 2

我们可以看到在 RESULTS 中显示了 1+1

的结果为 2,

并且注意到这里使用了 return

,

而非 print

实战二显示打印值,而非返回值。

print(1+1)2

注意,如果你在阅读这篇教程的 HTML 版本,上面的代码块和结果在 org mode 中是这样的。

#+caption: print value

#+name: print value

#+begin_src python :results output

print(1+1)

#+end_src

#+RESULTS: print value

: 2

要显示打印值,我们只需要在代码块的 header 中加上 :results output 选项即可。

header 的选项非常多,我也只使用过其中一些,具体的细节请参考文档。

实战三给代码块取名。

从 实战一

和 实战二

中,我已经给两个代码块都命名了。

命名的好处是,代码块的结果会出现在与之有相同名称的 RESULTS 里。

如果我们不给代码块命名或是两个代码块名称重复的话,那们所有的结果都会出现在一个 RESULTS 里,

一般情况下这是我们想要避免的。

此外,代码块的名字还会在导出到其他格式时被保留。

最后,命名了的代码块还可以和 noweb 一起使用(见实战四)。

我曾经的一个痛点是,我想要给每个代码块都命名,这样它们的结果不会相互覆盖,

但是有时想要试验一些想法时,某个代码块叫什么名字并不是最重要的,

于是我写了下面这段 yasnippet 。

# -*- mode: snippet -*-

# name: python src

# key: py

# --

#+caption: ${1:`(insert-random-uuid)`}

#+name: $1

#+begin_src python :results output

$0

#+end_src

#+RESULTS:

这段代码能帮我生成一个代码块和它对应的结果,然后用一个随机的 uuid 来命名这两个块。

如果我需要的话,可以直接重命名这个代码块,不需要的话就接受默认值即可。

#+caption: C3395424-A7F4-4228-A373-25F349858A73

#+name: C3395424-A7F4-4228-A373-25F349858A73

#+begin_src python :results output

#+end_src

#+RESULTS: C3395424-A7F4-4228-A373-25F349858A73

读者可以在附录中找到随机函数

insert-random-uuid 的定义。

实战四利用 noweb 导入已命名的代码块。

假设我有一个函数 add 。

def add(x, y):

return x+y下面我有两个独立的代码块都想要使用上面的 add

函数。

我可以通过在代码块的 header 设置 :noweb yes 来导入它。

<>

print(add(1, 1))2

<>

print(add(2, 2))4

注意,如果你在阅读这篇教程的 HTML 版本,上面的第二个代码块和结果在 org mode 中是这样的。

#+caption: second block to use add function

#+name: second block to use add function

#+begin_src python :results output :noweb yes

<>

print(add(2, 2))

#+end_src

#+RESULTS: second block to use add function

: 4

实战五使用其他代码块的输出作为一个代码块的输入。

(我很少使用这个功能,所以可能会有错漏)

print("yaoni")yaoni

我们可以使用 :var 来给当前的代码块提供一个变量。

print(f"Hello {name}")Hello yaoni

注意,如果你在阅读这篇教程的 HTML 版本,上面的代码块和结果在 org mode 中是这样的。

#+caption: my-name

#+name: my-name

#+begin_src python :results output

print("yaoni")

#+end_src

#+RESULTS: my-name

: yaoni

#+caption: say hello to me

#+name: say hello to me

#+begin_src python :results output :var name=my-name

print(f"Hello {name}")

#+end_src

#+RESULTS: say hello to me

: Hello yaoni

:

相关推荐