2.1 OCaml 顶层( Toplevel)环境

184 阅读5分钟

toplevel 就像 OCaml 的计算器或命令行界面。它类似于 Java 中的JShell或交互式 Python 解释器。toplevel 可以方便地尝试小段代码,而无需启动 OCaml 编译器。但不要太依赖它,因为创建、编译和测试大型程序将需要更强大的工具。其他一些语言会将 toplevel 称为REPL,它代表 read-eval-print-loop:它读取程序员输入,执行,打印结果,然后重复。

在终端窗口中,键入utop 以启动 toplevel 。按 Control-D 退出 toplevel 。你也可以输入#quit;;并按回车键。注意,这里 “#” 不是提示符的 # 。

类型和值

你可以在OCaml toplevel 中输入表达式。使用双分号 ;; 结束表达式,并按回车键。然后OCaml将计算表达式,告诉你结果值和值的类型。例如:

# 42;;
- : int = 42

让我们来仔细研究 utop 响应,从右到左阅读:

  • `42` 是值。
    
  • int 是值的类型。

  • 该值没有命名,因此使用符号 -

这种 utop 交互是本书“硬编码”的一部分。我们必须输入所有字符:#- 等。但是,用于编写本书的基础设施实际上使我们能够在书被翻译成HTML或PDF时由OCaml执行我们编写的代码。从现在开始,这就是我们通常要做的。它看起来是这样的:

42
 - : int = 42

第一个包含 42 的代码块是我们让OCaml运行的代码。如果你想在 utop 中输入,可以复制粘贴。在区块的右上角有一个图标,可以很容易地做到这一点。只需要记住在结尾加上双分号。第二个代码块缩进了一点,是翻译本书时OCaml的输出。

如果你在web浏览器中查看,请在右上角寻找下载图标。选择 .md 选项,你将看到本书这一页的原始[MyST Markdown][myst-parser.readthedocs.io/en/latest/]…

可以使用 let 定义将值绑定到名称上,如下所示:

let x = 42
val x : int = 42

让我们再次来仔细研究 utop 响应,这次从左到右阅读:

  • 一个值被绑定到名称上,因此有 val 关键字。

  • x 是值绑定到的名称。

  • int 是值的类型。

  • 42 是值。

你可以把整个输出读成“xint 类型,等于 42”。

函数

函数可以使用如下语法在 toplevel 中定义:

let increment x = x + 1
val increment : int -> int = <fun>

我们来研究一下这个响应:

  • increment 是值绑定的标识符。

  • int -> int 是值的类型。这是一种接受 int 作为输入,并产生 int 作为输出的函数。把箭头 -> 看作是一种视觉隐喻,表示从一个值转换到另一个值——这就是函数所做的事情。

  • 该值是一个函数,toplevel 选择不打印它(因为它现在已经被编译,并且在内存中有一种表示形式,不容易进行美化输出)。相反,toplevel 仅打印一个占位符 <fun>

<fun> 本身不是一个值。它仅指示一个不可打印的函数值。

你可以像下面这样“调用”函数:

increment 0
increment(21)
increment (increment 5)

但是,在OCaml中,通常的词汇是“应用”函数,而不是“调用”函数。

请注意,OCaml在是否写括号以及是否写空格方面非常灵活。第一次学习OCaml的挑战之一是弄清楚什么时候需要括号。因此,如果你发现自己有语法错误的问题,一个策略是尝试添加一些括号。不过,首选的样式通常是在不需要的时候省略括号。所以, increment 21increment(21) 更好。

在 toplevel 加载代码

除了允许你定义函数之外,toplevel 还接受不是OCaml代码而是告诉 toplevel 自身做某事的指令。所有指令都以 # 字符开头。也许最常见的指令是 #use,它将文件中的所有代码加载到 toplevel 中,就像你将该文件中的代码输入到 toplevel 中一样。

例如,假设你创建了一个名为 mycode.ml 的文件。在该文件中放入以下代码:

let inc x = x + 1

启动 toplevel。尝试输入以下表达式,并观察错误:

:tags: ["raises-exception"]
inc 3

File "[7]", line 1, characters 0-3:
1 | inc 3
    ^^^
Error: Unbound value inc
Hint: Did you mean incr?

发生错误是因为 toplevel 还不知道关于名为 inc 的函数的任何信息。现在向 toplevel 发出以下指令(译者注:需要在 mycode.ml 文件所在目录打开命令行并启动 utop):

# #use "mycode.ml";;

请注意,上面的第一个 # 字符表示 toplevel 提示。第二个 # 字符是用来告诉 toplevel 你正在发出指令的。如果没有这个字符,toplevel 会认为你在尝试应用一个名为use的函数。

现在再试一次:

inc 3
 - : int = 4

toplevel 工作流

当 toplevel 使用存储在文件中的代码时,最佳工作流程是:

  • 编辑文件中的代码。

  • 使用 #use 将代码加载到顶层。

  • 交互式地测试代码。

  • 退出 toplevel。**警告:**不要跳过这一步。

假设你想修复代码中的一个错误。我们希望不退出 toplevel,直接编辑文件,然后在同一个 toplevel 会话中重新使用 #use 指令加载。抵制这种诱惑。在同一个会话中,从先前的 #use 指令加载的“修改过的代码”可能会导致令人惊讶的事情发生 —— 无论如何,当你第一次学习这门语言时,这是令人惊讶的。因此,在重用文件之前,请务必退出 toplevel。

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