泛型

OCaml 里用以下方式使用泛型

1
2
type f = a Template
(* 等价于 C++ 的 using f = Template<a> *)

Lists

构造列表有三种语法

1
2
3
4
[] (* 空列表 *)
e1 :: e2 (* 列表拼接,这个运算符是 Right Associative 的
并且对于这种运算符,`e1 : t` 而 `e2 : t list` *)
[e1; e2; e3; ...; en] (* 列表枚举 *)

列表的基础操作:

1
lst1 @ lst2 (* 列表拼接 *)

Pattern Matching

1
2
3
4
5
6
(* 用 match with 进行 pattern matching,注意必须涵盖所有可能的情况 *)
match e1 with
| value1 -> e2
| value2 -> e3
| _ -> e4 (* _ 表示匹配任何值 *)
(* 注意,这里的 value1/value2 **只发生** binding,而不是变量代表的值 *)

如果 pattern matching 的对象是最后一个参数,那么我们可以用 function 语法糖

1
2
3
4
5
6
7
8
let rec sum lst =
match lst with
| [] -> 0
| h :: t -> h + sum t
(* 等价于 *)
let rec sum = function
| [] -> 0
| h :: t -> h + sum t

Variants

A variant is a data type representing a value that is one of several possibilities. 类似其他语言里的 enum

1
2
type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat
let d = Tue

后定义的 variant 如果有相同的名字,那么会产生覆盖,所以建议在定义 variant 的时候,带上独特的前缀.

1
2
3
type t1 = C | D
type t2 = D | E
let x = D (* val x : t2 = D *)

使用 OUnit 进行单元测试

假设 sum.ml 里面定义了函数

1
2
3
let rec sum = function
| [] -> 0
| x :: xs -> x + sum xs

我们创建一个 test.ml 进行单元测试

1
2
3
4
5
6
7
8
9
10
open OUnit2
open Sum

let tests = "test suite for sum" >::: [
"empty" >:: (fun _ -> assert_equal 0 (sum []));
"singleton" >:: (fun _ -> assert_equal 1 (sum [1]));
"two_elements" >:: (fun _ -> assert_equal 3 (sum [1; 2]));
]

let _ = run_test_tt_main tests

在项目的 dune 文件里对 test.ml 添加编译

1
2
3
(executable
(name test)
(libraries ounit2))

运行

1
dune exec ./text.exe

现在我们来看看 OUnit 如何定义单元测试.

1
2
3
4
5
6
7
8
9
10
11
12
[
"empty" >:: (fun _ -> assert_equal 0 (sum []));
"one" >:: (fun _ -> assert_equal 1 (sum [1]));
"onetwo" >:: (fun _ -> assert_equal 3 (sum [1; 2]));
]
(*
每个单元测试的结构如下:

{单元测试的名称} >:: {单元测试的具体内容(匿名函数)}

匿名函数的输入是 `test context`,函数调用 OUnit 内置的 `assert_equal`.
*)

然后,我们创建 test suite:

1
2
3
4
5
let tests = "test suite for sum" >::: [
"empty" >:: (fun _ -> assert_equal 0 (sum []));
"singleton" >:: (fun _ -> assert_equal 1 (sum [1]));
"two_elements" >:: (fun _ -> assert_equal 3 (sum [1; 2]));
]

>::: 放在 test suite 和 a list of unit tests 之间.

最后运行测试:let _ = run_test_tt_main tests


在匿名函数中添加 ~printer 参数可以打印 first argument 和 second argument.


Records

我们还是用 type 关键字定义有字段的结构体,字段之间用 ; 分隔.

1
2
3
4
5
6
7
8
9
10
type ptype = TNormal | TFire | TWater
type mon = {name : string; hp : int; ptype : ptype}

(* 定义一个结构体 *)
let c = {name = "Charmander"; hp = 39; ptype = TFire};;
c.hp (* 获取字段 *)

(* 对结构体进行 Pattern Matching *)
match c with {name = n; hp = h; ptype = t} -> h
match c with {name; hp; ptype} -> hp

Record Copy

1
2
3
4
5
(*
返回一个新的结构体:
这个新结构体所有其他字段和 e 都相同,除了 f1 f2 ... fn 这些字段.
*)
{e with f1 = e1; ...; fn = en}

Tuples

1
2
(* 用逗号分隔进行定义,同样可以 pattern matching *)
(e1, e2, ..., en)
Type Synonyms

已有类型的别名,还是用 type 进行定义

1
2
3
4
5
6
7
8
9
10
11
12
type point = float * float
type vector = float list
type matrix = float list list

(* 调用示例 *)
let get_x = fun (x, _) -> x

let p1 : point = (1., 2.)
let p2 : float * float = (1., 3.)

let a = get_x p1
let b = get_x p2

Options

和 Haskell 的 Maybe 是类似的生态位

1
2
3
4
let extract o =
match o with
| Some i -> string_of_int i
| None -> ""