Ocaml 中的module system

来源:互联网 发布:淘宝达人入口网址 编辑:程序博客网 时间:2024/05/21 06:38

文章内容翻译自 manual-ocaml-4.02点击打开链接, 和Ocaml Tutorials其中加入了个人的理解


Modules

基本用法:

在Ocaml中,代码都是被包装在一个个的module中的,一个module本身也可以是另一个module的子module,非常类似于文件系统中的目录结构,但这样的用法很少.

当你写了一个程序,此程序使用了两个文件(amodule.ml 和 bmodule.ml   备注:Ocaml中文件后缀是.ml),那么这两个文件就会自动的定义了两个module,分别叫做 Amodule 和 Bmodule, 你写在这两个文件中的东西就可以通过这两个module提供给外界

Here is the code that we have in our file amodule.ml:

let hello () = print_endline "Hello"

此句ocaml-top会做这样的提示val hello : unit -> unit = <fun>  表示我们定义了一个函数叫做hello,该函数的输入和输出都是unit

And here is what we have in bmodule.ml:

Amodule.hello ()

Usually files are compiled one by one, let's do it:

ocamlopt -c amodule.mlocamlopt -c bmodule.mlocamlopt -o hello amodule.cmx bmodule.cmx

现在我们有一个很好的可执行文件用来打印 “Hello”。如你所见,如果你要访问一个给定模块的任何东西,你要用模块的名字(通常是大写字母开头)后面跟一个点号,然后是你要用的东西。可能是一个值,一个类型构造器,或者是给定模块能提供的任何东西。

标准库提供了很多module。比如,List.iter指定List模块中的iter函数。 好了,如果你正在重度使用一个给定的模块,你可以使这个模块的内容直接可以访问。要实现这个,我们要使用open指令。在我们的例子中,bmodule.ml可以写成这样:

open Amodule;;hello ();;

很类似java 中的import

注意,人们倾向于避免使用丑陋的“;;”,所以这样写更加普遍:

open Amodulelet () =  hello ()

不管怎样,用不用open是个人品味的问题。一些模块中的命名在很多其他模块中也有。List模块就是这样的例子。通常我们不用open List。我们直接写List.XXX 这样可以避免混淆。像Printf模块,提供通常不冲突的名字,比如printf。为了避免到处写Printf.printf,在文件开头放一句open Printf是有道理的。


有一个简短的例子描述我们刚刚提到的(在toplevel中)。

# open Printf  let my_data = [ "a"; "beautiful"; "day" ]  let () = List.iter (fun s -> printf "%s\n" s) my_data;;abeautifuldayval my_data : string list = ["a"; "beautiful"; "day"]


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Interfaces and signatures 接口和签名(暂时翻译为签名)

一个模块可以给使用它的其他程序提供若干东西(函数,类型,子模块,……)。如果没有什么特别指定,在模块中定义的一切可以从外部访问。这样在小的个人程序中是一般可以的,但是在很多情况下,一个模块只提供它想要给出来的会更好,任何内部使用的辅助的函数和类型我们通常是希望对外面隐藏起来的。
要实现这个我们得定义模块接口,接口就像罩在模块实现上的面具。就像模块会从 .ml 文件自动被导出,相应的模块接口或者叫签名会从 .mli 文件得到(在OCaml中,*.mli文件是被导出的模块的签名(signature),猜测这个i表示的是interface,且编译器严格执行它 就拿一个叫Foo模块来说, 通常你会有两个文件: foo.mlfoo.mlifoo.ml 是实现, foo.mli是接口或签名.)。它包含了一个值的列表以及他们的类型,以及更多东西。让我们重写amodule.ml文件。

# let message = "Hello"  let hello () = print_endline message;;val message : string = "Hello"val hello : unit -> unit = <fun>

因此 Amodule 的接口就是下面这样: 值以及他们的类型,一个字符串,一个函数

val message : stringval hello : unit -> unit

我们现在想把message隐藏起来,可以通过定义一个受限的接口来实现. This is our amodule.mli file:

val hello : unit -> unit(** Displays a greeting message. *)

(note that it is a good habit to document .mli files, using the format supported by ocamldoc)

.mli 必须在相应的 .ml 文件之前被编译. They are compiled using ocamlc, even if .ml files are compiled to native code using ocamlopt:

ocamlc -c amodule.mliocamlopt -c amodule.ml...


----------------------------------------------------------------------------------------------------------------------------------------------------

Abstract types

类型定义是怎么样的呢?我们看到像函数这样的值可以采用把它们的名字和类型放到 .mli 文件的方式来导出。

val hello : unit -> unit

但是模块经常定义新的类型。让我们来定义一个简单的record类型,用来表达一个日期。type关键字用来定义一个新的类型

type date = { day : int;  month : int;  year : int }

当要写 .mli 文件的时候有四种选择,而不是两种:

  1. The type is completely omitted from the signature.
  2. The type definition is copy-pasted into the signature.
  3. The type is made abstract: only its name is given.
  4. The record fields are made read-only: type date = private { ... }

  1. 类型在签名signature中完全忽略
  2. 类型定义复制拷贝到签名,也就是说签名中的跟ml中的一样
  3. 类型做成抽象的:只给出名字
  4. record的域做成只读的:type date = private { ... }

In case 3, it would be the following code:

type date

现在,这个模块的用户能操作date类型的对象,但是他们不能直接访问record的域,他们必须使用模块提供的函数。假设这个模块提供三个函数,一个用来创建一个日期,一个用来计算两个日期之间的间隔,还有一个用年的形式返回一个日期。

type dateval create : ?days:int -> ?months:int -> ?years:int -> unit -> dateval sub : date -> date -> dateval years : date -> float

只有createsub才能用来创建daterecord。因此,这个模块的用户不可能创建不规范的record。实际上,我们的实现使用record,但是我们可以修改它,并且确保不破坏任何依赖这个模块的代码!这在一个库中很重要,同一个库之后的版本能够暴露同样的接口,同时可以内部改变实现,包括数据结构。比如我们可以不使用record来实现日期,使用其他的数据结构, 对外面的用户来说你内部如何实现我不关心






0 0
原创粉丝点击