OCaml学习——《Real-World-Ocaml》Chap4

Chap4 文件、模块和程序

(本章很多内容 15 年中文版不适用,需要 2nd Edition 英文版,现在已经转向英文版学习)

从练习转向实践,抛开顶层环境,由文件来构建程序。

文件不只是存储和管理代码的一种方便方式,在 OCaml 中,文件还与模块对应,相当于边界,可以把程序划分为不同的概念单元。

单文件程序

实际例子,从stdin读入输入行,并计算各行的频数,最后,会写出频数最高的行。构建freq.ml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
open Base
open Stdio

let build_counts () =
In_channel.fold_lines In_channel.stdin ~init:[] ~f:(fun counts line ->
let count =
match List.Assoc.find ~equal:String.equal counts line with
| None -> 0
| Some x -> x
in
(* List assoc 关联列表 添加 *)
List.Assoc.add ~equal:String.equal counts line (count + 1)
)

let () =
build_counts ()
|> List.sort ~compare:(fun (_,x) (_,y) -> Int.descending x y)
|> (fun l -> List.take l 10)
|> List.iter ~f:(fun (line,count) -> printf "%3d: %s\n" count line)

funciton build_counts readds in lines from stdin, constructing from those lines an association list with the frequencies of each line.

Main 函数?

OCaml 与 C 不同,程序并没有一个唯一的main函数。执行程序时,实现文件中的所有语句会按其链接的顺序进行计算。某种程度上,整个代码基都是一个庞大的 main 函数。

let () =是一个惯用法,这里 let 绑定是对一个 unit 类型值的模式匹配,它是为了确保右边的表达式返回 unit,对于主要为得到副作用的函数来说,这个用法很常见。

直接使用ocamlopt freq.ml freq编译该文件会出现编译错误,因为无法找到BaseStdio。使用ocamlfind找寻依赖或者使用dune

使用ocamlfind找到对应的 pkg 再进行编译。

1
ocamlfind ocamlopt -linkpkg -package base -package stdio freq.ml -o freq

BYTECODE VERSUS NATIVE CODE

OCaml ships with two compilers

  • ocamlopt: the native code compiler
  • ocamlc: the bytecode compiler

Aside from performance, executables generated by the two compilers have nearly identical behavior. There are a few things to be aware of.

  • The bytecode compiler can be used on more architectures, and has some tools that are not available for native code. (although gdb, the GNU Debugger, works with some limitations on OCaml native-code applications).
  • The bytecode compiler is also quicker than the native-code compiler.
  • In order to run a bytecode executable, you typically need to have OCaml installed on the system in question. That's not strictly required, though, since you can build a bytecode executable with an embedded runtime, using the -custom compiler flag.

多文件程序和模块

Souce files in Ocaml are tied into the module system, with each file compiling down into a module whose name is derived from the name of the file. At its simplest, you can think of a module as a collection of definitions that are stored within a namespace.

每个文件编译为一个模块。将之前的freq.ml拆分进行练习。拆分出counter.ml,会编译为名为Counter的模块。

After refactor

1
2
3
4
5
6
7
8
9
open Base

let touch counts line =
let count =
match List.Assoc.find ~equal:String.equal counts line with
| None -> 0
| Some x -> x
in
List.Assoc.add ~equal:String.equal counts line (count + 1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(* open Base
* open Stdio
*
* let build_counts () =
* In_channel.fold_lines In_channel.stdin ~init:[] ~f:(fun counts line ->
* let count =
* match List.Assoc.find ~equal:String.equal counts line with
* | None -> 0
* | Some x -> x
*
* in
* List.Assoc.add ~equal:String.equal counts line (count + 1)
* )
*
* let () =
* build_counts ()
* |> List.sort ~compare:(fun (_,x) (_,y) -> Int.descending x y)
* |> (fun l -> List.take l 10)
* |> List.iter ~f:(fun (line,count) -> printf "%3d: %s\n" count line) *)

(* Rewrite freq.ml *)

(* With counter.ml we compiled to Counter module, we will change the anonymous fun counts line -> ... to Counter.touch *)

open Base
open Stdio

let build_counts () =
In_channel.fold_lines In_channel.stdin ~init:[] ~f:Counter.touch

let () =
build_counts ()
|> List.sort ~compare:(fun (_,x) (_,y) -> Int.descending x y)
|> (fun l -> List.take l 10)
|> List.iter ~f:(fun (line,count) -> printf "%3d: %s\n" count line)

1
ocamlfind ocamlopt -linkpkg -package base -package stdio  counter.ml freq.ml -o freq

Hint: counter.ml should be placed before freq.ml as the later relied on the former.

签名和抽象类型

签名(signature),在.mli中。例如filename.ml定义的模块会受文件filename.mli中签名的约束。

语法

1
val <identifier> : <type>

可以要求 OCaml 从源文件自动生成一个,然后修改这个生成文件来满足需求。

1
core build counter.inferred.mli