编译 OCaml 程序

使用 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 这样的集成开发环境中工作,可能会感到陌生。不用担心,很快你就会驾轻就熟。

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

$ rm hello.byte hello.cmi hello.cmo

main 是什么?

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

Dune

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

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

您项目中的每个目录都可以包含一个名为 dune 的文件。该文件向 Dune 描述了您希望该目录(及其子目录)中的代码如何编译。Dune 文件使用一种源自 LISP 的函数式编程语法,称为 s 表达式,其中括号用于显示形成树状结构的嵌套数据,类似于 HTML 标记的方式。Dune 文件的语法在 Dune 手册中有详细说明。

手动创建Dune Project

这里是如何使用 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 不仅在 Windows 平台上使用 .exe 扩展。这导致 Dune 构建本机可执行文件,而不是字节码可执行文件。

Dune 将在 _build 中创建一个目录,并在其中编译我们的程序。这是构建系统相对于直接运行编译器的一个好处:不会在源目录中产生大量生成的文件,而是会干净地创建在一个单独的目录中。在 _build 中,Dune 会创建许多文件。我们的可执行文件被埋藏在几个层级之下:

$ _build/default/hello.exe
Hello world!

但 dune 提供了一个快捷方式,避免记住和输入所有这些内容。为了一步构建和执行程序,我们可以简单地运行:

$ dune exec ./hello.exe
Hello world!

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

$ dune clean

这将删除 _build 目录,仅保留您的源代码。

当 Dune 编译您的程序时,它会在 _build/default 中缓存您的源文件的副本。如果您不小心犯了一个错误导致源文件丢失,您可能可以从 _build 中恢复它。当然,使用像 git 这样的源代码控制也是明智的。

请勿编辑 _build 目录中的任何文件。如果您收到关于尝试保存只读文件的错误消息,您可能正在尝试编辑 _build 目录中的文件。

自动创建Dune Project

在终端中,切换到您想要存储工作的目录,例如,“~/work”。为您的项目选择一个名称,比如“calculator”。运行:

$ dune init project calculator
$ cd calculator
$ code .

您现在应该已经打开了 VS Code,并看到了 Dune 自动生成的项目文件。

calculator 目录中的终端运行:

$ dune exec bin/main.exe

它将打印 Hello, World!

如果您使用 ocamlformat 自动格式化源代码,请注意 Dune 不会自动向您的项目添加一个 .ocamlformat 文件。您可能希望在项目的toplevel目录(即根目录)中添加一个文件。这个目录中包含名为 dune-project 的文件。

连续运行Dune

当你运行 dune build 时,它会对你的项目进行一次编译。您可能希望每次保存项目中的文件时自动编译您的代码。要实现这一点,请运行以下命令:

$ dune build --watch

Dune 将会回应说它正在等待文件系统的更改。这意味着 Dune 现在正在持续运行,并且每次您在 VS Code 中保存文件时都会重新构建您的项目。要停止 Dune,请按 Control+C。