2.2 编译 OCaml 程序

531 阅读3分钟

使用OCaml作为一种交互式计算器可能会很有趣,但我们无法通过这种方式编写大型程序。相反,我们需要将代码存储在文件中并编译它们。

将代码存储在文件中

打开一个终端,创建一个新目录,然后在该目录中打开 VS Code。例如,你可以使用以下命令:

$ mkdir hello-world
$ cd hello-world

不要使用 Unix 用户主目录的根目录作为存储文件的位置。我们即将使用的构建系统 dune 可能无法在主目录的根目录下正常工作。相反,你需要使用主目录下的一个子目录。

使用 VS Code 创建一个名为 hello.ml 的新文件。在文件中输入如下代码:

let _ = print_endline "Hello world!"

在这行代码的最后没有双分号 ;; 。双分号用于 toplevel 交互式会话,这样 toplevel 就知道你已经完成了一段代码的输入。通常没有理由把它写在 .ml 文件中。

上面的 let _ = 意味着我们不用给 = 右侧的代码命名(因此有“空白”或下划线)。

保存文件并返回到命令行。编译代码:

$ ocamlc -o hello.byte hello.ml

编译器名为 ocamlc-o hello.byte 选项表示将输出可执行文件命名为 hello.byte。可执行文件包含已编译的OCaml字节码。此外,还生成了另外两个文件hello.cmihello.cmo。我们现在不需要关注这些文件。运行可执行文件:

$ ./hello.byte

它应该打印 Hello world! 然后结束。

现在,将打印的字符串更改为你所选择的内容。保存文件,重新编译,然后重新运行。尝试让代码打印多行。

如果习惯于在 Eclipse 等 IDE 中工作,那么编辑器和命令行之间来回编辑—编译—运行的循环可能会让你感到陌生。不过别担心; 你很快就会变得自然。

现在让我们清理所有生成的文件:

$ rm hello.byte hello.cmi hello.cmo

主函数是什么?

与 C 或 Java 不同,OCaml 程序不需要调用名为 main 的特殊函数来启动程序。通常的习惯做法是让文件中的最后一个定义作为主函数,它启动将要进行的任何计算。

Dune

在较大的项目中,我们不想手动运行编译器或手动清理代码。相反,我们希望使用一个构建系统来自动查找和链接库。OCaml 有一个名为 ocamlbuild 的遗留构建系统,以及一个名为 Dune 的更新的构建系统。类似的系统包括在 Unix 世界中长期用于 C 和其他语言的 make;以及与 Java 一起使用的 Gradle、Maven 和 Ant。

Dune 项目是一个包含你想要编译的 OCaml 代码的目录(及其子目录)。项目的根目录是其层次结构中的最高目录。项目可能依赖于提供已经编译的额外代码的外部包。通常,软件包是用 OCaml 包管理器 OPAM 安装的。

项目中的每个目录都可以包含一个名为 dune 的文件。该文件向 Dune 描述了你希望如何编译该目录(以及子目录)中的代码。Dune 文件使用一种源自 LISP 的函数式编程语法,称为 s-表达式,其中括号用于显示形成树的嵌套数据,非常像 HTML 标签所做的。Dune 文件的语法记录在 Dune 手册中。

以下是如何使用 Dune 的一个小例子。在与 hello.ml 相同的目录下,创建一个名为 dune 的文件,并放入以下内容:

(executable
 (name hello))

它声明了一个主文件为 hello.ml 的可执行程序(能够直接执行的程序)。

另外创建一个名为 dune-project 的文件,并放入以下内容:

(lang dune 3.4)

这告诉 Dune 这个项目使用的是 Dune 3.4 版本,在本书发布时这是最新版本。在你想要用 Dune 编译的每个源码树的根目录中都需要这个项目文件。一般来说,在源码树的每个子目录中都有一个 dune 文件,但只有一个 dune-project 文件在根目录中。

然后在终端运行这个命令:

$ dune build hello.exe

请注意,Dune 在所有平台上都使用 .exe 扩展名,而不仅仅是在Windows上。这将使得 Dune 构建原生可执行文件,而不是字节码可执行文件。

Dune 将创建一个目录 _build,并在其中编译我们的程序。这是构建系统相对于直接运行编译器的一个好处:不会让一堆生成的文件污染源码目录,而是在一个单独的目录中干净地创建它们。在 _build 中有许多由 Dune 创建的文件。我们的可执行文件被隐藏了两个目录层次(在 _build 目录的 default 子目录下):

$ _build/default/hello.exe
Hello world!

但 Dune 提供了一种快捷方式,让你必须记住并输入所有这些内容。要一步构建和执行程序,我们可以简单地运行:

$ dune exec ./hello.exe
Hello world!

最后,清理所有编译后的代码,我们只需要运行:

$ dune clean

这会删除 _build 目录,只留下源代码。

当 Dune 编译程序时,它会在 _build/default 中缓存一个源文件的副本。如果你因为意外犯错导致源文件丢失,你可以从 _build 内部恢复它。当然,使用 git 这样的版本控制也是明智之举。

注:本书是康奈尔大学 CS 3110 数据结构和函数式编程的教材。原书为英文版,在学习的过程中,根据自己的理解,翻译了一些,做一个记录,版权归原作者所有,如有侵权,请联系我删除。